diff --git a/w7/dshlib.c b/w7/dshlib.c new file mode 100644 index 0000000000000000000000000000000000000000..9728a77751742c3aef8a1ec3011a2071214bdfbe --- /dev/null +++ b/w7/dshlib.c @@ -0,0 +1,622 @@ +#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 "dshlib.h" +#include <errno.h> +#include <limits.h> +#include <sys/types.h> +#include <sys/stat.h> + + + +/* + * Implement your exec_local_cmd_loop function by building a loop that prompts the + * user for input. Use the SH_PROMPT constant from dshlib.h and then + * use fgets to accept user input. + * + * while(1){ + * printf("%s", SH_PROMPT); + * if (fgets(cmd_buff, ARG_MAX, stdin) == NULL){ + * printf("\n"); + * break; + * } + * //remove the trailing \n from cmd_buff + * cmd_buff[strcspn(cmd_buff,"\n")] = '\0'; + * + * //IMPLEMENT THE REST OF THE REQUIREMENTS + * } + * + * Also, use the constants in the dshlib.h in this code. + * SH_CMD_MAX maximum buffer size for user input + * EXIT_CMD constant that terminates the dsh program + * SH_PROMPT the shell prompt + * OK the command was parsed properly + * WARN_NO_CMDS the user command was empty + * ERR_TOO_MANY_COMMANDS too many pipes used + * ERR_MEMORY dynamic memory management failure + * + * errors returned + * OK No error + * ERR_MEMORY Dynamic memory management failure + * WARN_NO_CMDS No commands parsed + * ERR_TOO_MANY_COMMANDS too many pipes used + * + * console messages + * CMD_WARN_NO_CMD print on WARN_NO_CMDS + * CMD_ERR_PIPE_LIMIT print on ERR_TOO_MANY_COMMANDS + * CMD_ERR_EXECUTE print on execution failure of external command + * + * Standard Library Functions You Might Want To Consider Using (assignment 1+) + * malloc(), free(), strlen(), fgets(), strcspn(), printf() + * + * Standard Library Functions You Might Want To Consider Using (assignment 2+) + * fork(), execvp(), exit(), chdir() + */ + +/* + int exec_local_cmd_loop() { // Main loop handling user input and command execution + char input_buffer[SH_CMD_MAX]; + cmd_buff_t cmd; + + while (1) { // Infinite loop to continuously prompt user for input + printf("%s", SH_PROMPT); // Display shell prompt + if (fgets(input_buffer, SH_CMD_MAX, stdin) == NULL) { // Read user input + printf("\n"); + break; + } + + // Remove trailing newline + input_buffer[strcspn(input_buffer, "\n")] = '\0'; // Remove newline character + + // Skip empty input + if (strlen(input_buffer) == 0) { // Skip empty input + continue; + } + + // Allocate command buffer + if (alloc_cmd_buff(&cmd) != OK) { + printf("Error allocating command buffer.\n"); + continue; + } + + // Build command buffer + if (build_cmd_buff(input_buffer, &cmd) != OK) { + printf("Error parsing command.\n"); + free_cmd_buff(&cmd); + continue; + } + + // Handle built-in commands + Built_In_Cmds built_in = match_command(cmd.argv[0]); // Identify built-in commands + if (built_in != BI_NOT_BI) { + exec_built_in_cmd(&cmd); + free_cmd_buff(&cmd); + continue; + } + + // Execute external command + exec_cmd(&cmd); + + // Free command buffer + free_cmd_buff(&cmd); + } + return OK; +} +*/ + +/* + +// Main loop handling user input and command execution +int exec_cmd(cmd_buff_t *cmd) { + pid_t pid = fork(); // Create a child process + if (pid < 0) { + perror("fork failed"); + return ERR_EXEC_CMD; + } + + if (pid == 0) { // Child process executes command + // Replace current process image with the new command using execvp() + // execvp() looks for the command in PATH and replaces the process if found + execvp(cmd->argv[0], cmd->argv); + perror("execvp failed"); + exit(ERR_EXEC_CMD); + } else { // Parent process waits for child process to complete + int status; + waitpid(pid, &status, 0); + if (WIFEXITED(status)) { // Check if process exited normally + int exit_status = WEXITSTATUS(status); + // printf("%d\n", exit_status); + if (exit_status != 0) { + return ERR_EXEC_CMD; + } + // continue; + } + } + return OK; +} + +*/ +/* +int exec_cmd(cmd_buff_t *cmd) { + pid_t pid = fork(); + if (pid < 0) { + perror("fork failed"); + return ERR_EXEC_CMD; + } + + if (pid == 0) { // Child Process + int fd; + for (int i = 0; i < cmd->argc; i++) { + if (strcmp(cmd->argv[i], ">") == 0) { + fd = open(cmd->argv[i + 1], O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd < 0) { + perror("output redirection failed"); + exit(ERR_EXEC_CMD); + } + dup2(fd, STDOUT_FILENO); + close(fd); + cmd->argv[i] = NULL; // Remove `>` from argv list + } + if (strcmp(cmd->argv[i], ">>") == 0) { + fd = open(cmd->argv[i + 1], O_WRONLY | O_CREAT | O_APPEND, 0644); + if (fd < 0) { + perror("append redirection failed"); + exit(ERR_EXEC_CMD); + } + dup2(fd, STDOUT_FILENO); + close(fd); + cmd->argv[i] = NULL; // Remove `>>` from argv list + } + } + + // Execute the actual command + execvp(cmd->argv[0], cmd->argv); + perror("execvp failed"); + exit(ERR_EXEC_CMD); + } else { + waitpid(pid, NULL, 0); + return OK; + } +} + +*/ + + + + + + +int exec_local_cmd_loop() { + char input_buffer[SH_CMD_MAX]; + command_list_t clist; + + while (1) { + printf("%s", SH_PROMPT); + if (fgets(input_buffer, SH_CMD_MAX, stdin) == NULL) { + printf("\n"); + break; + } + input_buffer[strcspn(input_buffer, "\n")] = '\0'; + if (strlen(input_buffer) == 0) continue; + + // Parsing command list + int rc = build_cmd_list(input_buffer, &clist); + if (rc != OK) { + if (rc == WARN_NO_CMDS) printf(CMD_WARN_NO_CMD); + else if (rc == ERR_TOO_MANY_COMMANDS) printf(CMD_ERR_PIPE_LIMIT, CMD_MAX); + continue; + } + + // Check built-in commands + if (clist.num > 1) { + for (int i = 0; i < clist.num; i++) { + Built_In_Cmds bi = match_command(clist.commands[i].argv[0]); + if (bi != BI_NOT_BI) { + fprintf(stderr, "error: built-in command in pipeline\n"); + rc = ERR_CMD_ARGS_BAD; + break; + } + } + if (rc != OK) { + free_cmd_list(&clist); + continue; + } + } else { + cmd_buff_t *cmd = &clist.commands[0]; + Built_In_Cmds bi = match_command(cmd->argv[0]); + if (bi != BI_NOT_BI) { + exec_built_in_cmd(cmd); + free_cmd_list(&clist); + continue; + } + } + + // Execution pipeline + execute_pipeline(&clist); + free_cmd_list(&clist); + } + return OK; +} + + + +/** + * @brief Executes a single command, handling output redirection. + */ +int exec_cmd(cmd_buff_t *cmd) { + pid_t pid = fork(); + if (pid < 0) { + perror("fork failed"); + return ERR_EXEC_CMD; + } + + if (pid == 0) { // Child Process + int fd = -1; + char *output_file = NULL; + int append_mode = 0; + char *argv_clean[CMD_ARGV_MAX]; + int i, j = 0; + + // Look for `>` or `>>` in the command arguments + for (i = 0; i < cmd->argc; i++) { + if (strcmp(cmd->argv[i], ">") == 0 || strcmp(cmd->argv[i], ">>") == 0) { + if (i + 1 >= cmd->argc) { + fprintf(stderr, "error: no output file specified\n"); + exit(ERR_EXEC_CMD); + } + output_file = cmd->argv[i + 1]; + append_mode = (strcmp(cmd->argv[i], ">>") == 0); + i++; // Skip the filename in arguments + } else { + argv_clean[j++] = cmd->argv[i]; // Copy valid arguments + } + } + argv_clean[j] = NULL; // Null-terminate the cleaned-up argument list + + // Redirect output to a file if needed + if (output_file) { + fd = open(output_file, O_WRONLY | O_CREAT | (append_mode ? O_APPEND : O_TRUNC), 0644); + if (fd < 0) { + perror("Failed to open output file"); + exit(ERR_EXEC_CMD); + } + dup2(fd, STDOUT_FILENO); + close(fd); + } + + // Execute the actual command + execvp(argv_clean[0], argv_clean); + perror("execvp failed"); + exit(ERR_EXEC_CMD); + } else { + waitpid(pid, NULL, 0); + return OK; + } +} + + + + + +// Processing pipeline execution +int execute_pipeline(command_list_t *clist) { + int num_cmds = clist->num; + int prev_pipe = -1; + int fd[2]; + pid_t pids[num_cmds]; + + for (int i = 0; i < num_cmds; i++) { + if (i < num_cmds - 1 && pipe(fd) < 0) { + perror("pipe"); + return ERR_EXEC_CMD; + } + + pids[i] = fork(); + if (pids[i] < 0) { + perror("fork"); + return ERR_EXEC_CMD; + } + + if (pids[i] == 0) { // Child + if (i > 0) { + dup2(prev_pipe, STDIN_FILENO); + close(prev_pipe); + } + if (i < num_cmds - 1) { + dup2(fd[1], STDOUT_FILENO); + close(fd[1]); + } + // Close all pipe ends + if (i > 0) close(prev_pipe); + if (i < num_cmds-1) close(fd[0]); + + execvp(clist->commands[i].argv[0], clist->commands[i].argv); + perror("execvp failed"); + exit(ERR_EXEC_CMD); + } else { // Parent + if (i > 0) close(prev_pipe); + if (i < num_cmds - 1) { + prev_pipe = fd[0]; + close(fd[1]); + } + } + } + + // Wait for all children + for (int i = 0; i < num_cmds; i++) { + waitpid(pids[i], NULL, 0); + } + return OK; +} + +/* +Built_In_Cmds exec_built_in_cmd(cmd_buff_t *cmd) { + if (strcmp(cmd->argv[0], "exit") == 0) { + exit(0); + } else if (strcmp(cmd->argv[0], "cd") == 0) { + const char *target = cmd->argc > 1 ? cmd->argv[1] : getenv("HOME"); + if (!target) { + fprintf(stderr, "cd: HOME not set\n"); + return BI_EXECUTED; + } + if (chdir(target) != 0) { + perror("cd failed"); + } else { + char cwd[PATH_MAX]; + getcwd(cwd, sizeof(cwd)); + printf("%s\n", cwd); + } + return BI_EXECUTED; + } else if (strcmp(cmd->argv[0], "rc") == 0) { + printf("%d\n", errno); + return BI_EXECUTED; + } + return BI_NOT_BI; +} + +*/ + +Built_In_Cmds exec_built_in_cmd(cmd_buff_t *cmd) { + if (strcmp(cmd->argv[0], "exit") == 0) { + free_cmd_buff(cmd); + exit(0); + } + + if (strcmp(cmd->argv[0], "cd") == 0) { + char target_dir[PATH_MAX]; + + if (cmd->argc > 1) { + strncpy(target_dir, cmd->argv[1], PATH_MAX); + } if (cmd->argc > 2) { // Too many arguments for cd + fprintf(stderr, "error: cd command accepts at most 1 argument\n"); + return ERR_CMD_ARGS_BAD; + } + // strncpy(target_dir, "/tmp", PATH_MAX); // Default to /tmp + + + // Ensure string is null-terminated + target_dir[PATH_MAX - 1] = '\0'; + + // Check if directory exists + struct stat st; + if (stat(target_dir, &st) == -1) { + if (mkdir(target_dir, 0777) != 0) { + //perror("mkdir failed"); + return BI_EXECUTED; + } + } + + // Check permissions before `chdir()` + if (access(target_dir, R_OK | X_OK) != 0) { + perror("cd failed"); + return BI_EXECUTED; + } + + // Attempt to change directory + if (chdir(target_dir) != 0) { + perror("cd failed"); + } else { + char cwd[PATH_MAX]; + if (getcwd(cwd, sizeof(cwd)) != NULL) { + //printf("%s\n", cwd); // Print working directory only once + } else { + perror("getcwd failed"); + } + } + + return BI_EXECUTED; + } + + if (strcmp(cmd->argv[0], "rc") == 0) { + printf("%d\n", errno); + return BI_EXECUTED; + } + + return BI_NOT_BI; +} + +Built_In_Cmds match_command(const char *input) { + if (strcmp(input, "exit") == 0) return BI_CMD_EXIT; + if (strcmp(input, "cd") == 0) return BI_CMD_CD; + if (strcmp(input, "rc") == 0) return -1; + return BI_NOT_BI; +} + + +/* + +Built_In_Cmds exec_built_in_cmd(cmd_buff_t *cmd) { + if (strcmp(cmd->argv[0], "exit") == 0) { + free_cmd_buff(cmd); + exit(0); + } + + if (strcmp(cmd->argv[0], "cd") == 0) { + char target_dir[PATH_MAX]; + + if (cmd->argc > 1) { + strncpy(target_dir, cmd->argv[1], PATH_MAX); + } if (cmd->argc > 2) { // Too many arguments for cd + fprintf(stderr, "error: cd command accepts at most 1 argument\n"); + return ERR_CMD_ARGS_BAD; + } + // strncpy(target_dir, "/tmp", PATH_MAX); // Default to /tmp + + + // Ensure string is null-terminated + target_dir[PATH_MAX - 1] = '\0'; + + // Check if directory exists + struct stat st; + if (stat(target_dir, &st) == -1) { + if (mkdir(target_dir, 0777) != 0) { + //perror("mkdir failed"); + return BI_EXECUTED; + } + } + + // Check permissions before `chdir()` + if (access(target_dir, R_OK | X_OK) != 0) { + perror("cd failed"); + return BI_EXECUTED; + } + + // Attempt to change directory + if (chdir(target_dir) != 0) { + perror("cd failed"); + } else { + char cwd[PATH_MAX]; + if (getcwd(cwd, sizeof(cwd)) != NULL) { + //printf("%s\n", cwd); // Print working directory only once + } else { + perror("getcwd failed"); + } + } + + return BI_EXECUTED; + } + + if (strcmp(cmd->argv[0], "rc") == 0) { + printf("%d\n", errno); + return BI_EXECUTED; + } + + return BI_NOT_BI; +} +*/ + + +int alloc_cmd_buff(cmd_buff_t *cmd_buff) { + cmd_buff->_cmd_buffer = (char *)malloc(SH_CMD_MAX); + if (!cmd_buff->_cmd_buffer) return ERR_MEMORY; + memset(cmd_buff, 0, sizeof(cmd_buff_t)); + return OK; +} + +int free_cmd_buff(cmd_buff_t *cmd_buff) { + if (cmd_buff->_cmd_buffer) { + free(cmd_buff->_cmd_buffer); + cmd_buff->_cmd_buffer = NULL; + } + return OK; +} + +int clear_cmd_buff(cmd_buff_t *cmd_buff) { + memset(cmd_buff, 0, sizeof(cmd_buff_t)); + return OK; +} + +int build_cmd_buff(char *cmd_line, cmd_buff_t *cmd_buff) { + cmd_buff->argc = 0; + char *ptr = cmd_line; + int in_quotes = 0; + char *token_start = NULL; + + while (*ptr) { + while (isspace(*ptr) && !in_quotes) ptr++; // Skip spaces outside quotes + + if (*ptr == '"') { // Detect start of quoted string + in_quotes = !in_quotes; + ptr++; + token_start = ptr; + } else { + token_start = ptr; + } + + while (*ptr && (in_quotes || !isspace(*ptr))) { // Capture argument + if (*ptr == '"') { + in_quotes = !in_quotes; + *ptr = '\0'; // Properly terminate quoted string + } + ptr++; + } + + if (*ptr) { + *ptr = '\0'; // Terminate non-quoted argument + ptr++; + } + + cmd_buff->argv[cmd_buff->argc++] = token_start; + //if (cmd_buff->argc >= CMD_ARGV_MAX - 1) break; + if (cmd_buff->argc > CMD_ARGV_MAX - 1) { + fprintf(stderr, "error: too many arguments\n"); + return ERR_CMD_ARGS_BAD; // Return error -4 + } + } + cmd_buff->argv[cmd_buff->argc] = NULL; + return OK; +} + + +// Used to split commands by pipeline +int build_cmd_list(char *cmd_line, command_list_t *clist) { + clist->num = 0; + char *saveptr; + char *token = strtok_r(cmd_line, "|", &saveptr); + + while (token != NULL) { + if (clist->num >= CMD_MAX) { + fprintf(stderr, CMD_ERR_PIPE_LIMIT, CMD_MAX); + return ERR_TOO_MANY_COMMANDS; + } + + // Trim leading/trailing spaces + while (*token == ' ') token++; + size_t len = strlen(token); + while (len > 0 && token[len-1] == ' ') token[--len] = '\0'; + + cmd_buff_t *cmd = &clist->commands[clist->num]; + if (alloc_cmd_buff(cmd) != OK) return ERR_MEMORY; + + int rc = build_cmd_buff(token, cmd); + if (rc != OK) { + free_cmd_buff(cmd); + return rc; + } + + clist->num++; + token = strtok_r(NULL, "|", &saveptr); + } + + if (clist->num == 0) { + printf(CMD_WARN_NO_CMD); + return WARN_NO_CMDS; + } + return OK; +} + +// Release command list +int free_cmd_list(command_list_t *clist) { + for (int i = 0; i < clist->num; i++) { + free_cmd_buff(&clist->commands[i]); + } + clist->num = 0; + return OK; +} + +