diff options
| -rw-r--r-- | Makefile | 4 | ||||
| -rw-r--r-- | lexical_analysis.c | 172 | ||||
| -rw-r--r-- | lexical_analysis.h | 9 | ||||
| -rw-r--r-- | shell.c | 478 | ||||
| -rw-r--r-- | shell.h | 22 |
5 files changed, 565 insertions, 120 deletions
@@ -3,12 +3,12 @@ OBJMODULES = $(SRCMODULES:.c=.o) CC = gcc CFLAGS = -Wall -g -c -all: shell-VI +all: shell-VII %.o: %.с %.h $(CC) $(CFLAGS) $< -o $@ -shell-VI: $(OBJMODULES) +shell-VII: $(OBJMODULES) $(CC) $(LIBS) $^ -o $@ -include deps.mk diff --git a/lexical_analysis.c b/lexical_analysis.c index 5db57cb..8196403 100644 --- a/lexical_analysis.c +++ b/lexical_analysis.c @@ -46,11 +46,48 @@ int is_empty_word(int ch, struct param_type params) params.empty_word_flag; } -int command_execution_condition(struct param_type *params) +static int find_end_subshell_before_cur_pos(struct readline_type *readline, + int offset) { + int i; + for(i = readline->considered_index-1-offset; i >= 0; --i) { + if(readline->arr[i] == ' ') + continue; + break; + } + return i >= 0 && readline->arr[i] == ')'; +} + +int command_execution_condition(struct param_type *params, + struct readline_type *readline) +{ + /* + * return 0 when + * ()&& + * ()|| + * (); + * ()& + * Nothing to handling cause empty word + */ + if(params->tokens == and || params->tokens == or) { + if(find_end_subshell_before_cur_pos(readline, 2)) + return 0; + } + if(params->tokens == next_command) { + if(find_end_subshell_before_cur_pos(readline, 1)) + return 0; + } + if(params->tokens == '&') { + if(find_end_subshell_before_cur_pos(readline, 1)) + return 0; + } + if(params->tokens == start_subshell && params->pipeline) + return 1; + return (params->tokens != '<' && params->tokens != '>' && - params->tokens != append && !params->pipeline) || + params->tokens != append && !params->pipeline && + params->tokens != start_subshell) || (params->pipeline && params->tokens == '&'); } @@ -59,24 +96,24 @@ int is_first_special_token_character(int ch) return ch == '<' || ch == '>' || ch == '&' || ch == '|' || ch == ';'; } -int excessive_words(int ch, struct param_type *params) +int excessive_words(int ch, struct param_type *params, + struct readline_type *readline) { - 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_first_special_token_character(next_ch)) { + int i; + for(i = readline->considered_index+1; i <= readline->last_element_index; + ++i) + { + if(readline->arr[i] == ' ') + continue; + if(!is_first_special_token_character(readline->arr[i])) { params->wrong_command = err_extra_chars_after_filename; return 1; - } - else + } else break; } - ungetc(next_ch, stdin); } return 0; } @@ -101,16 +138,10 @@ int validate_redirections(int ch, int next_ch, struct param_type *params) params->streams.output_stream_to_append == NULL); } -int is_double_token(struct param_type *params) -{ - return params->tokens == and || params->tokens == or || - params->tokens == append; -} - int is_special_token(int ch, int next_ch) { - return (ch == '&' && next_ch == '&') || (ch == '|' && ch == '|') || - ch == '&' || ch == ';' || ch == '|'; + return (ch == '&' && next_ch == '&') || (ch == '|' && next_ch == '|') || + ch == '&' || ch == ';' || ch == '|' || ch == '(' || ch == ')'; } int is_redirect_token(int ch, int next_ch) @@ -132,6 +163,10 @@ int stream_redirect_tokens(struct w_queue *word_chain, next_ch = readline->arr[readline->considered_index + 1]; if(is_redirect_token(ch, next_ch)) { + /* + * The word will be added because the token has not yet been + * set in the redirect. + */ add_word_or_filename(word_chain, tmp_word, params); if(params->wrong_command) @@ -139,8 +174,8 @@ int stream_redirect_tokens(struct w_queue *word_chain, if(validate_redirections(ch, next_ch, params)) { params->tokens = (ch == '>' && next_ch == '>') ? append : ch; - if(is_double_token(params)) - ++readline->considered_index; + if(params->tokens == append) + readline->considered_index += 2; return 1; } else params->wrong_command = err_redirect_stream_again; @@ -167,12 +202,23 @@ int pipeline_token_processing(struct w_queue *word_chain, struct dynamic_array *tmp_word, struct param_type *params) { + /* + * pipeline after subshell + * no words to add to cmdlines + */ + if(tmp_word->last_element_index == -1 && params->tokens == start_subshell) { + params->tokens = '|'; + params->pipeline = 1; + return 1; + } + char **cmdline = NULL; if(is_stream_redirection_set(params) && wrong_streams_redirection(params)) { params->wrong_command = err_redirect_stream_in_pipeline; return 0; } + params->tokens = '|'; params->pipeline = 1; cmdline = @@ -184,44 +230,92 @@ int pipeline_token_processing(struct w_queue *word_chain, return 1; } +static void stop_by_previous_effect_tokens(struct param_type *params) +{ + if((params->tokens == and && last_execution_status == 1) || + (params->tokens == or && last_execution_status == 0)) + params->wrong_command = err_set_failure; +} + +static int find_repeated_background_symbol(struct readline_type *readline) +{ + int i; + for(i = readline->considered_index + 1; i <= readline->last_element_index; + ++i) + { + if(readline->arr[i] == '&') + return 1; + } + return 0; +} + /* * verification of special tokens (|, &, &&, ||), except redirection tokens */ int special_tokens(struct w_queue *word_chain, struct c_queue *cmdlines, - struct dynamic_array *tmp_word, int ch, + struct dynamic_array *tmp_word, int *ch, struct param_type *params, struct readline_type *readline) { int next_ch, i; - - if(ch == '|' || ch == '&') + if(*ch == '|' || *ch == '&') next_ch = readline->arr[readline->considered_index + 1]; - if(is_special_token(ch, next_ch)) { + if(is_special_token(*ch, next_ch)) { + /* + * if were tokens == ‘<’ or ‘>’ or ‘>>’, then the filename is added + * else just word + */ add_word_or_filename(word_chain, tmp_word, params); + stop_by_previous_effect_tokens(params); if(params->wrong_command) return 0; - if(ch == '|' && next_ch == '|') + 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 == '&') + readline->considered_index += 2; + return 1; + } + if(*ch == '|') + return pipeline_token_processing(word_chain, cmdlines, tmp_word, + params); + if(*ch == '&' && next_ch == '&') { params->tokens = and; - else if(ch == '&') { + readline->considered_index += 2; + return 1; + } + if(*ch == '&') { + /* repeated & */ + if(find_repeated_background_symbol(readline)) { + params->wrong_command = err_repeated_background_ch; + return 0; + } + for(i = readline->considered_index+1; readline->arr[i] != new_line; ++i) { - if(readline->arr[i] != whitespace && readline->arr[i] != tab) { - params->wrong_command = err_bg_process; - return 0; - } + if(readline->arr[i] == ' ') + continue; + else + break; } + if(readline->arr[i] == new_line) + *ch = new_line; params->tokens = '&'; + return 1; + } + if(*ch == start_subshell) { + params->tokens = start_subshell; + return 1; + } + if(*ch == end_subshell) { + params->tokens = end_subshell; + return 1; } - if(is_double_token(params)) - ++readline->considered_index; + if(*ch == next_command) { + params->tokens = next_command; + return 1; + } + return 1; } return 0; diff --git a/lexical_analysis.h b/lexical_analysis.h index 9d7d556..7fc1734 100644 --- a/lexical_analysis.h +++ b/lexical_analysis.h @@ -12,18 +12,19 @@ int ignore_spaces(int ch, struct param_type params); int change_mode(int ch, struct param_type params); int start_escape_sequence(int ch, struct param_type params); int is_empty_word(int ch, struct param_type params); -int command_execution_condition(struct param_type *params); +int command_execution_condition(struct param_type *params, + struct readline_type *readline); int is_special_token(int ch, int next_ch); int is_redirect_token(int ch, int next_ch); -int excessive_words(int ch, struct param_type *params); +int excessive_words(int ch, struct param_type *params, + struct readline_type *readline); void add_word_or_filename(struct w_queue *word_chain, struct dynamic_array *tmp_word, struct param_type *params); int validate_redirections(int ch, int next_ch, struct param_type *params); -int is_double_token(struct param_type *params); int stream_redirect_tokens(struct w_queue *word_chain, struct dynamic_array *tmp_word, @@ -38,7 +39,7 @@ int pipeline_token_processing(struct w_queue *word_chain, struct param_type *params); int special_tokens(struct w_queue *word_chain, struct c_queue *cmdlines, - struct dynamic_array *tmp_word, int ch, + struct dynamic_array *tmp_word, int *ch, struct param_type *params, struct readline_type *readline); #endif @@ -22,6 +22,7 @@ MAKE_QUEUE_PUSH(c_queue, cmdline, char**) MAKE_QUEUE_PUSH(p_queue, pid, int) enum modes { word_separation, whole_word }; +volatile sig_atomic_t last_execution_status = 0; static void change_terminal_settings(struct termios *cur_terminal_settings, struct termios *save_terminal_settings) @@ -49,11 +50,17 @@ static void show_invitation() static void handler(int signal) { int save_errno = errno; - int pid; + int pid, result; if(signal == SIGCHLD) { do { - pid = wait4(-1, NULL, WNOHANG, NULL); + pid = wait4(-1, &result, WNOHANG, NULL); + if(pid > 0) { + if(WIFEXITED(result)) + last_execution_status = WEXITSTATUS(result) == 0 ? 0 : 1; + else + last_execution_status = 1; + } } while(pid > 0); } errno = save_errno; @@ -71,12 +78,15 @@ static void init_params(struct param_type *params, enum modes *current_mode) 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; params->new_readline = 1; - params->general_pgid = -1; + params->general_pipe_pgid = -1; + params->subshell = 0; + params->background_process = 0; + params->fd_read_for_pipeline = -1; + params->fd_read_for_subshell = -1; *current_mode = word_separation; + last_execution_status = 0; } static void clear_filename(struct param_type *params) @@ -105,6 +115,10 @@ static void reset_params(struct param_type *params, readline_reset_array(readline); clear_filename(params); init_params(params, current_mode); + if(params->fd_read_for_pipeline != -1) + close(params->fd_read_for_pipeline); + if(params->fd_read_for_subshell != -1) + close(params->fd_read_for_subshell); } static void prepare_for_next_command_on_line(struct param_type *params, @@ -149,6 +163,8 @@ static const char* error_code_to_token(int error_code) case err_empty_command: return "empty command"; case err_extra_chars_after_filename: return "extra chars after filename"; case err_odd_double_quotes: return "odd number of double quotes"; + case err_missing_closing_bracket: return "missing closing bracket"; + case err_repeated_background_ch: return "repeated &"; } return NULL; } @@ -158,26 +174,39 @@ void error_identification(const struct param_type *params) print_error_msg(error_code_to_token(params->wrong_command)); } +/* + * Set params->tokens if possible + */ static int special_token_handling(struct w_queue *word_chain, struct c_queue *cmdlines, - struct dynamic_array *tmp_word, int ch, + struct dynamic_array *tmp_word, int *ch, struct param_type *params, struct readline_type *readline) { - return - stream_redirect_tokens(word_chain, tmp_word, ch, params, readline) ? - 1 : special_tokens(word_chain, cmdlines, tmp_word, ch, params, - readline); + int redirect_token_result, special_tokens_result; + + redirect_token_result = + stream_redirect_tokens(word_chain, tmp_word, *ch, params, readline); + if(redirect_token_result) + return 1; + + special_tokens_result = + special_tokens(word_chain, cmdlines, tmp_word, ch, params, readline); + return special_tokens_result; } static void wait_for_process_to_complete(struct p_queue *pid_store) { - int total_process_counter, current_process_counter, wait_pid; + int total_process_counter, current_process_counter, wait_pid, result; total_process_counter = p_queue_get_process_quantity(pid_store); current_process_counter = 0; do { - wait_pid = wait(NULL); + wait_pid = wait(&result); + if(WIFEXITED(result)) + last_execution_status = WEXITSTATUS(result) == 0 ? 0 : 1; + else + last_execution_status = 1; if(p_queue_find_pid(pid_store, wait_pid)) ++current_process_counter; } while(total_process_counter != current_process_counter); @@ -202,23 +231,42 @@ static void close_files(int input_fd, int output_fd) close(output_fd); } +static int get_true_terminal_fd() +{ + if(isatty(0)) + return 0; + else if(isatty(1)) + return 1; + else return 2; +} + 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) { + int terminal_fd; clean_up_memory(word_chain, cmdlines, params); close_files(input_fd, output_fd); - if(params->tokens != '&') { + if(!params->background_process) { wait_for_process_to_complete(pid_store); + /* + * The SIGTTOU signal is sent to a group of background + * processes when attempting to write to the terminal or + * change settings -- this also includes attempting to change + * the current foreground group of terminal (tcsetpgrp). + * https://ru.stackoverflow.com/q/1472943 + */ signal(SIGTTOU, SIG_IGN); - tcsetpgrp(0, getpid()); + terminal_fd = get_true_terminal_fd(); + /* return terminal pgid as foreground group */ + tcsetpgrp(terminal_fd, getpid()); } p_queue_clear(pid_store); } static void set_signal_disposition(struct param_type *params) { - if(params->tokens == '&') + if(params->background_process) /* zombie process termination on signal */ signal(SIGCHLD, handler); else @@ -249,59 +297,143 @@ int is_stream_redirection_set(const struct param_type *params) params->streams.output_stream_to_append; } -static void identify_general_pgid(struct param_type *params, int pid) +static void set_foreground_group(struct param_type *params, int pid) { - if(params->general_pgid == -1) - params->general_pgid = pid; - if(params->tokens != '&') + int terminal_fd; + + if(!params->background_process) { /* foreground group */ - tcsetpgrp(0, params->general_pgid); + terminal_fd = get_true_terminal_fd(); + signal(SIGTTOU, SIG_IGN); + tcsetpgrp(terminal_fd, pid); + } +} + +static int find_subshell_after_pipe(struct readline_type *readline) +{ + int i; + for(i = readline->considered_index; i <= readline->last_element_index; ++i) + { + if(readline->arr[i] == ' ') + continue; + if(readline->arr[i] == '(') + return 1; + break; + } + return 0; +} + +static int is_background_command(struct readline_type *readline) +{ + int i; + for(i = readline->considered_index; i <= readline->last_element_index; ++i){ + if(readline->arr[i] == '&' && readline->arr[i+1] != '&') + return 1; + else if(readline->arr[i] == '&' && readline->arr[i+1] == '&') + ++i; + } + return 0; } static void make_pipeline(struct w_queue *word_chain, struct c_queue *cmdlines, struct param_type *params, + struct readline_type *readline, 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; + int subshell_fd[2]; + int save_read_fd, pid, subshell_after, subshell_before; + + subshell_after = find_subshell_after_pipe(readline); + subshell_before = params->fd_read_for_pipeline != -1 ? 1 : 0; - pipe(fd); cmdline = c_queue_pop(cmdlines); + /* + * If there's more than one element in pipeline, then create a pipeline + * otherwise, one element is used, and then a subshell appears, then the + * channel will be shared between them. + */ + if(!c_queue_is_empty(cmdlines)) + pipe(fd); + else if(subshell_after) + pipe(subshell_fd); + set_signal_disposition(params); pid = fork(); - /* parent process */ - if(pid) - identify_general_pgid(params, pid); - + /* parent */ + if(pid) { + /* + * Determine the shared pgid for all next pipe elements + */ + if(!subshell_before) + params->general_pipe_pgid = pid; + } + /* =================== first process from pipeline ===================*/ + /* child */ if(pid == 0) { - close(fd[0]); - dup2(fd[1], 1); - close(fd[1]); - if(is_stream_redirection_set(params)) + if(subshell_before) { + dup2(params->fd_read_for_pipeline, 0); + close(params->fd_read_for_pipeline); + } + if(!c_queue_is_empty(cmdlines)) { + close(fd[0]); + dup2(fd[1], 1); + close(fd[1]); + } else if(subshell_after) { + close(subshell_fd[0]); + dup2(subshell_fd[1], 1); + close(subshell_fd[1]); + } + if(is_stream_redirection_set(params)) { change_streams(input_fd, output_fd, 1, 1); + if(params->fd_read_for_pipeline != -1 && c_queue_is_empty(cmdlines)) + change_streams(input_fd, output_fd, 1, 0); + } /* change pgid */ - setpgid(getpid(), params->general_pgid == -1 ? - getpid() : params->general_pgid); + setpgid(getpid(), subshell_before == 1 ? + params->general_pipe_pgid : getpid()); + set_foreground_group(params, subshell_before == 1 ? + params->general_pipe_pgid : getpid()); execvp(cmdline[0], cmdline); perror(cmdline[0]); exit(1); } - - close(fd[1]); + /* ================= parent process ==================*/ p_queue_push(&pid_store, pid); + /* + * first process read data from subshell (via pipeline) + */ + if(subshell_before) + close(params->fd_read_for_pipeline); + /* + * last process in pipeline. Next comes subshell. We recorded all data in + * pipeline. Saved read descriptor so that subshell can use it later. + */ + if(subshell_after && c_queue_is_empty(cmdlines)) { + params->fd_read_for_subshell = subshell_fd[0]; + close(subshell_fd[1]); + } + /* + * first process wrote the data to pipeline. Leaving only read descriptor + */ + if(!c_queue_is_empty(cmdlines)) + close(fd[1]); + /* extracting the remaining programs from pipeline */ while(!c_queue_is_empty(cmdlines)) { cmdline = c_queue_pop(cmdlines); /* if not last process in pipeline*/ - if(!c_queue_is_empty(cmdlines)) { + if(!c_queue_is_empty(cmdlines)){ save_read_fd = fd[0]; pipe(fd); - } + } else if(subshell_after) + pipe(subshell_fd); pid = fork(); +/* =============== child process ================== */ if(pid == 0) { if(!c_queue_is_empty(cmdlines)) { dup2(save_read_fd, 0); @@ -314,26 +446,41 @@ static void make_pipeline(struct w_queue *word_chain, struct c_queue *cmdlines, close(fd[0]); } /* for last process in pipeline */ - if(c_queue_is_empty(cmdlines)) + if(c_queue_is_empty(cmdlines)) { if(is_stream_redirection_set(params)) change_streams(input_fd, output_fd, 1, 0); - + if(subshell_after) { + close(subshell_fd[0]); + dup2(subshell_fd[1], 1); + close(subshell_fd[1]); + } + } /* change pgid */ - setpgid(getpid(), params->general_pgid); - + setpgid(getpid(), params->general_pipe_pgid); execvp(cmdline[0], cmdline); perror(cmdline[0]); exit(1); } +/*============= parent process ==============*/ if(!c_queue_is_empty(cmdlines)) { close(save_read_fd); close(fd[1]); - } else + } else { + /* last process */ close(fd[0]); + if(subshell_after) + close(subshell_fd[1]); + } p_queue_push(&pid_store, pid); } +/*============= parent process ==============*/ + if(subshell_after) + params->fd_read_for_subshell = subshell_fd[0]; + else + params->fd_read_for_subshell = -1; postprocessing(word_chain, cmdlines, &pid_store, params, input_fd, output_fd); + params->pipeline = 0; } static void open_files(const struct param_type *params, int *input_fd, @@ -380,7 +527,8 @@ static void change_directory(char **cmdline) static void run_external_program(struct w_queue *word_chain, struct c_queue *cmdlines, - struct param_type *params) + struct param_type *params, + struct readline_type *readline) { int pid, input_fd, output_fd; struct p_queue pid_store; @@ -389,6 +537,11 @@ static void run_external_program(struct w_queue *word_chain, input_fd = 0; output_fd = 0; + if(is_background_command(readline)) + params->background_process = 1; + else + params->background_process = 0; + if(is_stream_redirection_set(params)) open_files(params, &input_fd, &output_fd); @@ -397,7 +550,8 @@ static void run_external_program(struct w_queue *word_chain, return; } if(params->pipeline) { - make_pipeline(word_chain, cmdlines, params, input_fd, output_fd); + make_pipeline(word_chain, cmdlines, params, readline, + input_fd, output_fd); return; } cmdline = c_queue_pop(cmdlines); @@ -410,19 +564,29 @@ static void run_external_program(struct w_queue *word_chain, perror("fork error"); exit(1); } - /* parent process */ - if(pid) - identify_general_pgid(params, pid); /* child process */ if(pid == 0) { if(is_stream_redirection_set(params)) change_streams(input_fd, output_fd, 0, 0); - /* change pgid */ - setpgid(getpid(), params->general_pgid == -1 ? - getpid() : params->general_pgid); + /* + * In the case where the subshell sets fd_read_for_pipeline + * upon encountering a pipe ahead, but then the subshell + * is started again. We then read from fd_read_for_pipeline + * + * Example: ()|() + * + * Here we start the process in a subshell (second '('). + */ + if(params->fd_read_for_pipeline != -1) { + dup2(params->fd_read_for_pipeline, 0); + close(params->fd_read_for_pipeline); + } + /* set foreground process */ + set_foreground_group(params, getpid()); + /* make the process group as a pid */ + setpgid(getpid(), getpid()); execvp(cmdline[0], cmdline); - params->last_execution_status = 1; perror(cmdline[0]); exit(1); } @@ -500,10 +664,14 @@ static void generate_readline(struct readline_type *readline) { struct termios cur_terminal_settings, save_terminal_settings; enum keys found_key = none; - int match_result; + int match_result, terminal_fd; char ch; change_terminal_settings(&cur_terminal_settings, &save_terminal_settings); + /* return foreground group by main process */ + terminal_fd = get_true_terminal_fd(); + signal(SIGTTOU, SIG_IGN); + tcsetpgrp(terminal_fd, getpid()); while((ch = getchar()) && !readline_termination_condition(ch, readline)) { if(ch == tab) { @@ -558,6 +726,9 @@ static void generate_readline(struct readline_type *readline) restore_terminal_settings(&cur_terminal_settings, &save_terminal_settings); } +/* + * Used to run a single program or pipeline + */ static void command_processing(struct param_type *params, enum modes *current_mode, struct w_queue *word_chain, @@ -589,27 +760,39 @@ static void command_processing(struct param_type *params, } else if(params->empty_word_flag || params->is_word) add_word(word_chain, tmp_word, params); - if((params->tokens == and || params->tokens == or) && - params->last_execution_status == 1) { - /* ??? */ - fprintf(stderr, "cannot be performed\n"); - return; - } - - if(word_chain->first == NULL) { - params->wrong_command = err_empty_command; + if(word_chain->first == NULL && cmdlines->first == NULL) { + /* + * Case ()|() + * Here we're on second '(' + * Empty programs in pipeline + * Subshell (second) read data from pipe via + * fd_read_for_pipeline (run_external_program()) + */ + if(params->tokens != '(') { + params->wrong_command = err_empty_command; + params->pipeline = 0; + } goto clean; } + /* not subshell process; was &&/|| command\n */ + if(ch == new_line && + ((params->tokens == and && last_execution_status == 1) || + (params->tokens == or && last_execution_status == 0))) + 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); + if(word_chain->first != NULL) { + cmdline = create_cmdline(word_chain, w_queue_get_word_count(word_chain)); + c_queue_push(cmdlines, cmdline); + } + run_external_program(word_chain, cmdlines, params, readline); clean: error_identification(params); + if(params->tokens == end_subshell && params->subshell) + exit(last_execution_status); last_token = params->tokens; - if(last_token == '&' || last_token == 0 || ch == new_line) { + if(last_token == 0 || ch == new_line) { reset_params(params, current_mode, word_chain, cmdlines, tmp_word, readline); show_invitation(); @@ -619,6 +802,107 @@ clean: tmp_word); } +/* searching for the paired closed bracket too */ +static int find_pipeline_after_subshell(struct readline_type *readline, + struct param_type *params) +{ + int i; + for(i = readline->considered_index; i <= readline->last_element_index && + readline->arr[i] != ')'; ++i) + {} + + if(i == readline->last_element_index+1) { + params->wrong_command = err_missing_closing_bracket; + return 0; + } + ++i; + while(i <= readline->last_element_index) { + if(readline->arr[i] == ' ') { + ++i; + continue; + } + break; + } + if(readline->arr[i] == '|') + return 1; + else + return 0; +} + +static void readline_idx_after_subshell(struct readline_type *readline, + struct param_type *params) +{ + int i; + for(i = readline->considered_index; i <= readline->last_element_index && + readline->arr[i] != ')'; ++i) + {} + + if(i == readline->last_element_index + 1) { + params->wrong_command = err_missing_closing_bracket; + return; + } + + ++i; + /* skip whitespace symbols */ + for( ; i <= readline->last_element_index && readline->arr[i] == ' '; ++i) + {} + readline->considered_index = i; +} + +static void make_subshell(struct readline_type *readline, + struct param_type *params) +{ + int fd[2]; + int pid, pipeline_after, pipeline_before; + + pipeline_after = 0; + + pipeline_after = find_pipeline_after_subshell(readline, params); + pipeline_before = params->fd_read_for_subshell != -1 ? 1 : 0; + + if(pipeline_after) { + pipe(fd); + params->fd_read_for_pipeline = fd[0]; + } + + pid = fork(); + /* child -- subshell */ + if(pid == 0) { + params->subshell = 1; + params->pipeline = 0; + if(pipeline_after) { + /* all processes in pipeline will have the same pgid */ + setpgid(getpid(), getpid()); + set_foreground_group(params, getpid()); + close(fd[0]); + dup2(fd[1], 1); + close(fd[1]); + } + if(pipeline_before) { + dup2(params->fd_read_for_subshell, 0); + close(params->fd_read_for_subshell); + /* + * pipeline has already created a process group + * subshell is a member of pipeline + */ + setpgid(getpid(), params->general_pipe_pgid); + } + /* parent */ + } else { + /* + * Delay to let the subshell (child process) finish and set + * last_execution_status, so the parent + * can check it — useful for && and ||. + */ + sleep(1); + if(pipeline_after) { + close(fd[1]); + params->general_pipe_pgid = pid; + } + readline_idx_after_subshell(readline, params); + } +} + static void word_separation_processing(int ch, struct param_type *params, enum modes *current_mode, struct w_queue *word_chain, @@ -626,6 +910,8 @@ static void word_separation_processing(int ch, struct param_type *params, struct dynamic_array *tmp_word, struct readline_type *readline) { + int token_handling_result; + /* could be a marker for the beginning of a blank word */ params->stored_symbol = ch; if(is_empty_word(ch, *params)) { @@ -644,15 +930,48 @@ static void word_separation_processing(int ch, struct param_type *params, params->escape_sequences = 1; return; } - if(special_token_handling(word_chain, cmdlines, tmp_word, ch, params, - readline)) { - if(command_execution_condition(params)) + token_handling_result = + special_token_handling(word_chain, cmdlines, tmp_word, &ch, params, + readline); + /* was found special token */ + if(token_handling_result) { + if(command_execution_condition(params, readline)) command_processing(params, current_mode, word_chain, cmdlines, tmp_word, readline, ch); + /* + * The start_subshell token acts as a special marker. + * It helps determine the point at which programs (or a pipeline) + * should start. However, even after that, we still launch a + * subshell process. + */ + if(params->tokens == start_subshell) + make_subshell(readline, params); return; } if(params->wrong_command) { error_identification(params); + + /* subshell termination failed; failure with (&&, ||) too */ + if(params->subshell) { + /* + * (proc_1 && proc_2) -> false + * proc_1 went wrong + */ + if(params->tokens == and) + exit(1); + /* + * (proc_1 || proc_2) -> true + * proc_1 was completed successfully + */ + else if(params->tokens == or) + exit(0); + /* + * other errors related to subshell termination + */ + else + exit(1); + } + clean_input_buffer(); reset_params(params, current_mode, word_chain, cmdlines, tmp_word, readline); @@ -660,9 +979,12 @@ static void word_separation_processing(int ch, struct param_type *params, return; } if(check_separation(ch, *params)) { - if(excessive_words(ch, params)) { + if(excessive_words(ch, params, readline)) { error_identification(params); clean_input_buffer(); + /* subshell termination failed */ + if(params->subshell) + exit(1); reset_params(params, current_mode, word_chain, cmdlines, tmp_word, readline); show_invitation(); @@ -731,6 +1053,7 @@ int main() dynarr_create_array(&tmp_word); readline_create_array(&readline); init_params(¶ms, ¤t_mode); + signal(SIGCHLD, handler); show_invitation(); generate_readline(&readline); @@ -742,14 +1065,27 @@ int main() params.new_readline = 0; if(ch == new_line) + /* also contains the following calls to generate_readline */ command_processing(¶ms, ¤t_mode, &word_chain, &cmdlines, &tmp_word, &readline, ch); else if(current_mode == word_separation) { word_separation_processing(ch, ¶ms, ¤t_mode, &word_chain, &cmdlines, &tmp_word, &readline); - /* double token was found */ - if(readline.considered_index != i) - ++i; + /* + * parent process skiped subshell part + * move to position after ( + */ + if(params.tokens == start_subshell && !params.subshell) { + i = readline.considered_index; + continue; + } + /* + * double token was found + */ + if(readline.considered_index != i) { + i = readline.considered_index; + continue; + } } else if(current_mode == whole_word) whole_word_processing(ch, ¶ms, ¤t_mode, &word_chain, @@ -4,8 +4,9 @@ #include "dynamic_array.h" #include "queue.h" +#include <signal.h> + enum { - /* CTRL-D */ end_of_file = 4, new_line = 10, whitespace = ' ', @@ -19,7 +20,10 @@ enum { enum { append = '>' + 1, and = '&' + 1, - or = '|' + 1 + or = '|' + 1, + next_command = ';', + start_subshell = '(', + end_subshell = ')' }; /* error codes */ @@ -30,7 +34,10 @@ enum { err_bg_process = 4, err_empty_command = 5, err_extra_chars_after_filename = 6, - err_odd_double_quotes = 7 + err_odd_double_quotes = 7, + err_set_failure = 8, + err_missing_closing_bracket = 9, + err_repeated_background_ch = 10 }; /* storing file names to redirect standard input/output streams */ @@ -49,12 +56,19 @@ struct param_type { int tokens; int wrong_command; struct io_type streams; - int last_execution_status; int pipeline; int new_readline; + int general_pipe_pgid; + int general_subshell_pgid; int general_pgid; + int subshell; + int fd_read_for_pipeline; + int fd_read_for_subshell; + int background_process; }; +extern volatile sig_atomic_t last_execution_status; + int filename_waiting(struct param_type *params); void add_filename(struct dynamic_array *tmp_word, struct param_type *params); void add_word(struct w_queue *word_chain, struct dynamic_array *tmp_word, |
