diff --git a/Assignment-05/starter/dsh b/Assignment-05/starter/dsh new file mode 100755 index 0000000000000000000000000000000000000000..d9bbee0f94d14b88ce2b9fb8aeccce18ea7b2709 Binary files /dev/null and b/Assignment-05/starter/dsh differ diff --git a/Assignment-05/starter/dshlib.c b/Assignment-05/starter/dshlib.c index a21bbe118fc2e9a07dbce69cd35702cf72f86fe0..cda615e4041620fc409f812571fedcf6ac5ea930 100644 --- a/Assignment-05/starter/dshlib.c +++ b/Assignment-05/starter/dshlib.c @@ -9,6 +9,16 @@ #include "dshlib.h" +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <stdbool.h> +#include <unistd.h> + +#include "dshlib.h" + +// int build_cmd_buff(char *cmd_line, cmd_buff_t *cmd_buff) { if (cmd_line == NULL || cmd_buff == NULL) { return ERR_CMD_OR_ARGS_TOO_BIG; @@ -19,175 +29,194 @@ int build_cmd_buff(char *cmd_line, cmd_buff_t *cmd_buff) { return ERR_MEMORY; } - char *trimmed_line = original_line; - while (*trimmed_line == ' ') { - trimmed_line++; - } - - size_t len = strlen(trimmed_line); - while (len > 0 && trimmed_line[len - 1] == ' ') { - trimmed_line[--len] = '\0'; - } - cmd_buff->argc = 0; cmd_buff->_cmd_buffer = original_line; - char *saveptr1; - char *token = strtok_r(trimmed_line, " ", &saveptr1); - - while (token != NULL && cmd_buff->argc < CMD_ARGV_MAX) { - if (token[0] == '"' || token[0] == '\'') { - char *end_quote = strchr(token + 1, token[0]); - if (end_quote != NULL) { - *end_quote = '\0'; - cmd_buff->argv[cmd_buff->argc++] = token + 1; - token = strtok_r(NULL, " ", &saveptr1); - continue; + char *ptr = original_line; + char *arg_start = NULL; + bool in_quotes = false;//For quotes + char quote_char = '\0'; + + while (*ptr != '\0') { + if ((isspace((unsigned char)*ptr) && !in_quotes)) { + if (arg_start != NULL) { + *ptr = '\0'; + cmd_buff->argv[cmd_buff->argc++] = arg_start; + arg_start = NULL; + } + } else if (*ptr == '"' || *ptr == '\'') { + if (in_quotes && *ptr == quote_char) { + in_quotes = false; + quote_char = '\0'; + *ptr = '\0'; // End the argument at the closing quote + } else if (!in_quotes) { + in_quotes = true; + quote_char = *ptr; + arg_start = ptr + 1; // Start the argument after the opening quote + } + } else { + if (arg_start == NULL) { + arg_start = ptr; } } - cmd_buff->argv[cmd_buff->argc++] = token; - token = strtok_r(NULL, " ", &saveptr1); + ptr++; } + + if (arg_start != NULL) { + cmd_buff->argv[cmd_buff->argc++] = arg_start; + } + cmd_buff->argv[cmd_buff->argc] = NULL; - return OK; -} + // Debugging output to verify parsing + // printf("Parsed command:\n"); + // for (int i = 0; i < cmd_buff->argc; i++) { + // printf(" argv[%d]: %s\n", i, cmd_buff->argv[i]); + // } -int execute_pipeline(command_list_t *cmd_list) { - int num_commands = cmd_list->num; - int pipefd[2]; - int prev_pipe_read = -1; - pid_t pids[num_commands]; - for (int i = 0; i < num_commands; i++) { - if (i < num_commands - 1) { - if (pipe(pipefd) == -1) { - perror("pipe"); - return ERR_MEMORY; - } - } + return OK; +} - pids[i] = fork(); - if (pids[i] == -1) { - perror("fork"); - return ERR_MEMORY; - } +char *trim_whitespace(char *str) { //Had to make a new function for readability + char *end; - if (pids[i] == 0) { - if (prev_pipe_read != -1) { - dup2(prev_pipe_read, STDIN_FILENO); - close(prev_pipe_read); - } + while (isspace((unsigned char)*str)) str++; - if (i < num_commands - 1) { - dup2(pipefd[1], STDOUT_FILENO); - close(pipefd[1]); - close(pipefd[0]); - } + if (*str == 0) // All spaces? + return str; - if (prev_pipe_read != -1) { - close(prev_pipe_read); - } + // Trim trailing space + end = str + strlen(str) - 1; + while (end > str && isspace((unsigned char)*end)) end--; - cmd_buff_t *cmd = &cmd_list->commands[i]; + // new null terminator + *(end + 1) = '\0'; - if (cmd->argv[0] == NULL) { - fprintf(stderr, "execvp error: command is NULL\n"); - exit(ERR_EXEC_CMD); - } + return str; +} - execvp(cmd->argv[0], cmd->argv); - perror("execvp"); - exit(ERR_EXEC_CMD); - } else { - if (prev_pipe_read != -1) { - close(prev_pipe_read); - } - if (i < num_commands - 1) { - prev_pipe_read = pipefd[0]; - close(pipefd[1]); - } - } + +int parse_pipeline(const char *cmd_line, command_list_t *clist) { + if (cmd_line == NULL || clist == NULL) { + return ERR_CMD_OR_ARGS_TOO_BIG; } - if (prev_pipe_read != -1) { - char buffer[1024]; - ssize_t bytes_read; - while ((bytes_read = read(prev_pipe_read, buffer, sizeof(buffer) - 1)) > 0) { - buffer[bytes_read] = '\0'; - printf("%s", buffer); + char *line_copy = strdup(cmd_line); + if (line_copy == NULL) { + return ERR_MEMORY; + } + //Parsing using pipe. + clist->num = 0; + char *saveptr; + char *command = strtok_r(line_copy, "|", &saveptr); + + while (command != NULL) { + if (clist->num >= CMD_MAX) { + free(line_copy); + return ERR_TOO_MANY_COMMANDS; } - if (bytes_read < 0) { - perror("read"); + + cmd_buff_t *cmd_buff = &clist->commands[clist->num]; + int result = build_cmd_buff(command, cmd_buff); + if (result != OK) { + free(line_copy); + return result; } - close(prev_pipe_read); - } - for (int i = 0; i < num_commands; i++) { - waitpid(pids[i], NULL, 0); + clist->num++; + command = strtok_r(NULL, "|", &saveptr); } + free(line_copy); return OK; } +int execute_pipeline(command_list_t *clist) { + int num_commands = clist->num; + int pipefd[2 * (num_commands - 1)]; + pid_t pids[num_commands]; + + // for (int i = 0; i < clist->num; i++) { + // printf("Command %d:\n", i); + // for (int j = 0; clist->commands[i].argv[j] != NULL; j++) { + // printf(" argv[%d]: %s\n", j, clist->commands[i].argv[j]); + // } + // } -int parse_pipeline(char *cmd_line, command_list_t *clist) { - if (cmd_line == NULL || clist == NULL) { - return ERR_CMD_OR_ARGS_TOO_BIG; - } - char *original_line = strdup(cmd_line); - if (original_line == NULL) { - return ERR_MEMORY; - } - char *trimmed_line = original_line; - while (*trimmed_line == ' ') { - trimmed_line++; - } - size_t len = strlen(trimmed_line); - while (len > 0 && trimmed_line[len - 1] == ' ') { - trimmed_line[--len] = '\0'; + for (int i = 0; i < num_commands - 1; i++) { + if (pipe(pipefd + 2 * i) == -1) { + perror("pipe"); + return ERR_MEMORY; + } } - int command_count = 0; - char *saveptr1, *saveptr2; - char *command = strtok_r(trimmed_line, PIPE_STRING, &saveptr1); + // Fork processes for each command + for (int i = 0; i < num_commands; i++) { + pids[i] = fork(); + if (pids[i] == -1) { + perror("fork"); + return ERR_MEMORY; + } + + if (pids[i] == 0) { // Child process + // if not first moves input + if (i > 0) { + if (dup2(pipefd[2 * (i - 1)], STDIN_FILENO) == -1) { + perror("dup2 stdin"); + exit(EXIT_FAILURE); + } + } - while (command != NULL && command_count < CMD_MAX) { - memset(&clist->commands[command_count], 0, sizeof(cmd_buff_t)); + // Redirect output if not the last command + if (i < num_commands - 1) { + if (dup2(pipefd[2 * i + 1], STDOUT_FILENO) == -1) { + perror("dup2 stdout"); + exit(EXIT_FAILURE); + } + } - char *token = strtok_r(command, " ", &saveptr2); - if (token != NULL) { - int arg_count = 0; - while (token != NULL && arg_count < CMD_ARGV_MAX - 1) { - clist->commands[command_count].argv[arg_count] = token; - arg_count++; - token = strtok_r(NULL, " ", &saveptr2); + // Close all pipe file descriptors. Very important stuff + for (int j = 0; j < 2 * (num_commands - 1); j++) { + close(pipefd[j]); } - clist->commands[command_count].argv[arg_count] = NULL; + + // Execute the command + execvp(clist->commands[i].argv[0], clist->commands[i].argv); + perror("execvp"); + exit(EXIT_FAILURE); } + } - command_count++; - command = strtok_r(NULL, PIPE_STRING, &saveptr1); + // Closes all pipe + for (int i = 0; i < 2 * (num_commands - 1); i++) { + close(pipefd[i]); } - if (command != NULL) { - free(original_line); - return ERR_TOO_MANY_COMMANDS; + // waitpid so everyhting is smooth + for (int i = 0; i < num_commands; i++) { + int status; + waitpid(pids[i], &status, 0); + if (WIFEXITED(status) && WEXITSTATUS(status) != 0) { + fprintf(stderr, "Command %d exited with status %d\n", i, WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + fprintf(stderr, "Command %d terminated by signal %d\n", i, WTERMSIG(status)); + } } - clist->num = command_count; - free(original_line); return OK; } + + int exec_local_cmd_loop() { char cmd_buff[SH_CMD_MAX]; int rc = OK; @@ -197,7 +226,7 @@ int exec_local_cmd_loop() { if (fgets(cmd_buff, SH_CMD_MAX, stdin) == NULL) { printf("\n"); - break; + break; } cmd_buff[strcspn(cmd_buff, "\n")] = '\0'; @@ -216,11 +245,11 @@ int exec_local_cmd_loop() { continue; } - if (execute_pipeline(&cmd_list) != OK) { - fprintf(stderr, "%s\n", CMD_ERR_PIPE_LIMIT); - rc = ERR_MEMORY; - } + // Execute the pipeline + + execute_pipeline(&cmd_list); + // Free memory for each command's buffer for (int i = 0; i < cmd_list.num; i++) { free(cmd_list.commands[i]._cmd_buffer); } @@ -233,8 +262,8 @@ int exec_local_cmd_loop() { } if (strcmp(cmd.argv[0], EXIT_CMD) == 0) { - free(cmd._cmd_buffer); - break; + free(cmd._cmd_buffer); + break; } if (strcmp(cmd.argv[0], "cd") == 0) { @@ -258,7 +287,7 @@ int exec_local_cmd_loop() { } else if (pid == 0) { execvp(cmd.argv[0], cmd.argv); perror("execvp failed"); - exit(EXIT_FAILURE); + exit(EXIT_FAILURE); } else { int status; wait(&status); @@ -276,3 +305,4 @@ int exec_local_cmd_loop() { return rc; } + diff --git a/Assignment-05/starter/dshlib.h b/Assignment-05/starter/dshlib.h index fe3b24680bed23840340d37a89310125ae517306..0f949dcfd6284795a93293c206215d78a32ac94a 100644 --- a/Assignment-05/starter/dshlib.h +++ b/Assignment-05/starter/dshlib.h @@ -90,4 +90,4 @@ int execute_pipeline(command_list_t *clist); #define CMD_WARN_NO_CMD "warning: no commands provided\n" #define CMD_ERR_PIPE_LIMIT "error: piping limited to %d commands\n" -#endif \ No newline at end of file +#endif diff --git a/Assignment-05/starter/student_tests.sh b/Assignment-05/starter/student_tests.sh index 638bc341446f7580a80c2aff52971b8023407ea8..6047b4a77d0910700dfb77987077a9fd3fcbbc67 100644 --- a/Assignment-05/starter/student_tests.sh +++ b/Assignment-05/starter/student_tests.sh @@ -4,11 +4,53 @@ # # Create your unit tests suit in this file -@test "Example: check ls runs without errors" { - run ./dsh <<EOF -ls + +# Test pipeline: 'echo' output piped to 'grep' +@test "Test echo output piped to grep" { + run ./dsh <<EOF +echo "Hello World" | grep "World" +exit +EOF + + # Assertions + [ "$status" -eq 0 ] + [[ "$output" == *"Hello World"* ]] +} + +# Test pipeline with multiple commands: 'ls' piped to 'grep' and then to 'wc -l' +@test "Test ls piped to grep and wc -l" { + run ./dsh <<EOF +ls | grep "student_tests.sh" | wc -l +exit +EOF + + # Assertions + [ "$status" -eq 0 ] + [[ "$output" -eq 1 ]] +} + +# Test redirection: output of 'echo' redirected to a file +@test "Test output redirection to a file" { + run ./dsh <<EOF +echo "Test Output" > test_output.txt +cat test_output.txt +exit +EOF + + # Assertions + [ "$status" -eq 0 ] + [[ "$output" == *"Test Output"* ]] +} + +# Test background execution: 'sleep' command in the background +@test "Test background execution of sleep command" { + run ./dsh <<EOF +sleep 2 & +jobs +exit EOF # Assertions [ "$status" -eq 0 ] + [[ "$output" == *"sleep 2 &"* ]] }