back to scratko.xyz
aboutsummaryrefslogtreecommitdiff
path: root/shell.c
diff options
context:
space:
mode:
authorscratko <m@scratko.xyz>2025-07-28 01:19:54 +0300
committerscratko <m@scratko.xyz>2025-07-28 01:19:54 +0300
commit0de355ebbf6d9eb9ab673da22eb18cbb01d005f8 (patch)
tree197d81a0d4df5c728217cc3ebaa255678d621b86 /shell.c
parent765c24a70be0b968a08bbd3c26b1644843863fcd (diff)
downloadshell-0de355ebbf6d9eb9ab673da22eb18cbb01d005f8.tar.gz
shell-0de355ebbf6d9eb9ab673da22eb18cbb01d005f8.tar.bz2
shell-0de355ebbf6d9eb9ab673da22eb18cbb01d005f8.zip
Revise subshell execution and terminal group handlingshell-VII
find_end_subshell_before_cur_pos() now accounts for offset caused by two-character tokens (|| and &&). Added many explanatory comments. Modified or removed some fields of the params structure that affected pipe and subshell behavior (e.g., pgid for multiple processes, file descriptors). Changed logic for setting the current terminal process group. Removed side effect in the conditional operator inside special_token_handling() that made the code harder to read. Removed the functions identify_general_pgid() and identify_general_pipe_pgid(). Replaced them with a single function: set_foreground_group(). make_pipeline() now also checks for subshell_before before executing the pipeline. Major changes to the functions make_pipeline(), make_subshell(), and run_external_program(). Subshell exit code now depends on the success or failure of executed programs when using logical operators (|| and &&).
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;