#include "queue.h" #include "dynamic_array.h" #include "lexical_analysis.h" #include #include #include #include #include #include #include #include MAKE_QUEUE_INIT(w_queue) MAKE_QUEUE_INIT(p_queue) MAKE_QUEUE_PUSH(w_queue, word, char*) MAKE_QUEUE_PUSH(p_queue, pid, int) enum modes { word_separation, whole_word }; #if 0 enum { new_line = 10, whitespace = ' ', tab = 9, backslash = '\\', double_quotes = '"' }; /* two-letter tokens */ enum { append = '>' + 1, and = '&' + 1, or = '|' + 1 }; /* storing file names to redirect standard input/output streams */ struct io_type { char *input_stream; char *output_stream; char *output_stream_to_append; }; 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; struct io_type streams; int last_execution_status; int pipeline; }; #endif static void show_invitation() { printf("> "); } static void print_error() { fprintf(stderr, "Error: unmatched quotes\n"); } /* remove background zombie processes */ static void handler(int signal) { int save_errno = errno; int pid; if(signal == SIGCHLD) { do { pid = wait4(-1, NULL, WNOHANG, NULL); } while(pid > 0); } errno = save_errno; } 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; params->streams.input_stream = NULL; params->streams.output_stream = NULL; params->streams.output_stream_to_append = NULL; /* 0 - success, 1 - error */ params->last_execution_status = 0; params->pipeline = 0; *current_mode = word_separation; } static void clear_filename(struct param_type *params) { if(params->streams.input_stream) free(params->streams.input_stream); if(params->streams.output_stream) free(params->streams.output_stream); if(params->streams.output_stream_to_append) free(params->streams.output_stream_to_append); params->streams.input_stream = NULL; params->streams.output_stream = NULL; params->streams.output_stream_to_append = NULL; } static void reset_params(struct param_type *params, enum modes *current_mode, struct w_queue *word_chain, struct c_queue *cmdlines, struct dynamic_array *tmp_word) { w_queue_clear(word_chain); c_queue_clear(cmdlines); dynarr_drop_word(tmp_word); clear_filename(params); init_params(params, current_mode); } #if 0 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; } #endif int filename_waiting(struct param_type *params) { if(params->tokens == '<' || params->tokens == '>' || params->tokens == append) { if(!params->is_word) params->wrong_command = 1; return 1; } else return 0; } void add_filename(struct dynamic_array *tmp_word, struct param_type *params) { dynarr_push_back(tmp_word, '\0'); switch(params->tokens) { case '<': params->streams.input_stream = malloc(tmp_word->last_element_index+1); dynarr_copy_array(tmp_word, params->streams.input_stream); break; case '>': params->streams.output_stream = malloc(tmp_word->last_element_index+1); dynarr_copy_array(tmp_word, params->streams.output_stream); break; case append: params->streams.output_stream_to_append = malloc(tmp_word->last_element_index+1); dynarr_copy_array(tmp_word, params->streams.output_stream_to_append); } dynarr_drop_word(tmp_word); params->is_word = 0; } void add_word(struct w_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); w_queue_push(word_chain, word); dynarr_drop_word(tmp_word); params->is_word = 0; } static void add_letter(int ch, struct dynamic_array *tmp_word, struct param_type *params) { dynarr_push_back(tmp_word, ch); params->is_word = 1; params->escape_sequences = 0; } #if 0 static int is_double_token(struct param_type *params) { return params->tokens == and || params->tokens == or || params->tokens == append; } static int is_redirect_token(int ch, int next_ch) { return ch == '<' || ch == '>' || (ch == '>' && next_ch == '>'); } static int validate_redirections(int ch, int next_ch, struct param_type *params) { return (ch == '<' && params->streams.input_stream == NULL) || ((ch == '>' || (ch == '>' && next_ch == '>')) && params->streams.output_stream == NULL && params->streams.output_stream_to_append == NULL); } static void add_word_or_filename(struct w_queue *word_chain, struct dynamic_array *tmp_word, struct param_type *params) { /* filenames */ if(filename_waiting(params) && !params->wrong_command) add_filename(tmp_word, params); /* execute command */ else if(params->is_word) add_word(word_chain, tmp_word, params); } static int stream_redirect_tokens(struct w_queue *word_chain, struct dynamic_array *tmp_word, int ch, struct param_type *params) { int next_ch; next_ch = getchar(); ungetc(next_ch, stdin); if(is_redirect_token(ch, next_ch)) { add_word_or_filename(word_chain, tmp_word, params); if(params->wrong_command) return 0; if(validate_redirections(ch, next_ch, params)) { params->tokens = (ch == '>' && next_ch == '>') ? append : ch; if(is_double_token(params)) getchar(); return 1; } else { fprintf(stderr, "syntax error\n"); params->wrong_command = 1; } } return 0; } #endif static void clean_input_buffer() { int ch; while((ch = getchar()) != new_line) {} } #if 0 static int is_special_token(int ch) { return ch == and || ch == or || ch == '&' || ch == ';' || ch == '|'; } static int wrong_streams_redirection(struct param_type *params) { return (!params->pipeline && (params->tokens == '>' || params->tokens == append)) || (params->pipeline && (params->tokens == '>' || params->tokens == append || params->tokens == '<')); } #endif char** create_cmdline(const struct w_queue *word_chain, int word_counter) { char **cmdline = malloc((word_counter + 1) * sizeof(char*)); w_queue_copy_words_to_args(word_chain, cmdline); return cmdline; } int is_stream_redirection_set(const struct param_type *params) { return params->streams.input_stream || params->streams.output_stream || params->streams.output_stream_to_append; } #if 0 static int pipeline_token_processing(struct w_queue *word_chain, struct c_queue *cmdlines, struct dynamic_array *tmp_word, struct param_type *params) { char **cmdline = NULL; if(is_stream_redirection_set(params) && wrong_streams_redirection(params)) { /* TODO: add error codes */ params->wrong_command = 1; return 0; } params->tokens = '|'; params->pipeline = 1; cmdline = create_cmdline(word_chain, w_queue_get_word_count(word_chain)); c_queue_push(cmdlines, cmdline); w_queue_clear(word_chain); dynarr_drop_word(tmp_word); return 1; } static int special_tokens(struct w_queue *word_chain, struct c_queue *cmdlines, struct dynamic_array *tmp_word, int ch, struct param_type *params) { int next_ch; next_ch = getchar(); ungetc(next_ch, stdin); if(is_special_token(ch)) { add_word_or_filename(word_chain, tmp_word, params); if(params->wrong_command) return 0; if(ch == '|' && next_ch == '|') params->tokens = or; else if(ch == '|') { if(!pipeline_token_processing(word_chain, cmdlines, tmp_word, params)) return 0; } 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 0; } } params->tokens = '&'; } if(is_double_token(params)) getchar(); return 1; } return 0; } #endif static int special_token_handling(struct w_queue *word_chain, struct c_queue *cmdlines, struct dynamic_array *tmp_word, int ch, struct param_type *params) { return stream_redirect_tokens(word_chain, tmp_word, ch, params) ? 1 : special_tokens(word_chain, cmdlines, tmp_word, ch, params); } #if 0 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; } /* name of file for opening stream already exists */ static int excessive_words(int ch, const struct param_type *params) { int next_ch; if(filename_waiting(params)) { if(ch == new_line) return 0; while((next_ch = getchar()) != new_line) { if(next_ch == ' ') continue; if(!is_special_token(next_ch) && next_ch != '<' && next_ch != '>') return 1; else break; } ungetc(next_ch, stdin); } return 0; } #endif static int is_cd_command(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 open_files(const struct param_type *params, int *input_fd, int *output_fd) { if(params->streams.input_stream) *input_fd = open(params->streams.input_stream, O_RDONLY); if(params->streams.output_stream) *output_fd = open(params->streams.output_stream, O_WRONLY | O_CREAT | O_TRUNC, 0666); if(params->streams.output_stream_to_append) *output_fd = open(params->streams.output_stream_to_append, O_WRONLY | O_APPEND, 0666); } static void change_streams(int input_fd, int output_fd, int is_pipeline, int is_begin_pipeline) { if((!is_pipeline && input_fd) || (is_pipeline && is_begin_pipeline && input_fd)) { dup2(input_fd, 0); close(input_fd); } if((!is_pipeline && output_fd) || (is_pipeline && !is_begin_pipeline && output_fd)) { dup2(output_fd, 1); close(output_fd); } } static void set_signal_disposition(struct param_type *params) { if(params->tokens == '&') /* zombie process termination on signal */ signal(SIGCHLD, handler); else /* the parent process will wait for the process to complete; default signal disposition */ signal(SIGCHLD, SIG_DFL); } static void wait_for_process_to_complete(struct p_queue *pid_store) { int total_process_counter, current_process_counter, wait_pid; total_process_counter = p_queue_get_process_quantity(pid_store); current_process_counter = 0; do { wait_pid = wait(NULL); if(p_queue_find_pid(pid_store, wait_pid)) ++current_process_counter; } while(total_process_counter != current_process_counter); /* return of background process zombie cleanup */ signal(SIGCHLD, handler); } static void clean_up_memory(struct w_queue *word_chain, struct c_queue *cmdlines, struct param_type *params) { w_queue_clear(word_chain); c_queue_clear(cmdlines); clear_filename(params); } static void close_files(int input_fd, int output_fd) { if(input_fd) close(input_fd); if(output_fd) close(output_fd); } static void postprocessing(struct w_queue *word_chain, struct c_queue *cmdlines, struct p_queue *pid_store, struct param_type *params, int input_fd, int output_fd) { clean_up_memory(word_chain, cmdlines, params); close_files(input_fd, output_fd); if(params->tokens != '&') wait_for_process_to_complete(pid_store); p_queue_clear(pid_store); } static void make_pipeline(struct w_queue *word_chain, struct c_queue *cmdlines, struct param_type *params, int input_fd, int output_fd) { struct p_queue pid_store; p_queue_init(&pid_store); char **cmdline = NULL; int fd[2]; int save_read_fd, pid; pipe(fd); cmdline = c_queue_pop(cmdlines); set_signal_disposition(params); pid = fork(); if(pid == 0) { close(fd[0]); dup2(fd[1], 1); close(fd[1]); if(is_stream_redirection_set(params)) change_streams(input_fd, output_fd, 1, 1); execvp(cmdline[0], cmdline); perror(cmdline[0]); exit(1); } close(fd[1]); p_queue_push(&pid_store, pid); while(!c_queue_is_empty(cmdlines)) { cmdline = c_queue_pop(cmdlines); /* if not last process in pipeline*/ if(!c_queue_is_empty(cmdlines)) { save_read_fd = fd[0]; pipe(fd); } pid = fork(); if(pid == 0) { if(!c_queue_is_empty(cmdlines)) { dup2(save_read_fd, 0); close(save_read_fd); dup2(fd[1], 1); close(fd[1]); } else { dup2(fd[0], 0); close(fd[0]); } /* for last process in pipeline */ if(c_queue_is_empty(cmdlines)) if(is_stream_redirection_set(params)) change_streams(input_fd, output_fd, 1, 0); execvp(cmdline[0], cmdline); perror(cmdline[0]); exit(1); } if(!c_queue_is_empty(cmdlines)) { close(save_read_fd); close(fd[1]); } else close(fd[0]); p_queue_push(&pid_store, pid); } postprocessing(word_chain, cmdlines, &pid_store, params, input_fd, output_fd); } static void run_external_program(struct w_queue *word_chain, struct c_queue *cmdlines, struct param_type *params) { int pid, input_fd, output_fd; struct p_queue pid_store; p_queue_init(&pid_store); char **cmdline = NULL; input_fd = 0; output_fd = 0; if(is_stream_redirection_set(params)) open_files(params, &input_fd, &output_fd); if(input_fd == -1 || output_fd == -1) { perror("can't open file"); return; } if(params->pipeline) { make_pipeline(word_chain, cmdlines, params, input_fd, output_fd); return; } cmdline = c_queue_pop(cmdlines); if(is_cd_command(cmdline[0])) change_directory(cmdline); else { set_signal_disposition(params); pid = fork(); if(pid == -1) { perror("fork error"); exit(1); } /* child process */ if(pid == 0) { if(is_stream_redirection_set(params)) change_streams(input_fd, output_fd, 0, 0); execvp(cmdline[0], cmdline); params->last_execution_status = 1; perror(cmdline[0]); exit(1); } /* parent process */ p_queue_push(&pid_store, pid); postprocessing(word_chain, cmdlines, &pid_store, params, input_fd, output_fd); } } static void command_processing(struct param_type *params, enum modes *current_mode, struct w_queue *word_chain, struct c_queue *cmdlines, struct dynamic_array *tmp_word, int ch) { char **cmdline = NULL; /* and odd number of double quotes */ if(!is_double_quotes_pair(*params)) { print_error(); reset_params(params, current_mode, word_chain, cmdlines, tmp_word); show_invitation(); return; } if(filename_waiting(params)) { if(params->wrong_command) goto clean; if(params->pipeline) { if(params->tokens == '>' || params->tokens == append) add_filename(tmp_word, params); else if(params->tokens == '<') { fprintf(stderr, "read in the last pipline element\n"); goto clean; } } else add_filename(tmp_word, params); } else if(params->empty_word_flag || params->is_word) add_word(word_chain, tmp_word, params); #if 0 if(params->empty_word_flag || params->is_word) { if(filename_waiting(params)) { if(excessive_words(ch, params)) { fprintf(stderr, "too many args\n"); goto clean; } if(params->pipeline) { if(params->tokens == '>' || params->tokens == append) add_filename(tmp_word, params); else if(params->tokens == '<') { fprintf(stderr, "read in the last pipline element\n"); goto clean; } } else add_filename(tmp_word, params); } else add_word(word_chain, tmp_word, params); } else if(filename_waiting(params)) { fprintf(stderr, "filename expected\n"); goto clean; } #endif if((params->tokens == and || params->tokens == or) && params->last_execution_status == 1) { fprintf(stderr, "cannot be performed\n"); return; } if(word_chain->first == NULL) { fprintf(stderr, "empty command\n"); goto clean; } cmdline = create_cmdline(word_chain, w_queue_get_word_count(word_chain)); c_queue_push(cmdlines, cmdline); run_external_program(word_chain, cmdlines, params); clean: if(params->tokens == '&' || params->tokens == 0 || ch == new_line) show_invitation(); reset_params(params, current_mode, word_chain, cmdlines, tmp_word); } #if 0 static int is_empty_word(int ch, struct param_type params) { return (ch == whitespace || ch == tab) && !params.is_word && params.empty_word_flag; } static int command_execution_condition(struct param_type *params) { return (!filename_waiting(params) && !params->pipeline) || (params->pipeline && params->tokens == '&'); } #endif static void word_separation_processing(int ch, struct param_type *params, enum modes *current_mode, struct w_queue *word_chain, struct c_queue *cmdlines, 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(special_token_handling(word_chain, cmdlines, tmp_word, ch, params)) { if(command_execution_condition(params)) command_processing(params, current_mode, word_chain, cmdlines, tmp_word, ch); return; } if(params->wrong_command) { clean_input_buffer(); reset_params(params, current_mode, word_chain, cmdlines, tmp_word); show_invitation(); return; } if(check_separation(ch, *params)) { if(excessive_words(ch, params)) { clean_input_buffer(); reset_params(params, current_mode, word_chain, cmdlines, tmp_word); fprintf(stderr, "too many args\n"); show_invitation(); return; } if(!filename_waiting(params)) { add_word(word_chain, tmp_word, params); params->is_word = 0; } return; } if(ignore_spaces(ch, *params)) return; add_letter(ch, tmp_word, params); } #if 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 == '"'; } #endif static void whole_word_processing(int ch, struct param_type *params, enum modes *current_mode, struct w_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); add_letter(ch, tmp_word, params); } int main() { int ch; struct param_type params; struct w_queue word_chain; struct c_queue cmdlines; struct dynamic_array tmp_word; enum modes current_mode = word_separation; w_queue_init(&word_chain); c_queue_init(&cmdlines); dynarr_create_array(&tmp_word); init_params(¶ms, ¤t_mode); show_invitation(); while((ch = getchar()) != EOF) { if(ch == new_line) command_processing(¶ms, ¤t_mode, &word_chain, &cmdlines, &tmp_word, ch); else if(current_mode == word_separation) word_separation_processing(ch, ¶ms, ¤t_mode, &word_chain, &cmdlines, &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; }