#include #include #include #include #include #include #include #include #include #include #include // If we don't want to be restricted in this way, we could use an STL vector // Or we could figure out how many arguments we have, and allocate enough space // This approach will run faster, but it does have a static limit #define MAX_ARGS 16 #define MAX_PIPELEN 16 pid_t child_pids[MAX_PIPELEN]; int children_running = 0; char* strip_whitespace(char *start){ for(; isspace(*start); start++); char *actual_text_start = start; char inquotes = 0; for(; (inquotes || !isspace(*start)) && *start; start++) if(*start == '"') inquotes = !inquotes; *start = 0; return actual_text_start; } void make_argv(char **argv, int *p_argc, char *cmd){ *p_argc = 1; argv[0] = cmd; // is there an argument? char *maybe_argument; do{ maybe_argument = strip_whitespace(argv[*p_argc - 1] + strlen(argv[*p_argc - 1]) + 1); if(*maybe_argument){ argv[*p_argc] = maybe_argument; (*p_argc)++; } } while(*maybe_argument); argv[*p_argc] = 0; } void run_command(char *command, int standard_in, int standard_out){ int standard_error = 2; char *cmd = strip_whitespace(command); char *argv[MAX_ARGS]; char *new_argv[MAX_ARGS]; memset(new_argv, 0, MAX_ARGS * sizeof(char*)); /* Here's how argv has to be set up: argv[0] = command name argv[1] = first argument (if any) argv[2] = second argument (if any) argv[whatever is one past the last argument] = 0 */ int argc = 1; make_argv(argv, &argc, cmd); /* * echo hi > anewfile * argv = ["echo", "hi", ">", "anewfile"] */ for(int old_argv_idx = 0, new_argv_idx = 0; argv[old_argv_idx]; old_argv_idx++){ printf("argv[%d] = %s\n", old_argv_idx, argv[old_argv_idx]); char *filename = argv[old_argv_idx + 1]; if(strncmp(argv[old_argv_idx], "2>>", 3) == 0){ if(argv[old_argv_idx][3]) filename = &argv[old_argv_idx][3]; standard_error = open(filename, O_APPEND | O_CREAT | O_WRONLY, 0600); } else if(strncmp(argv[old_argv_idx], "2>", 2) == 0){ if(argv[old_argv_idx][2]) filename = &argv[old_argv_idx][2]; standard_error = open(filename, O_TRUNC | O_CREAT | O_WRONLY, 0600); } else if(strncmp(argv[old_argv_idx], "<", 1) == 0){ if(argv[old_argv_idx][1]) filename = &argv[old_argv_idx][1]; standard_in = open(filename, O_RDONLY); } else if(strncmp(argv[old_argv_idx], ">>", 2) == 0){ if(argv[old_argv_idx][2]) filename = &argv[old_argv_idx][2]; standard_out = open(filename, O_APPEND | O_CREAT | O_WRONLY, 0600); } else if(strncmp(argv[old_argv_idx], ">", 1) == 0){ if(argv[old_argv_idx][1]) filename = &argv[old_argv_idx][1]; standard_out = open(filename, O_TRUNC | O_CREAT | O_WRONLY, 0600); } else { new_argv[new_argv_idx++] = argv[old_argv_idx]; continue; // Don't clean up from a file redirect if we didn't do one! } // Cleanup from doing a file redirect if(filename == argv[old_argv_idx + 1]) old_argv_idx++; } pid_t pid = fork(); if(pid){ // We're the parent // really need to close here if(standard_in != 0) close(standard_in); if(standard_out != 1) close(standard_out); if(standard_error != 2) close(standard_error); // remember pid in case the user does ctrl+c child_pids[children_running] = pid; children_running++; } else { // We're the child // This is the place to do the redirect if(standard_in != 0) dup2(standard_in, 0); if(standard_out != 1) dup2(standard_out, 1); if(standard_error != 2) dup2(standard_error, 2); execvp(new_argv[0], new_argv); perror(cmd); exit(1); } } void handle_ctrl_c(int signal){ printf("ctrl+c pressed\n"); // stop the child process for(int i = 0; i < children_running; i++){ kill(child_pids[i], SIGINT); } } int main(){ struct sigaction sa; sa.sa_handler = handle_ctrl_c; sigaction(SIGINT, &sa, 0); char prompt[64]; char path_array[PATH_MAX]; char *cmdbuffer; while(1){ sprintf(prompt, "%s >>> ", getcwd(path_array, PATH_MAX)); cmdbuffer = readline(prompt); // I think there's something funny here if(!cmdbuffer) break; if(!strlen(cmdbuffer)) continue; printf("cmdbuffer = \"%s\"\n", cmdbuffer); add_history(cmdbuffer); /* for each command (separated by pipes) * run each command, having done input and output redirection */ int pipe_index = 0; char inquotes = 0; char *commands[MAX_PIPELEN]; int total_commands = 1; commands[0] = cmdbuffer; /* This loop looks for pipes */ for(int i = 0; cmdbuffer[i]; i++){ if(cmdbuffer[i] == '"' && cmdbuffer[i] != '\\') inquotes = !inquotes; if(cmdbuffer[i] == '|' && !inquotes){ commands[total_commands] = cmdbuffer + i + 1; total_commands++; cmdbuffer[i] = 0; i++; } } /* This part looks for shell builtins. The only one we have is cd */ if(!strncmp(cmdbuffer, "cd ", 3)){ char *argv[16]; int argc; char *cmd = strip_whitespace(cmdbuffer); make_argv(argv, &argc, cmd); if(chdir(argv[1])) printf("No such directory: %s\n", argv[1]); free(cmdbuffer); continue; } if(!strncmp(cmdbuffer, "exit\n", 5)) break; /* This next part executes programs */ int input = 0; for(int i = 0; i < total_commands; i++){ if(i + 1 == total_commands){ // if we're on the last one run_command(commands[i], input, 1); } else { // We're not on the last one int pipe_fds[2]; pipe(pipe_fds); // 0 is the read end, 1 is the write end run_command(commands[i], input, pipe_fds[1]); input = pipe_fds[0]; } } for(int i = 0; i < total_commands; i++){ pid_t wpid; do { wpid = wait(0); } while (wpid == -1); children_running--; } free(cmdbuffer); } write(1, "\n", 1); return 0; }