/* Remaining items before Project 2: * # Set home directory automatically so ~ works for everyone * # Switch the input routine to use GNU Readline * # Unify code paths in handle_pipes to provide a natural spot for redirection */ #include #include #include #include #include #include #include #include #include #include #include "string_helpers.h" #define PROMPT_LIMIT 512 void handle_pipes(char*, int); void run_with_redirects(char *linebuffer); char debug = 0; void handle_ctrlc(int signal){ write(1, "^C", 2); fsync(1); } struct variable{ char *name; char *value; }; int main(){ size_t max_variables = 128; size_t actual_variables = 0; struct variable *variables = malloc(max_variables * sizeof(struct variable)); size_t linebuffer_limit = 8192; char *linebuffer = malloc(linebuffer_limit); char working_directory[512]; getcwd(working_directory, 512); ssize_t readlen; struct sigaction ctrlc_action; char *user_home = getenv("HOME"); //"/home/seth/"; // TODO: Set this automatically char prompt[PROMPT_LIMIT]; ctrlc_action.sa_handler = handle_ctrlc; sigaction(SIGINT, &ctrlc_action, 0); while(1){ snprintf(prompt, PROMPT_LIMIT, "%s: ", working_directory); char *cmdbuffer = readline(prompt); if(!cmdbuffer) break; if(!*cmdbuffer) continue; add_history(cmdbuffer); strncpy(linebuffer, cmdbuffer, linebuffer_limit); free(cmdbuffer); if(debug) printf("You entered: \"%s\" (%ld bytes)\n", linebuffer, readlen); /* Shell expansions */ for(size_t i = 0; linebuffer[i]; i++){ if(linebuffer[i] == '~'){ int length = strlen(user_home); move_right(linebuffer + i, length - 1); strncpy(linebuffer + i, user_home, length); } } for(size_t i = 0; linebuffer[i]; i++){ if(linebuffer[i] == '$'){ char *varname = linebuffer + i + 1; char *varname_end = varname; while(*varname_end != ' ' && *varname_end != '\n' && *varname_end) varname_end++; char old_varname_end = *varname_end; *varname_end = 0; for(int j = 0; j < actual_variables; j++) if(!strcmp(variables[j].name, varname)){ int shift = 1 + strlen(varname) - strlen(variables[j].value); // 1 for the $ if(shift > 0){ *varname_end = old_varname_end; move_left(varname_end, shift); } else if(shift < 0) { *varname_end = old_varname_end; move_right(varname_end - 1, -shift); } memcpy(linebuffer + i, variables[j].value, strlen(variables[j].value)); goto found_it; } // if we never found it *varname_end = old_varname_end; found_it:; } } /* Shell Builtins */ if(!strncmp(linebuffer, "export ", 7)){ char *varname = 7 + linebuffer; char *equals = varname; while(*equals != '=') ++equals; *equals = 0; char* value = equals + 1; struct variable new_var; new_var.name = malloc(strlen(varname)); strcpy(new_var.name, varname); new_var.value = malloc(strlen(value)); strcpy(new_var.value, value); variables[actual_variables] = new_var; actual_variables++; continue; } if(!strncmp(linebuffer, "cd ", 3)){ chdir(linebuffer + 3); getcwd(working_directory, 512); continue; } if(!strncmp(linebuffer, "debug", 5)){ debug = !debug; continue; } if(!strncmp(linebuffer, "exit", 4)){ break; } /* Most shells have a few more builtins than that */ /* Maybe the user specified a program */ handle_pipes(linebuffer, 0); } write(1, "\n", 1); free(linebuffer); return 0; } void handle_pipes(char *linebuffer, int input){ char *secondcmd; if(secondcmd = strstr(linebuffer, "|")){ *secondcmd = 0; // This'll overwrite the | with a terminator char *end_of_first = secondcmd - 1; while(*end_of_first == ' '){ *end_of_first = 0; --end_of_first; } while(*++secondcmd == ' ') *secondcmd = ' '; if(debug) { printf("First command: \"%s\"\n", linebuffer); printf("Second command: \"%s\"\n", secondcmd); } int pipefd[2]; pipe(pipefd); // Run first command pid_t pid = fork(); if(!pid) { // Set up and exec the first command, the one in linebuffer dup2(pipefd[1], 1); dup2(input, 0); close(pipefd[0]); // Pipes are done, redirects are next run_with_redirects(linebuffer); } // Run second command close(pipefd[1]); handle_pipes(secondcmd, pipefd[0]); wait(0); return; } pid_t pid = fork(); if(!pid) { // Set up and exec the first command, the one in linebuffer dup2(input, 0); // Pipes are done, redirects are next run_with_redirects(linebuffer); } wait(0); } /* echo hi >afile * ["echo", "hi", ">afile"] then filename = argv[i]+1 * echo hi > afile * ["echo", "hi", ">", "afile] then filename = argv[i+1] * > afile echo goose * [">", "afile", "echo", "goose"] * ["echo", "goose"] */ void open_and_redirect(int flags, char ** argv, int i, int over_fd, int oplen){ char *filename = argv[i+1]; if(argv[i][oplen]) filename = argv[i] + oplen; int fd = open(filename, flags, 0644); if(fd == -1) { perror("open"); exit(1); } int j, offset; if(argv[i+1] == filename) offset = 2; else offset = 1; for(j = i + offset; argv[j]; j++) argv[j-offset] = argv[j]; argv[j - offset] = 0; dup2(fd, over_fd); // here } void run_with_redirects(char *linebuffer){ size_t length; char **argv = split(linebuffer, &length); for(int i = 0; argv[i]; i++){ if(!strncmp("<", argv[i], 2)){ open_and_redirect(O_RDONLY, argv, i, 0, 1); } if(argv[i] && !strncmp("2>", argv[i], 2)){ int oplen = 2, append_create = O_TRUNC; // here if(!strncmp("2>>", argv[i], 3)) { // here oplen = 3; // here append_create = O_APPEND; } open_and_redirect(O_WRONLY | O_CREAT | append_create, argv, i, 2, oplen); } if(argv[i] && '>' == argv[i][0]){ // segfault prevention: If argv[i] == 0, then we don't try to dereference int oplen = 1, append_create = O_TRUNC; if(!strncmp(">>", argv[i], 2)) { oplen = 2; append_create = O_APPEND; } open_and_redirect(O_WRONLY | O_CREAT | append_create, argv, i, 1, oplen); } } if(debug){ printf("["); for(int i = 0; argv[i]; i++) printf("%s, ", argv[i]); printf("\b\b]\n"); } execvp(argv[0], argv); free(argv); perror("exec"); exit(1); }