#define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #include <fcntl.h> #include <signal.h> #include <stdbool.h> #include "tokenize.h" #include "shell_helper.h" /* This is a shell helper class that contains the main functionality of * our shell and performs built-in commands and operations. */ /* Closes a file descriptor, and if there was an error, halts the program. */ int assert_close(int fd) { if(close(fd) == -1) { perror("Error closing file descriptor"); exit(1); } } /* Free all of the data within the command array. */ void free_data(char** cmd, int* cmdlen) { for (int i = 0; i < *cmdlen; i++) { free(cmd[i]); i++; } } /* Exits the program by printing message and frees data. */ void bye_bye(char** cmd, int* cmdlen) { printf("Bye bye.\n"); free_data(cmd, cmdlen); exit(0); } /* Traverses given input and then adds each in to the cmd vector for later execution. */ void store_cmds(char* in, char** cmd, int* cmdlen) { // remove new line character in[strcspn(in, "\n")] = 0; vect_t* temp = create_tokens(in); for (int i = 0; i < vect_size(temp); i++) { cmd[i] = vect_get_copy(temp, i); } cmd[vect_size(temp)] = NULL; *cmdlen = vect_size(temp); vect_delete(temp); } /* Sends invalid argument message for requested command. */ void invalid_args() { printf("Given command has an invalid number of arguments\n"); } /* Updates the previous command using current input. */ void change_prev_cmd(char* in, char* prev) { for (int i = 0; i < MAX_SIZE; i++) { prev[i] = in[i]; } } /* In a child process, attempt to execute the commands. */ void exec_chld(int current_cmd, int last_cmd, char*** cmd_sequence, char* file_to_read, char* file_to_write, int* parent_pipe_fd) { pid_t pid = fork(); if (pid == 0) { // If there are more commands in the pipe chain, do the recursive piping if (current_cmd > 0) { int pipe_fd[2]; pipe(pipe_fd); exec_chld(current_cmd - 1, last_cmd, cmd_sequence, file_to_read, file_to_write, pipe_fd); assert_close(STDIN_FILENO); dup2(pipe_fd[0], STDIN_FILENO); assert_close(pipe_fd[0]); assert_close(pipe_fd[1]); } // If this is a child, send its output to the pipe of the parent above if (parent_pipe_fd != NULL) { assert_close(STDOUT_FILENO); dup2(parent_pipe_fd[1], STDOUT_FILENO); assert_close(parent_pipe_fd[0]); assert_close(parent_pipe_fd[1]); } // If this is command1 in the pipe chain, it may be able to read from a file if (current_cmd == 0 && file_to_read != NULL) { int fd_in = open(file_to_read, O_RDONLY); assert_close(STDIN_FILENO); dup2(fd_in, STDIN_FILENO); assert_close(fd_in); } // If this is the last command in the pipe chain, it may be able to write to a file if (current_cmd == last_cmd && file_to_write != NULL) { int fd_out = open(file_to_write, O_WRONLY | O_CREAT | O_TRUNC, 0644); assert_close(STDOUT_FILENO); dup2(fd_out, STDOUT_FILENO); assert_close(fd_out); } // Execute this command if (execvp(cmd_sequence[current_cmd][0], cmd_sequence[current_cmd]) == -1) { printf("%s: command could not be executed properly\n", cmd_sequence[current_cmd][0]); exit(1); } } else { waitpid(pid, NULL, 0); } } /* Changes current working directory of the shell to the path specified. */ void change_directory(char** cmd) { char* new_dir = malloc(MAX_SIZE * sizeof(char)); new_dir = strncpy(new_dir, cmd[1], strlen(cmd[1]) + 1); // shift directories to d chdir(new_dir); free(new_dir); } /* Explains all the built-in commands available in our shell. */ void explain_cmds() { printf("\'%s\' : %s\n", "cd", "Changes the current working directory of the shell to the path specified as the argument"); printf("\'%s\' : %s\n", "source", "Executes a script, by taking filename as argument and then processing each line of the file as a command"); printf("\'%s\' : %s\n", "prev", "Prints the previous command line and executes it again"); printf("\'%s\' : %s\n", "help", "Explains all the built-in commands available in our shell"); } /* In the event of invalid piping or redirection, print an error message and free the memory. */ void invalid_piping_redirection(char*** cmd_sequence, int current_cmd) { printf("Given command has an invalid number of arguments\n"); for (int i = 0; i <= current_cmd; i++) { free(cmd_sequence[i]); } } /* Executes a "source" command (running a list of commands from a file). */ void execute_source_command(char** cmd) { // create holders char* n_cmd[MAX_SIZE]; int n_cmdlen; char* line = malloc(MAX_SIZE * sizeof(char)); char last_line[MAX_SIZE]; // file pointer FILE *fp; // open given file in read mode fp = fopen(cmd[1], "r"); // execute lines one by one size_t max_length = MAX_SIZE; while(getline(&line, &max_length, fp) != EOF) { store_cmds(line, n_cmd, &n_cmdlen); if (strcmp(n_cmd[0], "exit") == 0) { free_data(n_cmd, &n_cmdlen); break; } execute_command_helper(n_cmd, &n_cmdlen, line, last_line); free_data(n_cmd, &n_cmdlen); } fclose(fp); free(line); } /* Executes a system command (that is, all commands other than built-in commands). */ void execute_system_command(char** cmd, int num_args) { // setting up initial values for sequence of piping and redirection char** cmd_sequence[MAX_SIZE]; char* file_to_read = NULL; char* file_to_write = NULL; int current_cmd = 0; cmd_sequence[0] = malloc(MAX_SIZE * sizeof(char*)); int current_len = 0; // splitting cmd by "|" tokens into cmd_sequence, // and allowing ">" and "<" where they don't contradict "|" for (int i = 0; i < num_args; i++) { if (strcmp(cmd[i], "|") == 0) { if (current_len == 0) { // if you get 2 pipes in a row, or start with a pipe, error invalid_piping_redirection(cmd_sequence, current_cmd); return; } cmd_sequence[current_cmd][current_len] = NULL; current_cmd++; cmd_sequence[current_cmd] = malloc(MAX_SIZE * sizeof(char*)); current_len = 0; } else if (strcmp(cmd[i], ">") == 0) { // Error if there's a ">" that's not the second-to-last token in the command if (i + 2 != num_args) { invalid_piping_redirection(cmd_sequence, current_cmd); return; } file_to_write = cmd[i + 1]; i++; } else if (strcmp(cmd[i], "<") == 0) { // Error if there's a "<" that's not the second-to-last token in the first section of piping if (current_cmd != 0 || i + 2 > num_args || strcmp(cmd[i + 1], "|") == 0 || (i + 2 < num_args && strcmp(cmd[i + 2], "|") != 0)) { invalid_piping_redirection(cmd_sequence, current_cmd); return; } file_to_read = cmd[i + 1]; i++; } else { cmd_sequence[current_cmd][current_len] = cmd[i]; current_len++; } } if (current_len == 0) { // if you end with a pipe, error invalid_piping_redirection(cmd_sequence, current_cmd); return; } cmd_sequence[current_cmd][current_len] = NULL; exec_chld(current_cmd, current_cmd, cmd_sequence, file_to_read, file_to_write, NULL); for (int i = 0; i <= current_cmd; i++) { free(cmd_sequence[i]); } } /* Executes the given command and calls necessary helpers for further implementation. * Also returns a value representing whether the program should exit. */ int execute_command_helper(char** cmd, int* cmdlen, char* in, char* prev) { // keep track of number of arguments given int num_args = *cmdlen; // if there's no command, do nothing if (num_args == 0) { return 0; } // execute exit command else if (strcmp(cmd[0], "exit") == 0) { return 1; } // execute help command else if (strcmp(cmd[0], "help") == 0) { explain_cmds(); // make sure to update our previous command to be help change_prev_cmd(in, prev); } // execute previous command else if (strcmp(cmd[0], "prev") == 0) { if (num_args != 1) { invalid_args(); } else { // print previous command line printf("%s\n", prev); // now, execute it again char* p_cmd[MAX_SIZE]; int p_cmdlen; store_cmds(prev, p_cmd, &p_cmdlen); execute_command_helper(p_cmd, &p_cmdlen, prev, prev); // free data once done free_data(p_cmd, &p_cmdlen); // don't update our previous command to be prev, because we can't prev prev } } // if cd command to change directory else if (strcmp(cmd[0], "cd") == 0) { // expects either 1 or 2 args if (num_args > 2) { invalid_args(); } else if (num_args == 1) { chdir(".."); change_prev_cmd(in, prev); } else { change_directory(cmd); // make sure to update our previous command to be cd change_prev_cmd(in, prev); } } // execute commands from file else if (strcmp(cmd[0], "source") == 0) { // we want exactly 2 args here (source, filename) if (num_args != 2) { invalid_args(); } else { execute_source_command(cmd); } // make sure to update prev command to source change_prev_cmd(in, prev); } // execute a non-built-in command using the system path, with piping/redirection else { // piping/redirection do not need to support built-in commands, so we can implement them in here execute_system_command(cmd, num_args); // we update prev command normally with the assumption that we didn't use piping/redirection, // because that doesn't need to be supported change_prev_cmd(in, prev); } return 0; } /* Executes the given sequence of commands, by splitting the input on ";" tokens. * Also returns a value representing whether the program should exit. */ int execute_sequence(char** cmd, int* cmdlen, char* in, char* prev) { // if there's no command, do nothing if (*cmdlen == 0) { return 0; } // we don't need to create copies of in and prev, because sequencing doesn't need to support prev char** cmd_sequence[MAX_SIZE]; int cmdlen_sequence[MAX_SIZE]; // setting up initial values int current_cmd = 0; cmd_sequence[0] = malloc(MAX_SIZE * sizeof(char*)); int current_len = 0; cmdlen_sequence[0] = 0; // splitting cmd by ";" tokens into cmd_sequence for (int i = 0; i < *cmdlen; i++) { if (strcmp(cmd[i], ";") == 0) { cmd_sequence[current_cmd][current_len] = NULL; current_cmd++; cmd_sequence[current_cmd] = malloc(MAX_SIZE * sizeof(char*)); current_len = 0; cmdlen_sequence[current_cmd] = 0; } else { cmd_sequence[current_cmd][current_len] = cmd[i]; current_len++; cmdlen_sequence[current_cmd]++; } } // if the last token isn't a semicolon, you still need to end the command properly if (strcmp(cmd[(*cmdlen) - 1], ";") != 0) { cmd_sequence[current_cmd][current_len] = NULL; current_cmd++; cmd_sequence[current_cmd] = malloc(MAX_SIZE * sizeof(char*)); current_len = 0; cmdlen_sequence[current_cmd] = 0; } // executing each command separately for (int i = 0; i < current_cmd; i++) { // if a command is "exit", free all the remaining commands' memory and return 1 to exit if (execute_command_helper(cmd_sequence[i], cmdlen_sequence + i, in, prev) == 1) { while (i <= current_cmd) { free(cmd_sequence[i]); i++; } return 1; } free(cmd_sequence[i]); } free(cmd_sequence[current_cmd]); return 0; }