/***************************************************************************//** @file main.c @author Stephen Brennan; Revised by Evan Niederwerfer @date 03/09/25 @brief Custom bash shell written in C *******************************************************************************/ #include #include #include #include #include #include int size = 64; /* Function Declarations for builtin shell commands: */ int lsh_cd(char **args); int lsh_help(char **args); int lsh_exit(char **args); int lsh_about(char **args); char *builtin_str[] = { "cd", "help", "exit", "about" }; int (*builtin_func[]) (char **) = { &lsh_cd, &lsh_help, &lsh_exit, &lsh_about }; int lsh_num_builtins() { return sizeof(builtin_str) / sizeof(char *); } /* Builtin function implementations. */ /** @brief Bultin command: change directory. @param args List of args. args[0] is "cd". args[1] is the directory. @return Always returns 1, to continue executing. */ int lsh_cd(char **args) { if (args[1] == NULL) { fprintf(stderr, "lsh: expected argument to \"cd\"\n"); } else { if (chdir(args[1]) != 0) { perror("lsh"); } } return 1; } /** @brief Builtin command: print help. @param args List of args. Not examined. @return Always returns 1, to continue executing. */ int lsh_help(char **args) { int i; printf("Stephen Brennan's (revised by Evan Niederwerfer) LSH\n"); printf("Type program names and arguments, and hit enter.\n"); printf("The following are built in:\n"); for (i = 0; i < lsh_num_builtins(); i++) { printf(" %s\n", builtin_str[i]); } printf("Use the man command for information on other programs.\n"); return 1; } /** @brief Builtin command: exit. @param args List of args. Not examined. @return Always returns 0, to terminate execution. */ int lsh_exit(char **args) { return 0; } int lsh_about(char **args) { printf("Bash shell written in C - Stephen Brennan's shell revised by Evan Niederwerfer \n\n"); FILE *cmd = popen("bash --version", "r"); if (cmd == NULL) { perror("popen"); return 1; } char buffer[128]; while (fgets(buffer, sizeof(buffer), cmd) != NULL) { printf("%s", buffer); } fclose(cmd); return 1; } /** @brief Launch a program and wait for it to terminate. @param args Null terminated list of arguments (including program). @return Always returns 1, to continue execution. */ int lsh_launch(char **args) { pid_t pid; int status; pid = fork(); if (pid == 0) { // Child process if (execvp(args[0], args) == -1) { perror("lsh"); } exit(EXIT_FAILURE); } else if (pid < 0) { // Error forking perror("lsh"); } else { // Parent process do { waitpid(pid, &status, WUNTRACED); } while (!WIFEXITED(status) && !WIFSIGNALED(status)); } return 1; } int myPipe(char **args, int j) { pid_t pid, pid2; int pipefd[2]; if (pipe(pipefd) == -1) { perror("pipe"); return -1; } pid = fork(); if (pid == 0) { // Child close(pipefd[0]); dup2(pipefd[1], STDOUT_FILENO); // redirect stdout to the grandchild close(pipefd[1]); args[j] = NULL; execvp(args[0], args); } pid2 = fork(); if (pid2 == 0) { // Grandchild close(pipefd[1]); dup2(pipefd[0], STDIN_FILENO); // take in input from the child pipe close(pipefd[0]); execvp(args[j+1], &args[j+1]); } //parent close(pipefd[0]); close(pipefd[1]); waitpid(pid, NULL, 0); waitpid(pid2, NULL, 0); return 1; } int myAmphersand(char **args, int j) { pid_t pid; pid = fork(); if (pid == -1) { perror("Fork Failed"); return(-1); } if (pid == 0) { args[j] = NULL; lsh_launch(args); } else { args = &args[j+1]; lsh_launch(args); } waitpid(pid, NULL, 0); return 1; } int mySemicolon(char **args, int j) { args[j] = NULL; lsh_launch(args); args = &args[j+1]; lsh_launch(args); return 1; } /** @brief Execute shell built-in or launch program. @param args Null terminated list of arguments. @return 1 if the shell should continue running, 0 if it should terminate */ int lsh_execute(char **args) { int i; if (args[0] == NULL) { // An empty command was entered. return 1; } for(int j = 0; args[j] != NULL; j++) { // compare "|", "&", ";" with args[j] // and go to coorresponding functions. for (int j = 0; args[j] != NULL; j++) { if (strcmp(args[j], "|") == 0) { return myPipe(args, j); // j is the index where the pipe symbol is found } } for (int j = 0; args[j] != NULL; j++) { if (strcmp(args[j], ";") == 0) { return mySemicolon(args, j); } } for (int j = 0; args[j] != NULL; j++) { if (strcmp(args[j], "&") == 0) { return myAmphersand(args, j); } } } for (i = 0; i < lsh_num_builtins(); i++) { if (strcmp(args[0], builtin_str[i]) == 0) { return (*builtin_func[i])(args); } } return lsh_launch(args); } #define LSH_RL_BUFSIZE 1024 /** @brief Read a line of input from stdin. @return The line from stdin. */ char *lsh_read_line(void) { int bufsize = LSH_RL_BUFSIZE; int position = 0; char *buffer = malloc(sizeof(char) * bufsize); int c; if (!buffer) { fprintf(stderr, "lsh: allocation error\n"); exit(EXIT_FAILURE); } while (1) { // Read a character c = getchar(); if (c == EOF) { exit(EXIT_SUCCESS); } else if (c == '\n') { buffer[position] = '\0'; return buffer; } else { buffer[position] = c; } position++; // If we have exceeded the buffer, reallocate. if (position >= bufsize) { bufsize += LSH_RL_BUFSIZE; buffer = realloc(buffer, bufsize); if (!buffer) { fprintf(stderr, "lsh: allocation error\n"); exit(EXIT_FAILURE); } } } } char* process_line(char *oline) { int length = strlen(oline); if (length > 100) { oline[100] = '\0'; } for (int i = 0; oline[i] != '\0'; i++) { if (oline[i] < 0 || oline[i] > 127) { // ASCII Range printf("Error: Invalid character detected. Only ASCII characters (A–Z, a–z, 0–9, dash, dot, forward slash) are allowed.\n"); exit(EXIT_FAILURE); } } return oline; } #define LSH_TOK_BUFSIZE 64 #define LSH_TOK_DELIM " \t\r\n\a" /** @brief Split a line into tokens (very naively). @param line The line. @return Null-terminated array of tokens. */ char **lsh_split_line(char *line) { int bufsize = LSH_TOK_BUFSIZE, position = 0; char **tokens = malloc(bufsize * sizeof(char*)); char *token, **tokens_backup; if (!tokens) { fprintf(stderr, "lsh: allocation error\n"); exit(EXIT_FAILURE); } token = strtok(line, LSH_TOK_DELIM); while (token != NULL) { tokens[position] = token; position++; if (position >= bufsize) { bufsize += LSH_TOK_BUFSIZE; tokens_backup = tokens; tokens = realloc(tokens, bufsize * sizeof(char*)); if (!tokens) { free(tokens_backup); fprintf(stderr, "lsh: allocation error\n"); exit(EXIT_FAILURE); } } token = strtok(NULL, LSH_TOK_DELIM); } tokens[position] = NULL; return tokens; } /** @brief Loop getting input and executing it. */ void lsh_loop(void) { char *line, *oline; char **args; int status; do { printf("CS3230-Evan>> "); oline = lsh_read_line(); line = process_line(oline); // process the input args = lsh_split_line(line); status = lsh_execute(args); free(line); free(args); } while (status); } /** @brief Main entry point. @param argc Argument count. @param argv Argument vector. @return status code */ int main(int argc, char **argv) { // Load config files, if any. // Run command loop. lsh_loop(); // Perform any shutdown/cleanup. return EXIT_SUCCESS; }