diff --git a/shell_syscalls_practice.c b/shell_syscalls_practice.c new file mode 100644 index 0000000..7e29204 --- /dev/null +++ b/shell_syscalls_practice.c @@ -0,0 +1,438 @@ +/***************************************************************************//** + + @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; +}