Skip to content

Commit 8d38bc1

Browse files
committed
Ensure the shell is in the foreground when job control is enabled
This commit implements part of the initialization of the job-control shell that is specified in POSIX.1-2024. We do it slightly differently from the standard due to the reason described in the comment for the Env::ensure_foreground method. This commit also ensures that the shell is in the foreground when job control is enabled after the shell starts. Fixes #481 Fixes #421 This commit also updates the test harness to use `tcgetsid` instead of `tcgetpgrp` to check the session ID of the terminal. It is unclear whether the current process becomes the foreground process group when it obtains the controlling terminal, so we should not rely on the initial foreground process group being the same as the shell's process group, just in case.
1 parent 8e5da28 commit 8d38bc1

File tree

7 files changed

+74
-9
lines changed

7 files changed

+74
-9
lines changed

yash-builtin/CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ specifying a delimiter character to terminate the input.
2323
The `read` built-in now returns a more specific exit status depending on the
2424
cause of the error. It also rejects an input containing a null byte.
2525

26+
The `set` bulit-in now suspends itself when the `-m` option is enabled in the
27+
background.
28+
2629
The `trap` built-in now implements the POSIX.1-2024 behavior of showing signal
2730
dispositions that are not explicitly set by the user. It also supports the `-p`
2831
(`--print`) option.
@@ -93,6 +96,8 @@ The `wait` built-in no longer treats suspended jobs as terminated jobs.
9396
- The `read::main` function now returns a more specific exit status depending on
9497
the cause of the error. It now returns `EXIT_STATUS_READ_ERROR` when finding a
9598
null byte in the input.
99+
- The `set::main` function now internally calls `yash_env::Env::ensure_foreground`
100+
when the `-m` option is enabled.
96101
- The `trap::syntax::interpret` function now supports the `-p` option.
97102
- The output of the `trap` built-in now includes not only user-defined traps but
98103
also signal dispositions that are not explicitly set by the user.

yash-builtin/src/set.rs

+24-7
Original file line numberDiff line numberDiff line change
@@ -166,13 +166,9 @@ pub mod syntax;
166166
// TODO pub mod semantics;
167167

168168
/// Enables or disables the internal dispositions for the "stopper" signals
169-
/// depending on the `Interactive` and `Monitor` option states. The dispositions
170-
/// are disabled in subshells.
169+
/// depending on the `Interactive` and `Monitor` option states.
171170
fn update_internal_dispositions_for_stoppers(env: &mut Env) {
172-
if env.options.get(Interactive) == State::On
173-
&& env.options.get(Monitor) == State::On
174-
&& !env.stack.contains(&Subshell)
175-
{
171+
if env.options.get(Interactive) == State::On && env.options.get(Monitor) == State::On {
176172
env.traps
177173
.enable_internal_dispositions_for_stoppers(&mut env.system)
178174
} else {
@@ -182,17 +178,34 @@ fn update_internal_dispositions_for_stoppers(env: &mut Env) {
182178
.ok();
183179
}
184180

181+
/// Ensures that the shell is in the foreground process group if the `Monitor`
182+
/// option is enabled.
183+
fn ensure_foreground(env: &mut Env) {
184+
if env.options.get(Monitor) == State::On {
185+
env.ensure_foreground().ok();
186+
}
187+
}
188+
185189
/// Modifies shell options and positional parameters.
186190
fn modify(
187191
env: &mut Env,
188192
options: Vec<(yash_env::option::Option, State)>,
189193
positional_params: Option<Vec<Field>>,
190194
) {
191195
// Modify options
196+
let mut monitor_changed = false;
192197
for (option, state) in options {
193198
env.options.set(option, state);
199+
monitor_changed |= option == Monitor;
200+
}
201+
202+
// Reinitialize job control
203+
if monitor_changed && !env.stack.contains(&Subshell) {
204+
// We ignore errors in theses functions because they are not essential
205+
// for updating the options.
206+
update_internal_dispositions_for_stoppers(env);
207+
ensure_foreground(env);
194208
}
195-
update_internal_dispositions_for_stoppers(env);
196209

197210
// Modify positional parameters
198211
if let Some(fields) = positional_params {
@@ -472,4 +485,8 @@ xtrace off
472485
let disposition = state.processes[&env.main_pid].disposition(SIGTSTP);
473486
assert_eq!(disposition, Disposition::Default);
474487
}
488+
489+
// TODO Test the case when the -m option is enabled while the shell is not
490+
// in the foreground. This requires the correct implementation of the
491+
// `VirtualSystem::tcsetpgrp` method.
475492
}

yash-cli/CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2929
the next command line. This behavior basically conforms to POSIX.1-2024, but
3030
differs in that the shell does not resume with the remaining commands
3131
following the next asynchronous and-or list.
32+
- When the shell starts job control, if it is in the background, the shell now
33+
suspends itself until it is resumed in the foreground. Previously, the shell
34+
would continue running in the background, interfering with the foreground
35+
process group.
3236
- If job control is enabled and the shell does not have a controlling terminal,
3337
the shell now proceeds without managing foreground-ness of process groups.
3438
Jobs are still assigned to their own process groups. Previously, the shell

yash-cli/src/startup.rs

+6
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@ pub fn configure_environment(env: &mut Env, run: Run) -> Work {
8686
}
8787
}
8888

89+
// Make sure the shell is in the foreground if job control is enabled
90+
if env.options.get(Monitor) == On {
91+
// Ignore failures as we can still proceed even if we can't get into the foreground
92+
env.ensure_foreground().ok();
93+
}
94+
8995
// Prepare built-ins
9096
env.builtins.extend(BUILTINS.iter().cloned());
9197

yash-cli/tests/pty/mod.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ use nix::fcntl::{OFlag, open};
2626
use nix::libc;
2727
use nix::pty::{PtyMaster, grantpt, posix_openpt, ptsname, unlockpt};
2828
use nix::sys::stat::Mode;
29-
use nix::unistd::{close, getpgrp, setsid, tcgetpgrp};
29+
use nix::sys::termios::tcgetsid;
30+
use nix::unistd::{close, getpid, setsid};
3031
use std::ffi::c_int;
3132
use std::os::fd::{AsRawFd as _, BorrowedFd, FromRawFd as _, OwnedFd};
3233
use std::path::{Path, PathBuf};
@@ -94,7 +95,7 @@ fn prepare_as_slave(slave_path: &Path) -> nix::Result<()> {
9495
unsafe { libc::ioctl(raw_fd, libc::TIOCSCTTY as _, 0 as c_int) };
9596

9697
let fd = unsafe { BorrowedFd::borrow_raw(raw_fd) };
97-
if tcgetpgrp(fd) == Ok(getpgrp()) {
98+
if tcgetsid(fd) == Ok(getpid()) {
9899
Ok(())
99100
} else {
100101
Err(nix::Error::ENOTSUP)

yash-env/CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515
It can be used to store arbitrary data.
1616
- The `Env` struct now has the `wait_for_subshell_to_halt` method.
1717
- This method waits for the subshell to terminate or stop.
18+
- The `Env` struct now has the `ensure_foreground` method.
19+
- This method ensures that the shell is in the foreground.
1820
- The `System` trait now has the `get_sigaction` method.
1921
- This method returns the current signal handling configuration for a signal.
2022
This method does not modify anything, so it can be used with an immutable

yash-env/src/lib.rs

+30
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,36 @@ impl Env {
327327
final_fd
328328
}
329329

330+
/// Ensures the shell is in the foreground.
331+
///
332+
/// If the current process belongs to the same process group as the session
333+
/// leader, this function forces the current process to be in the foreground
334+
/// by calling [`SystemEx::tcsetpgrp_with_block`]. Otherwise, this function
335+
/// suspends the process until it is resumed in the foreground by another
336+
/// job-controlling process (see [`SystemEx::tcsetpgrp_without_block`]).
337+
///
338+
/// This function returns an error if the process does not have a controlling
339+
/// terminal, that is, [`get_tty`](Self::get_tty) returns `Err(_)`.
340+
///
341+
/// # Note on POSIX conformance
342+
///
343+
/// This function implements part of the initialization of the job-control
344+
/// shell. POSIX says that the shell should bring itself into the foreground
345+
/// only if it is started as the controlling process (that is, the session
346+
/// leader) for the terminal session. However, this function also brings the
347+
/// shell into the foreground if the shell is in the same process group as
348+
/// the session leader because it is unlikely that there is another
349+
/// job-controlling process that can bring the shell into the foreground.
350+
pub fn ensure_foreground(&mut self) -> Result<(), Errno> {
351+
let fd = self.get_tty()?;
352+
353+
if self.system.tcgetpgrp(fd)? == self.main_pgid {
354+
self.system.tcsetpgrp_with_block(fd, self.main_pgid)
355+
} else {
356+
self.system.tcsetpgrp_without_block(fd, self.main_pgid)
357+
}
358+
}
359+
330360
/// Tests whether the current environment is an interactive shell.
331361
///
332362
/// This function returns true if and only if:

0 commit comments

Comments
 (0)