#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; } int run_command(char *command, int standard_in, int standard_out){ char *cmd = strip_whitespace(command); char *argv[MAX_ARGS]; /* 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); 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); // 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); execvp(cmd, 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[1024]; ssize_t readlen; while(1){ sprintf(prompt, "%s >>> ", getcwd(path_array, PATH_MAX)); write(1, prompt, strlen(prompt)); readlen = read(0, cmdbuffer, 1024); if(readlen == -1) continue; cmdbuffer[readlen] = 0; if(!readlen) break; /* 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]); 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); printf("Wait returned %d\n", wpid); } while (wpid == -1); children_running--; } } write(1, "\n", 1); return 0; }