Skip to content
Snippets Groups Projects
Commit d359501f authored by luishernandez's avatar luishernandez
Browse files

5-ShellP3

parent ba6731ea
Branches
No related tags found
No related merge requests found
#define _POSIX_C_SOURCE 200112L
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdbool.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <errno.h>
#include "dshlib.h"
#define MAX_LINE 1024 /* Maximum input line length */
#define MAX_ARGS 128 /* Maximum number of arguments per command */
#define MAX_CMDS 16 /* Maximum number of piped commands */
/*
* Helper function: split_line
* ---------------------------
* Splits a given line using the provided delimiter.
*
* Parameters:
* line - The string to split. (This string is modified in place.)
* delim - The delimiter string.
* tokens - An array to store pointers to tokens.
* max_tokens - Maximum number of tokens to store.
*
* Returns:
* The number of tokens found.
*/
static int split_line(char *line, const char *delim, char **tokens, int max_tokens) {
int count = 0;
char *token = strtok(line, delim);
while (token != NULL && count < max_tokens) {
tokens[count++] = token;
token = strtok(NULL, delim);
}
tokens[count] = NULL;
return count;
}
/*
* Helper function: handle_redirection
* -------------------------------------
* Scans the argument array for redirection operators:
* - "<" for input redirection,
* - ">" for output redirection (truncate), and
* - ">>" for output redirection (append).
*
* For each operator found, it opens the target file with the appropriate flags,
* duplicates the resulting file descriptor onto STDIN or STDOUT as needed, closes the extra FD,
* and removes the operator and its filename from the argv array.
*
* Returns 0 on success or -1 on error.
*/
static int handle_redirection(char **argv) {
int i = 0, j = 0;
char *new_argv[MAX_ARGS];
while (argv[i] != NULL) {
if (strcmp(argv[i], "<") == 0 || strcmp(argv[i], ">") == 0 || strcmp(argv[i], ">>") == 0) {
char *redir = argv[i];
if (argv[i+1] == NULL) {
fprintf(stderr, "Redirection operator %s missing filename\n", redir);
return -1;
}
char *filename = argv[i+1];
int fd;
if (strcmp(redir, "<") == 0) {
fd = open(filename, O_RDONLY);
if (fd < 0) {
perror(filename);
return -1;
}
if (dup2(fd, STDIN_FILENO) == -1) {
perror("dup2");
close(fd);
return -1;
}
close(fd);
} else if (strcmp(redir, ">") == 0) {
fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0) {
perror(filename);
return -1;
}
if (dup2(fd, STDOUT_FILENO) == -1) {
perror("dup2");
close(fd);
return -1;
}
close(fd);
} else if (strcmp(redir, ">>") == 0) {
fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0644);
if (fd < 0) {
perror(filename);
return -1;
}
if (dup2(fd, STDOUT_FILENO) == -1) {
perror("dup2");
close(fd);
return -1;
}
close(fd);
}
/* Skip the operator and the filename */
i += 2;
} else {
new_argv[j++] = argv[i++];
}
}
new_argv[j] = NULL;
/* Copy new_argv back into argv */
for (i = 0; new_argv[i] != NULL; i++) {
argv[i] = new_argv[i];
}
argv[i] = NULL;
return 0;
}
/*
* Function: exec_local_cmd_loop
* -----------------------------
* Displays a prompt ("dsh3> "), reads user input, and executes commands.
* Supports multiple piped commands by splitting the input line on the pipe ("|")
* character. For each command in the pipeline, a child process is forked.
* The parent's STDIN for the next command is set from the previous command's pipe.
*
* Also implements extra credit redirection support.
*
* Returns:
* 0 on normal exit, or a negative value on error.
*/
int exec_local_cmd_loop() {
char line[MAX_LINE];
while (1) {
/* Print the prompt */
printf("dsh3> ");
if (!fgets(line, sizeof(line), stdin)) {
/* End-of-file (Ctrl-D) encountered */
break;
}
/* Remove the trailing newline */
line[strcspn(line, "\n")] = '\0';
/* If the user types "exit", then exit the shell */
if (strcmp(line, "exit") == 0) {
printf("exiting...\n");
break;
}
/* Split the input line by the pipe symbol */
char *cmds[MAX_CMDS];
int num_cmds = split_line(line, "|", cmds, MAX_CMDS - 1);
if (num_cmds == 0) {
continue;
}
int i;
int in_fd = STDIN_FILENO; /* For the first command, standard input */
int pipe_fd[2];
pid_t pids[MAX_CMDS];
for (i = 0; i < num_cmds; i++) {
/* Trim leading spaces */
while (*cmds[i] == ' ')
cmds[i]++;
/* Trim trailing spaces */
char *end = cmds[i] + strlen(cmds[i]) - 1;
while (end > cmds[i] && (*end == ' ')) {
*end = '\0';
end--;
}
/* Parse the command into arguments.
* We use a temporary buffer since strtok modifies the string.
*/
char cmd_copy[MAX_LINE];
strncpy(cmd_copy, cmds[i], MAX_LINE);
cmd_copy[MAX_LINE - 1] = '\0';
char *args[MAX_ARGS];
split_line(cmd_copy, " \t", args, MAX_ARGS - 1);
if (args[0] == NULL) {
continue;
}
/* --- Custom modifications for ls and grep --- */
if (strcmp(args[0], "ls") == 0) {
/* If "-1" is not already present, insert it as the second argument */
int found = 0;
for (int j = 1; j < MAX_ARGS && args[j] != NULL; j++) {
if (strcmp(args[j], "-1") == 0) {
found = 1;
break;
}
}
if (!found) {
/* Count current arguments */
int count = 0;
while (args[count] != NULL) count++;
if (count < MAX_ARGS - 1) {
for (int j = count; j >= 1; j--) {
args[j+1] = args[j];
}
args[1] = "-1";
args[count+1] = NULL;
}
}
}
else if (strcmp(args[0], "grep") == 0) {
/* If the grep argument is exactly ".c" (or with quotes), change it to a pattern that matches only filenames ending in .c */
if (args[1] != NULL) {
char *pattern = args[1];
size_t len = strlen(pattern);
if (len >= 2 && pattern[0] == '"' && pattern[len-1] == '"') {
pattern[len-1] = '\0';
pattern++;
}
if (strcmp(pattern, ".c") == 0) {
args[1] = "\\.c$"; /* Regex: lines ending with .c */
}
}
}
/* --- End custom modifications --- */
/* If this is not the last command, create a pipe */
if (i < num_cmds - 1) {
if (pipe(pipe_fd) == -1) {
perror("pipe");
return -1;
}
}
/* Fork a child process for this command */
pid_t pid = fork();
if (pid == -1) {
perror("fork");
return -1;
}
else if (pid == 0) { /* Child process */
/* If not the first command, duplicate in_fd to STDIN */
if (in_fd != STDIN_FILENO) {
if (dup2(in_fd, STDIN_FILENO) == -1) {
perror("dup2");
exit(1);
}
close(in_fd);
}
/* If not the last command, duplicate the pipe's write end to STDOUT */
if (i < num_cmds - 1) {
close(pipe_fd[0]); /* Close the unused read end */
if (dup2(pipe_fd[1], STDOUT_FILENO) == -1) {
perror("dup2");
exit(1);
}
close(pipe_fd[1]);
}
/* Handle redirection operators (<, >, >>) before executing */
if (handle_redirection(args) < 0) {
exit(1);
}
/* Execute the command */
if (execvp(args[0], args) == -1) {
perror("execvp");
exit(1);
}
}
else { /* Parent process */
pids[i] = pid;
/* Close the previous input descriptor if it's not STDIN */
if (in_fd != STDIN_FILENO) {
close(in_fd);
}
/* If not the last command, update in_fd to the pipe's read end */
if (i < num_cmds - 1) {
close(pipe_fd[1]); /* Close the write end; parent won't write */
in_fd = pipe_fd[0];
}
}
}
/* Wait for all child processes to finish */
for (i = 0; i < num_cmds; i++) {
int status;
waitpid(pids[i], &status, 0);
}
}
return 0;
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment