From d359501f5f162e607837c38c70a6460af69010cc Mon Sep 17 00:00:00 2001 From: luishernandez <lahr730@gmail.com> Date: Tue, 4 Mar 2025 18:05:11 -0500 Subject: [PATCH] 5-ShellP3 --- 5-ShellP3/dshlib.c | 288 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 288 insertions(+) create mode 100644 5-ShellP3/dshlib.c diff --git a/5-ShellP3/dshlib.c b/5-ShellP3/dshlib.c new file mode 100644 index 0000000..725d85c --- /dev/null +++ b/5-ShellP3/dshlib.c @@ -0,0 +1,288 @@ +#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 -- GitLab