#include "queue.h" #include "dynamic_array.h" #include #include #include #include #include enum modes { word_separation, whole_word }; enum { new_line = 10, whitespace = ' ', tab = 9, backslash = '\\', double_quotes = '"' }; /* two-letter tokens */ enum { append = '>' + 1, and = '&' + 1, or = '|' + 1 }; struct param_type { int is_word; int escape_sequences; unsigned int double_quotes_counter; char stored_symbol; int empty_word_flag; int tokens; int wrong_command; }; static void show_invitation() { printf("> "); } static void print_error() { fprintf(stderr, "Error: unmatched quotes\n"); } static void init_params(struct param_type *params, enum modes *current_mode) { params->is_word = 0; params->escape_sequences = 0; params->double_quotes_counter = 0; params->stored_symbol = ' '; params->empty_word_flag = 0; params->tokens = 0; params->wrong_command = 0; *current_mode = word_separation; } static void reset_params(struct param_type *params, enum modes *current_mode, struct queue *word_chain, struct dynamic_array *tmp_word) { init_params(params, current_mode); queue_clear(word_chain); dynarr_drop_word(tmp_word); } static int is_double_quotes_pair(struct param_type params) { return !(params.double_quotes_counter % 2); } static int check_separation(int ch, struct param_type params) { return (ch == whitespace || ch == tab) && params.is_word && !params.escape_sequences; } static int single_tokens(int ch) { return ch == '<' || ch == ';' || ch == '(' || ch == ')'; } static void clean_input_buffer() { int ch; while((ch = getchar() != new_line)) {} } static void double_tokens_and_fg(int ch, struct param_type *params) { int next_ch; next_ch = getchar(); ungetc(next_ch, stdin); if(ch == '>' && next_ch == '>') params->tokens = append; else if(ch == '|' && next_ch == '|') params->tokens = or; else if(ch == '&' && next_ch == '&') params->tokens = and; else if(ch == '&') { while((ch = getchar()) != new_line) { if(ch != whitespace && ch != tab) { fprintf(stderr, "incorrect command\n"); params->wrong_command = 1; return; } } params->tokens = '&'; } } static int check_tokens(int ch, struct param_type *params) { if(single_tokens(ch)) params->tokens = ch; else double_tokens_and_fg(ch, params); return params->tokens; } static int ignore_spaces(int ch, struct param_type params) { return (ch == whitespace || ch == tab) && !params.escape_sequences; } static int change_mode(int ch, struct param_type params) { return ch == '"' && !params.escape_sequences; } static int start_escape_sequence(int ch, struct param_type params) { return ch == backslash && !params.escape_sequences; } static void add_word(struct queue *word_chain, struct dynamic_array *tmp_word, struct param_type *params) { dynarr_push_back(tmp_word, 0); char *word = malloc(tmp_word->last_element_index+1); dynarr_copy_array(tmp_word, word); queue_push(word_chain, word); dynarr_drop_word(tmp_word); params->is_word = 0; } #if 0 static void print_word(char *word) { putchar('['); while(*word) { putchar(*word); ++word; } printf("]\n"); } static void print_line(const struct queue *word_chain) { queue_processing(word_chain, print_word); } #endif static char** create_cmdline(const struct queue *word_chain, int word_counter) { char **cmdline = malloc((word_counter + 1) * sizeof(char*)); queue_copy_words_to_args(word_chain, cmdline); return cmdline; } static int check_cd(const char *arg) { return !strcmp(arg, "cd"); } static void change_directory(char **cmdline) { int result; char *first_arg = cmdline[1]; char *path = NULL; /* change to user home directory */ if(first_arg == NULL) { path = getenv("HOME"); if(!path) { perror("I don't know where's your home..."); return; } } else { if(cmdline[2]) { perror("cd: too many arguments"); return; } path = first_arg; } result = chdir(path); if(result == -1) perror(path); } static void clean_up_memory(struct queue *word_chain, char **cmdline) { queue_clear(word_chain); free(cmdline); } static void clean_up_zombie_process() { int pid; do { pid = wait4(-1, NULL, WNOHANG, NULL); } while(pid > 0); } static void run_external_program(struct queue *word_chain, struct param_type *params) { int pid, wait_pid, result; if(word_chain->first == NULL) { fprintf(stderr, "empty command\n"); return; } char **cmdline = create_cmdline(word_chain, queue_get_word_count(word_chain)); if(check_cd(cmdline[0])) { change_directory(cmdline); clean_up_memory(word_chain, cmdline); } else { pid = fork(); if(pid == -1) { perror("fork error"); exit(1); } /* child process */ if(pid == 0) { execvp(cmdline[0], cmdline); perror(cmdline[0]); exit(1); } /* parent process */ clean_up_memory(word_chain, cmdline); /* waiting for forground process */ if(params->tokens != '&') { do { wait_pid = wait(&result); } while(wait_pid != pid); } else clean_up_zombie_process(); } } static void command_processing(struct param_type *params, enum modes *current_mode, struct queue *word_chain, struct dynamic_array *tmp_word) { /* and odd number of double quotes */ if(!is_double_quotes_pair(*params)) { print_error(); reset_params(params, current_mode, word_chain, tmp_word); show_invitation(); return; } if(params->empty_word_flag || params->is_word) { if(params->empty_word_flag) dynarr_push_back(tmp_word, '\0'); add_word(word_chain, tmp_word, params); } run_external_program(word_chain, params); if(params->tokens == '&' || params->tokens == 0) show_invitation(); reset_params(params, current_mode, word_chain, tmp_word); #if 0 print_line(word_chain); #endif } static int is_empty_word(int ch, struct param_type params) { return (ch == whitespace || ch == tab) && !params.is_word && params.empty_word_flag; } static void word_separation_processing(int ch, struct param_type *params, enum modes *current_mode, struct queue *word_chain, struct dynamic_array *tmp_word) { /* could be a marker for the beginning of a blank word */ params->stored_symbol = ch; if(is_empty_word(ch, *params)) { dynarr_push_back(tmp_word, '\0'); add_word(word_chain, tmp_word, params); params->empty_word_flag = 0; return; } params->empty_word_flag = 0; if(change_mode(ch, *params)) { ++params->double_quotes_counter; *current_mode = whole_word; return; } if(start_escape_sequence(ch, *params)) { params->escape_sequences = 1; return; } if(check_tokens(ch, params)) { command_processing(params, current_mode, word_chain, tmp_word); return; } if(params->wrong_command) { clean_input_buffer(); reset_params(params, current_mode, word_chain, tmp_word); show_invitation(); return; } if(check_separation(ch, *params)) { add_word(word_chain, tmp_word, params); params->is_word = 0; return; } if(ignore_spaces(ch, *params)) return; dynarr_push_back(tmp_word, ch); params->is_word = 1; params->escape_sequences = 0; } static int escape_double_quotes_or_backslash(int ch, struct param_type params) { return params.escape_sequences && (ch == double_quotes || ch == backslash); } static int double_quotes_again(int ch, struct param_type params) { return ch == double_quotes && !params.is_word && params.stored_symbol == '"'; } static void whole_word_processing(int ch, struct param_type *params, enum modes *current_mode, struct queue *word_chain, struct dynamic_array *tmp_word) { if(double_quotes_again(ch, *params)) { params->stored_symbol = '\0'; params->empty_word_flag = 1; ++params->double_quotes_counter; *current_mode = word_separation; return; } params->stored_symbol = '\0'; if(change_mode(ch, *params)) { ++params->double_quotes_counter; *current_mode = word_separation; return; } if(escape_double_quotes_or_backslash(ch, *params)) { dynarr_push_back(tmp_word, ch); params->escape_sequences = 0; params->is_word = 1; return; } if(start_escape_sequence(ch, *params)) { params->escape_sequences = 1; return; } /* backslash recovery */ if(params->escape_sequences) dynarr_push_back(tmp_word, backslash); dynarr_push_back(tmp_word, ch); params->is_word = 1; params->escape_sequences = 0; } int main() { int ch; struct param_type params; struct queue word_chain; struct dynamic_array tmp_word; enum modes current_mode = word_separation; queue_init(&word_chain); dynarr_create_array(&tmp_word); init_params(¶ms, ¤t_mode); show_invitation(); while((ch = getchar()) != EOF) { clean_up_zombie_process(); if(ch == new_line) command_processing(¶ms, ¤t_mode, &word_chain, &tmp_word); else if(current_mode == word_separation) word_separation_processing(ch, ¶ms, ¤t_mode, &word_chain, &tmp_word); else if(current_mode == whole_word) whole_word_processing(ch, ¶ms, ¤t_mode, &word_chain, &tmp_word); } putchar(new_line); dynarr_clear(&tmp_word); return 0; }