back to scratko.xyz
aboutsummaryrefslogtreecommitdiff
path: root/shell.c
diff options
context:
space:
mode:
Diffstat (limited to 'shell.c')
-rw-r--r--shell.c261
1 files changed, 154 insertions, 107 deletions
diff --git a/shell.c b/shell.c
index be09e44..27ebf44 100644
--- a/shell.c
+++ b/shell.c
@@ -80,12 +80,10 @@ static void init_params(struct param_type *params, enum modes *current_mode)
params->streams.output_stream_to_append = NULL;
params->pipeline = 0;
params->new_readline = 1;
- params->general_pgid = -1;
params->general_pipe_pgid = -1;
- params->general_subshell_pgid = -1;
params->subshell = 0;
params->background_process = 0;
- params->fd_read_for_channel = -1;
+ params->fd_read_for_pipeline = -1;
params->fd_read_for_subshell = -1;
*current_mode = word_separation;
last_execution_status = 0;
@@ -117,8 +115,8 @@ 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_channel != -1)
- close(params->fd_read_for_channel);
+ 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);
}
@@ -176,16 +174,25 @@ 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 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)
@@ -242,8 +249,16 @@ static void postprocessing(struct w_queue *word_chain, struct c_queue *cmdlines,
close_files(input_fd, output_fd);
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);
terminal_fd = get_true_terminal_fd();
+ /* return terminal pgid as foreground group */
tcsetpgrp(terminal_fd, getpid());
}
p_queue_clear(pid_store);
@@ -282,36 +297,23 @@ int is_stream_redirection_set(const struct param_type *params)
params->streams.output_stream_to_append;
}
-static void identify_general_pipe_pgid(struct param_type *params, int pid)
+static void set_foreground_group(struct param_type *params, int pid)
{
int terminal_fd;
- if(params->general_pipe_pgid == -1)
- params->general_pipe_pgid = pid;
- if(!params->background_process) {
- /* foreground group */
- terminal_fd = get_true_terminal_fd();
- signal(SIGTTOU, SIG_IGN);
- tcsetpgrp(terminal_fd, params->general_pipe_pgid);
- }
-}
-static void identify_general_pgid(struct param_type *params, int pid)
-{
- int terminal_fd;
- if(params->general_pgid == -1)
- params->general_pgid = pid;
if(!params->background_process) {
/* foreground group */
terminal_fd = get_true_terminal_fd();
signal(SIGTTOU, SIG_IGN);
- tcsetpgrp(terminal_fd, params->general_pgid);
+ 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) {
+ for(i = readline->considered_index; i <= readline->last_element_index; ++i)
+ {
if(readline->arr[i] == ' ')
continue;
if(readline->arr[i] == '(')
@@ -343,42 +345,39 @@ static void make_pipeline(struct w_queue *word_chain, struct c_queue *cmdlines,
char **cmdline = NULL;
int fd[2];
int subshell_fd[2];
- int save_read_fd, pid, subshell_after;
+ int save_read_fd, pid, subshell_after, subshell_before;
subshell_after = find_subshell_after_pipe(readline);
- cmdline = c_queue_pop(cmdlines);
+ subshell_before = params->fd_read_for_pipeline != -1 ? 1 : 0;
+ 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);
-
- set_signal_disposition(params);
-
-#if 0
- if(params->fd_pipe_rd != -1)
- /* set foreground group by subshell process */
- identify_general_pgid(params, params->general_pgid);
-#endif
-
- if(c_queue_is_empty(cmdlines) && subshell_after)
+ else if(subshell_after)
pipe(subshell_fd);
+ set_signal_disposition(params);
pid = fork();
-#if 0
- /* parent process */
- if(pid)
- identify_general_pgid(params, pid);
-#endif
+ /* parent */
if(pid) {
- /* else set foreground group by first pipe process */
- if(params->general_pipe_pgid == -1)
- identify_general_pipe_pgid(params, 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) {
- if(params->fd_read_for_channel != -1) {
- dup2(params->fd_read_for_channel, 0);
- close(params->fd_read_for_channel);
+ 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]);
@@ -391,28 +390,40 @@ static void make_pipeline(struct w_queue *word_chain, struct c_queue *cmdlines,
}
if(is_stream_redirection_set(params)) {
change_streams(input_fd, output_fd, 1, 1);
- if(params->fd_read_for_channel != -1 && c_queue_is_empty(cmdlines))
+ 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_pipe_pgid == -1 ? getpid() :
- params->general_pipe_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);
}
+ /* ================= parent process ==================*/
p_queue_push(&pid_store, pid);
- if(params->fd_read_for_channel != -1)
- close(params->fd_read_for_channel);
-
+ /*
+ * 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*/
@@ -421,8 +432,8 @@ static void make_pipeline(struct w_queue *word_chain, struct c_queue *cmdlines,
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);
@@ -450,26 +461,26 @@ static void make_pipeline(struct w_queue *word_chain, struct c_queue *cmdlines,
perror(cmdline[0]);
exit(1);
}
+/*============= parent process ==============*/
if(!c_queue_is_empty(cmdlines)) {
close(save_read_fd);
close(fd[1]);
} 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);
-#if 0
- if(!subshell_after)
- params->general_pgid = -1;
-#endif
+ params->pipeline = 0;
}
static void open_files(const struct param_type *params, int *input_fd,
@@ -553,28 +564,28 @@ static void run_external_program(struct w_queue *word_chain,
perror("fork error");
exit(1);
}
-#if 0
- /* parent process */
- if(pid)
- identify_general_pgid(params, getpid());
-#endif
-
/* child process */
if(pid == 0) {
if(is_stream_redirection_set(params))
change_streams(input_fd, output_fd, 0, 0);
- if(params->fd_read_for_channel != -1) {
- dup2(params->fd_read_for_channel, 0);
- close(params->fd_read_for_channel);
+ /*
+ * 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 */
- if(params->subshell && params->general_pgid != -1)
- setpgid(getpid(), params->general_pgid);
- else {
- identify_general_pgid(params, getpid());
- setpgid(getpid(), getpid());
- }
+ set_foreground_group(params, getpid());
+ /* make the process group as a pid */
+ setpgid(getpid(), getpid());
execvp(cmdline[0], cmdline);
perror(cmdline[0]);
exit(1);
@@ -583,11 +594,6 @@ static void run_external_program(struct w_queue *word_chain,
p_queue_push(&pid_store, pid);
postprocessing(word_chain, cmdlines, &pid_store, params, input_fd,
output_fd);
- /* ??? */
-#if 0
- if(!params->subshell)
- params->general_pgid = -1;
-#endif
}
}
@@ -720,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,
@@ -752,21 +761,19 @@ static void command_processing(struct param_type *params,
add_word(word_chain, tmp_word, params);
if(word_chain->first == NULL && cmdlines->first == NULL) {
- /* if not ending subshell */
- if(params->tokens != '(')
+ /*
+ * 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;
}
-#if 0
- if((ch == new_line || ch == end_subshell) &&
- ((params->tokens == and && last_execution_status == 1) ||
- (params->tokens == or && last_execution_status == 0))) {
- if(ch == end_subshell && params->subshell)
- exit(1);
- params->wrong_command = err_set_failure;
- goto clean;
- }
-#endif
/* not subshell process; was &&/|| command\n */
if(ch == new_line &&
((params->tokens == and && last_execution_status == 1) ||
@@ -855,18 +862,18 @@ static void make_subshell(struct readline_type *readline,
if(pipeline_after) {
pipe(fd);
- params->fd_read_for_channel = fd[0];
+ params->fd_read_for_pipeline = fd[0];
}
pid = fork();
- /* child */
+ /* child -- subshell */
if(pid == 0) {
params->subshell = 1;
params->pipeline = 0;
if(pipeline_after) {
- identify_general_pipe_pgid(params, getpid());
- params->general_pgid = getpid();
- setpgid(getpid(), params->general_pipe_pgid);
+ /* 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]);
@@ -874,11 +881,19 @@ static void make_subshell(struct readline_type *readline,
if(pipeline_before) {
dup2(params->fd_read_for_subshell, 0);
close(params->fd_read_for_subshell);
- params->general_pgid = params->general_pipe_pgid;
+ /*
+ * 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]);
@@ -895,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)) {
@@ -913,11 +930,20 @@ 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)) {
+ 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;
@@ -926,8 +952,25 @@ static void word_separation_processing(int ch, struct param_type *params,
error_identification(params);
/* subshell termination failed; failure with (&&, ||) too */
- if(params->subshell)
- exit(1);
+ 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,
@@ -1022,19 +1065,23 @@ int main()
params.new_readline = 0;
if(ch == new_line)
+ /* also contains the following calls to generate_readline */
command_processing(&params, &current_mode, &word_chain,
&cmdlines, &tmp_word, &readline, ch);
else if(current_mode == word_separation) {
word_separation_processing(ch, &params, &current_mode, &word_chain,
&cmdlines, &tmp_word, &readline);
+ /*
+ * parent process skiped subshell part
+ * move to position after (
+ */
if(params.tokens == start_subshell && !params.subshell) {
i = readline.considered_index;
continue;
}
/*
- * either a double token was found
- * or the subshell part was skipped
- */
+ * double token was found
+ */
if(readline.considered_index != i) {
i = readline.considered_index;
continue;