diff --git a/src/exec.cpp b/src/exec.cpp index 2e5a9760e..234f656c2 100644 --- a/src/exec.cpp +++ b/src/exec.cpp @@ -300,7 +300,7 @@ void internal_exec(env_stack_t &vars, job_t *j, const io_chain_t &all_ios) { // commands in the pipeline will apply to exec. However, using exec in a pipeline doesn't // really make sense, so I'm not trying to fix it here. auto redirs = dup2_list_t::resolve_chain(all_ios); - if (redirs && !child_setup_process(0, *redirs)) { + if (redirs && !child_setup_process(nullptr, nullptr, *redirs)) { // Decrement SHLVL as we're removing ourselves from the shell "stack". auto shlvl_var = vars.get(L"SHLVL", ENV_GLOBAL | ENV_EXPORT); wcstring shlvl_str = L"0"; @@ -445,7 +445,7 @@ static bool fork_child_for_process(const std::shared_ptr &job, process_t // stdout and stderr, and then exit. p->pid = getpid(); child_set_group(job.get(), p); - child_setup_process(p, dup2s); + child_setup_process(job.get(), p, dup2s); child_action(); DIE("Child process returned control to fork_child lambda!"); } diff --git a/src/postfork.cpp b/src/postfork.cpp index 9550bc7c1..4643fad3e 100644 --- a/src/postfork.cpp +++ b/src/postfork.cpp @@ -96,10 +96,8 @@ bool child_set_group(job_t *j, process_t *p) { return false; } } else { - // The child does not actually use this field. j->pgid = getpgrp(); } - return true; } @@ -154,7 +152,7 @@ bool maybe_assign_terminal(const job_t *j) { return true; } -int child_setup_process(process_t *p, const dup2_list_t &dup2s) { +int child_setup_process(const job_t *job, process_t *p, const dup2_list_t &dup2s) { // Note we are called in a forked child. for (const auto &act : dup2s.get_actions()) { int err = act.target < 0 ? close(act.src) : dup2(act.src, act.target); @@ -169,6 +167,17 @@ int child_setup_process(process_t *p, const dup2_list_t &dup2s) { } // Set the handling for job control signals back to the default. signal_reset_handlers(); + + if (job != nullptr && job->get_flag(job_flag_t::TERMINAL) && job->is_foreground()) { + // Assign the terminal within the child to avoid the well-known race between tcsetgrp() in + // the parent and the child executing. We are not interested in error handling here, except + // we try to avoid this for non-terminals; in particular pipelines often make non-terminal + // stdin. + if (isatty(STDIN_FILENO)) { + (void)tcsetpgrp(STDIN_FILENO, job->pgid); + } + } + return 0; } diff --git a/src/postfork.h b/src/postfork.h index 51bc1afca..af845f7f9 100644 --- a/src/postfork.h +++ b/src/postfork.h @@ -30,7 +30,7 @@ bool maybe_assign_terminal(const job_t *j); /// /// \return 0 on sucess, -1 on failiure. When this function returns, signals are always unblocked. /// On failiure, signal handlers, io redirections and process group of the process is undefined. -int child_setup_process(process_t *p, const dup2_list_t &dup2s); +int child_setup_process(const job_t *job, process_t *p, const dup2_list_t &dup2s); /// Call fork(), optionally waiting until we are no longer multithreaded. If the forked child /// doesn't do anything that could allocate memory, take a lock, etc. (like call exec), then it's