From 3387390a717a81029f48e7dd887f2f60ba05ee6f Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy <mmstickman@gmail.com> Date: Thu, 6 Jul 2017 20:27:01 -0400 Subject: [PATCH] Implement Process Groups Almost everything is working now. The target/debug/ion command doesn't work yet though. --- src/builtins/job_control.rs | 63 ++++----- src/main.rs | 32 ++++- src/shell/job_control.rs | 92 ++++++++++-- src/shell/pipe.rs | 271 +++++++++++++++++------------------- 4 files changed, 260 insertions(+), 198 deletions(-) diff --git a/src/builtins/job_control.rs b/src/builtins/job_control.rs index ba542fe9..af0aea9d 100644 --- a/src/builtins/job_control.rs +++ b/src/builtins/job_control.rs @@ -2,10 +2,21 @@ use shell::Shell; use shell::job_control::{JobControl, ProcessState}; use shell::status::*; use std::io::{stderr, Write}; -use std::thread::sleep; -use std::time::Duration; #[cfg(not(target_os = "redox"))] use nix::sys::signal::{self, Signal}; -#[cfg(not(target_os = "redox"))] use libc::{self, pid_t}; +#[cfg(not(target_os = "redox"))] use nix::unistd; + +#[cfg(all(unix, not(target_os = "redox")))] +/// When given a process ID, that process's group will be assigned as the foreground process group. +pub fn set_foreground(pid: u32) { + let _ = unistd::tcsetpgrp(0, pid as i32); + let _ = unistd::tcsetpgrp(1, pid as i32); + let _ = unistd::tcsetpgrp(2, pid as i32); +} + +#[cfg(target_os = "redox")] +pub fn set_foreground(pid: u32) { + // TODO +} /// Display a list of all jobs running in the background. pub fn jobs(shell: &mut Shell) { @@ -19,32 +30,7 @@ pub fn jobs(shell: &mut Shell) { } } -#[cfg(not(target_os = "redox"))] -fn fg_listen(shell: &mut Shell, job: u32) { - loop { - sleep(Duration::from_millis(100)); - let job = &mut (*shell.background.lock().unwrap())[job as usize]; - if let ProcessState::Empty = job.state { break } - if let Ok(signal) = shell.signals.try_recv() { - match signal { - libc::SIGTSTP => { - let _ = signal::kill(job.pid as pid_t, Some(Signal::SIGTSTP)); - break - }, - libc::SIGTERM => { - shell.handle_signal(libc::SIGTERM); - }, - libc::SIGINT => { - let _ = signal::kill(job.pid as pid_t, Some(Signal::SIGINT)); - break - }, - _ => unimplemented!() - } - } - } -} - -#[cfg(not(target_os = "redox"))] +#[cfg(all(unix, not(target_os = "redox")))] pub fn fg(shell: &mut Shell, args: &[&str]) -> i32 { let mut status = 0; for arg in args { @@ -61,13 +47,15 @@ pub fn fg(shell: &mut Shell, args: &[&str]) -> i32 { match job.state { ProcessState::Running => { - fg_listen(shell, njob); - status = SUCCESS; + set_foreground(njob); + // TODO: This doesn't work + status = shell.watch_foreground(njob) }, ProcessState::Stopped => { - let _ = signal::kill(job.pid as pid_t, Some(Signal::SIGCONT)); - fg_listen(shell, njob); - status = SUCCESS; + let _ = signal::kill(-(job.pid as i32), Some(Signal::SIGCONT)); + set_foreground(njob); + // TODO: This doesn't work + status = shell.watch_foreground(njob); }, ProcessState::Empty => { let stderr = stderr(); @@ -91,7 +79,7 @@ pub fn fg(_: &mut Shell, _: &[&str]) -> i32 { 0 } -#[cfg(not(target_os = "redox"))] +#[cfg(all(unix, not(target_os = "redox")))] pub fn bg(shell: &mut Shell, args: &[&str]) -> i32 { let mut error = false; let stderr = stderr(); @@ -105,8 +93,9 @@ pub fn bg(shell: &mut Shell, args: &[&str]) -> i32 { error = true; }, ProcessState::Stopped => { - let _ = signal::kill(job.pid as pid_t, Some(Signal::SIGCONT)); - let _ = writeln!(stderr, "[{}] {} {}", njob, job.pid, job.state); + let _ = signal::kill(-(job.pid as i32), Some(Signal::SIGCONT)); + job.state = ProcessState::Running; + let _ = writeln!(stderr, "[{}] {} Running", njob, job.pid); }, ProcessState::Empty => { let _ = writeln!(stderr, "ion: bg: job {} does not exist", njob); diff --git a/src/main.rs b/src/main.rs index 64245edb..cec152a1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -72,7 +72,6 @@ fn inner_main(sigint_rx : mpsc::Receiver<i32>) { shell.execute(); } - #[cfg(not(target_os = "redox"))] fn main() { let (signals_tx, signals_rx) = mpsc::channel(); @@ -82,14 +81,17 @@ fn main() { let mut core = Core::new().unwrap(); let handle = core.handle(); - // Create a stream that will select over SIGINT, SIGTERM and SIGTSTP signals. - let signal_stream = Signal::new(unix_signal::SIGINT, &handle).flatten_stream() - .select(Signal::new(unix_signal::SIGTERM, &handle).flatten_stream()) - .select(Signal::new(libc::SIGTSTP, &handle).flatten_stream()); + // Mask the SIGTSTP signal -- prevents the shell from being stopped + // when the foreground group is changed during command execution. + mask_sigstp(); + + // Create a stream that will select over SIGINT and SIGTERM signals. + let signals = Signal::new(unix_signal::SIGINT, &handle).flatten_stream() + .select(Signal::new(unix_signal::SIGTERM, &handle).flatten_stream()); // Execute the event loop that will listen for and transmit received // signals to the shell. - core.run(signal_stream.for_each(|signal| { + core.run(signals.for_each(|signal| { let _ = signals_tx.send(signal); Ok(()) })).unwrap(); @@ -100,3 +102,21 @@ fn main() { let (_, signals_rx) = mpsc::channel(); inner_main(signals_rx); } + +#[cfg(all(unix, not(target_os = "redox")))] +fn mask_sigstp() { + unsafe { + use libc::{sigset_t, SIGTSTP, SIG_BLOCK, sigemptyset, sigaddset, sigprocmask}; + use std::mem; + use std::ptr; + let mut sigset = mem::uninitialized::<sigset_t>(); + sigemptyset(&mut sigset as *mut sigset_t); + sigaddset(&mut sigset as *mut sigset_t, SIGTSTP); + sigprocmask(SIG_BLOCK, &sigset as *const sigset_t, ptr::null_mut() as *mut sigset_t); + } +} + +#[cfg(target_os = "redox")] +fn mask_sigstp() { + // TODO +} diff --git a/src/shell/job_control.rs b/src/shell/job_control.rs index 0680c1a9..dde18351 100644 --- a/src/shell/job_control.rs +++ b/src/shell/job_control.rs @@ -14,7 +14,8 @@ pub trait JobControl { fn handle_signal(&self, signal: i32); fn foreground_send(&self, signal: i32); fn background_send(&self, signal: i32); - fn send_child_to_background(&mut self, child: u32, state: ProcessState); + fn watch_foreground(&mut self, pid: u32) -> i32; + fn send_to_background(&mut self, child: u32, state: ProcessState); } #[derive(Clone)] @@ -36,45 +37,48 @@ impl fmt::Display for ProcessState { } #[cfg(target_os = "redox")] -pub fn watch_pid(processes: Arc<Mutex<Vec<BackgroundProcess>>>, pid: u32, njob: usize) { +pub fn watch_background_pid(processes: Arc<Mutex<Vec<BackgroundProcess>>>, pid: u32, njob: usize) { // TODO: Implement this using syscall::call::waitpid } #[cfg(all(unix, not(target_os = "redox")))] -pub fn watch_pid ( +pub fn watch_background_pid ( processes: Arc<Mutex<Vec<BackgroundProcess>>>, pid: u32, njob: usize) { - use nix::sys::wait::{waitpid, WaitStatus}; + use nix::sys::wait::{waitpid, WaitStatus, WUNTRACED, WNOHANG}; loop { - match waitpid(pid as pid_t, None) { + match waitpid(-(pid as pid_t), Some(WUNTRACED | WNOHANG)) { Ok(WaitStatus::Exited(_, status)) => { - eprintln!("ion: background process ([{}] {}) exited with {}", njob, pid, status); + eprintln!("ion: ([{}] {}) exited with {}", njob, pid, status); let mut processes = processes.lock().unwrap(); let process = &mut processes.iter_mut().nth(njob).unwrap(); process.state = ProcessState::Empty; break }, - Ok(WaitStatus::Stopped(_, _)) => { + Ok(WaitStatus::Stopped(pid, _)) => { + eprintln!("ion: ([{}] {}) Stopped", njob, pid); let mut processes = processes.lock().unwrap(); let process = &mut processes.iter_mut().nth(njob).unwrap(); process.state = ProcessState::Stopped; }, - Ok(WaitStatus::Continued(_)) => { + Ok(WaitStatus::Continued(pid)) => { + eprintln!("ion: ([{}] {}) Running", njob, pid); let mut processes = processes.lock().unwrap(); let process = &mut processes.iter_mut().nth(njob).unwrap(); process.state = ProcessState::Running; }, Ok(_) => (), Err(why) => { - eprintln!("ion: background process ([{}] {}) errored: {}", njob, pid, why); + eprintln!("ion: ([{}] {}) errored: {}", njob, pid, why); let mut processes = processes.lock().unwrap(); let process = &mut processes.iter_mut().nth(njob).unwrap(); process.state = ProcessState::Empty; break } } + sleep(Duration::from_millis(100)); } } @@ -121,7 +125,7 @@ impl<'a> JobControl for Shell<'a> { #[cfg(all(unix, not(target_os = "redox")))] /// Suspends a given process by it's process ID. fn suspend(&mut self, pid: u32) { - let _ = signal::kill(pid as pid_t, Some(NixSignal::SIGTSTP)); + let _ = signal::kill(-(pid as pid_t), Some(NixSignal::SIGTSTP)); } #[cfg(target_os = "redox")] @@ -155,11 +159,71 @@ impl<'a> JobControl for Shell<'a> { // TODO: Redox doesn't support signals yet. } + #[cfg(all(unix, not(target_os = "redox")))] + fn watch_foreground(&mut self, pid: u32) -> i32 { + use nix::sys::wait::{waitpid, WaitStatus, WUNTRACED, WNOHANG}; + use nix::sys::signal::Signal; + loop { + match waitpid(-(pid as pid_t), Some(WUNTRACED | WNOHANG)) { + Ok(WaitStatus::Exited(_, status)) => { + break status as i32 + }, + Ok(WaitStatus::Signaled(_, signal, _)) => { + eprintln!("ion: process ended by signal"); + if signal == Signal::SIGTERM { + self.handle_signal(libc::SIGTERM); + } else if signal == Signal::SIGINT { + self.foreground_send(libc::SIGINT as i32); + } + break TERMINATED; + }, + Ok(WaitStatus::Stopped(pid, _)) => { + self.send_to_background(pid as u32, ProcessState::Stopped); + self.received_sigtstp = true; + break TERMINATED + }, + Ok(_) => (), + Err(why) => { + eprintln!("ion: process doesn't exist: {}", why); + break FAILURE + } + } + sleep(Duration::from_millis(1)); + } + } + + #[cfg(target_os = "redox")] + fn watch_foreground(&mut self, pid: u32) -> i32 { + loop { + match child.try_wait() { + Ok(Some(status)) => { + if let Some(code) = status.code() { + break code + } else { + let stderr = io::stderr(); + let mut stderr = stderr.lock(); + let _ = stderr.write_all(b"ion: child ended by signal\n"); + break TERMINATED + } + }, + Ok(None) => { + thread::sleep(Duration::from_millis(1)); + }, + Err(err) => { + let stderr = io::stderr(); + let mut stderr = stderr.lock(); + let _ = writeln!(stderr, "ion: failed to wait: {}", err); + break 100 // TODO what should we return here? + } + } + } + } + #[cfg(all(unix, not(target_os = "redox")))] /// Send a kill signal to all running foreground tasks. fn foreground_send(&self, signal: i32) { for process in self.foreground.iter() { - let _ = signal::kill(*process as pid_t, NixSignal::from_c_int(signal as c_int).ok()); + let _ = signal::kill(-(*process as pid_t), NixSignal::from_c_int(signal as c_int).ok()); } } @@ -173,7 +237,7 @@ impl<'a> JobControl for Shell<'a> { fn background_send(&self, signal: i32) { for process in self.background.lock().unwrap().iter() { if let ProcessState::Running = process.state { - let _ = signal::kill(process.pid as pid_t, NixSignal::from_c_int(signal as c_int).ok()); + let _ = signal::kill(-(process.pid as pid_t), NixSignal::from_c_int(signal as c_int).ok()); } } } @@ -183,12 +247,12 @@ impl<'a> JobControl for Shell<'a> { // TODO: Redox doesn't support signals yet } - fn send_child_to_background(&mut self, pid: u32, state: ProcessState) { + fn send_to_background(&mut self, pid: u32, state: ProcessState) { let processes = self.background.clone(); let _ = spawn(move || { let njob = add_to_background(processes.clone(), pid, state); eprintln!("ion: bg [{}] {}", njob, pid); - watch_pid(processes, pid, njob); + watch_background_pid(processes, pid, njob); }); } diff --git a/src/shell/pipe.rs b/src/shell/pipe.rs index c3e6636d..cc4e2aa5 100644 --- a/src/shell/pipe.rs +++ b/src/shell/pipe.rs @@ -1,19 +1,100 @@ #[cfg(all(unix, not(target_os = "redox")))] use libc; -#[cfg(all(unix, not(target_os = "redox")))] use nix::unistd::{fork, ForkResult}; +#[cfg(all(unix, not(target_os = "redox")))] use nix::unistd::{self, ForkResult}; #[cfg(all(unix, not(target_os = "redox")))] use nix::Error as NixError; #[cfg(target_os = "redox")] use syscall; use std::io::{self, Write}; use std::process::{Stdio, Command, Child}; use std::os::unix::io::{FromRawFd, AsRawFd, IntoRawFd}; +use std::os::unix::process::CommandExt; use std::fs::{File, OpenOptions}; use std::process::exit; -use std::thread; -use std::time::Duration; use super::job_control::{JobControl, ProcessState}; use super::{JobKind, Shell}; use super::status::*; use parser::peg::{Pipeline, RedirectFrom}; +/// The purpose of the signal handler is to ignore signals when it is active, and then continue +/// listening to signals once the handler is dropped. +struct SignalHandler; + +impl SignalHandler { + #[cfg(all(unix, not(target_os = "redox")))] + pub fn new() -> SignalHandler { + unsafe { let _ = libc::signal(libc::SIGTTOU, libc::SIG_IGN); } + SignalHandler + } + + #[cfg(target_os = "redox")] + pub fn new() -> SignalHandler { + // TODO + SignalHandler + } +} + +impl Drop for SignalHandler { + #[cfg(all(unix, not(target_os = "redox")))] + fn drop(&mut self) { + unsafe { let _ = libc::signal(libc::SIGTTOU, libc::SIG_DFL); } + } + + #[cfg(target_os = "redox")] + fn drop(&mut self) { + // TODO + } +} + +#[cfg(all(unix, not(target_os = "redox")))] +fn unmask_sigtstp() { + unsafe { + use libc::{sigset_t, SIG_UNBLOCK, SIGTSTP, sigemptyset, sigaddset, sigprocmask}; + use std::mem; + use std::ptr; + let mut sigset = mem::uninitialized::<sigset_t>(); + sigemptyset(&mut sigset as *mut sigset_t); + sigaddset(&mut sigset as *mut sigset_t, SIGTSTP); + sigprocmask(SIG_UNBLOCK, &sigset as *const sigset_t, ptr::null_mut() as *mut sigset_t); + } +} + +#[cfg(target_os = "redox")] +fn unmask_sigtstp() { + // TODO +} + +#[cfg(all(unix, not(target_os = "redox")))] +/// When given a process ID, that process will be assigned to a new process group. +fn create_process_group() { + let _ = unistd::setpgid(0, 0); +} + +#[cfg(target_os = "redox")] +fn create_process_group() { + // TODO +} + +#[cfg(all(unix, not(target_os = "redox")))] +/// When given a process ID, that process's group will be assigned as the foreground process group. +pub fn set_foreground(pid: u32) { + let _ = unistd::tcsetpgrp(0, pid as i32); + let _ = unistd::tcsetpgrp(1, pid as i32); + let _ = unistd::tcsetpgrp(2, pid as i32); +} + +#[cfg(target_os = "redox")] +pub fn set_foreground(pid: u32) { + // TODO +} + +#[cfg(all(unix, not(target_os = "redox")))] +fn get_pid() -> u32 { + unistd::getpid() as u32 +} + +#[cfg(target_os = "redox")] +fn get_pid() -> u32 { + // TODO +} + pub trait PipelineExecution { fn execute_pipeline(&mut self, pipeline: &mut Pipeline) -> i32; } @@ -72,10 +153,19 @@ impl<'a> PipelineExecution for Shell<'a> { } self.foreground.clear(); + // If the given pipeline is a background task, fork the shell. if piped_commands[piped_commands.len()-1].1 == JobKind::Background { fork_pipe(self, &mut piped_commands) } else { - pipe(self, &mut piped_commands) + // While active, the SIGTTOU signal will be ignored. + let sig_ignore = SignalHandler::new(); + // Execute each command in the pipeline, giving each command the foreground. + let exit_status = pipe(self, &mut piped_commands, true); + // Set the shell as the foreground process again to regain the TTY. + set_foreground(get_pid()); + // Dropping this will un-ignore the SIGTTOU signal. + drop(sig_ignore); + exit_status } } } @@ -97,30 +187,32 @@ fn ion_fork() -> syscall::error::Result<Fork> { #[cfg(all(unix, not(target_os = "redox")))] fn ion_fork() -> Result<Fork, NixError> { - match fork()? { - ForkResult::Parent{ child: pid } => Ok(Fork::Parent(pid as u32)), - ForkResult::Child => Ok(Fork::Child) + match unistd::fork()? { + ForkResult::Parent{ child: pid } => Ok(Fork::Parent(pid as u32)), + ForkResult::Child => Ok(Fork::Child) } } fn fork_pipe(shell: &mut Shell, commands: &mut [(Command, JobKind)]) -> i32 { match ion_fork() { Ok(Fork::Parent(pid)) => { - shell.send_child_to_background(pid, ProcessState::Running); + shell.send_to_background(pid, ProcessState::Running); SUCCESS }, Ok(Fork::Child) => { - exit(pipe(shell, commands)); + unmask_sigtstp(); + create_process_group(); + exit(pipe(shell, commands, false)); }, Err(why) => { - eprintln!("ion: background job: {}", why); + eprintln!("ion: background fork failed: {}", why); FAILURE } } } /// This function will panic if called with an empty slice -fn pipe(shell: &mut Shell, commands: &mut [(Command, JobKind)]) -> i32 { +fn pipe(shell: &mut Shell, commands: &mut [(Command, JobKind)], foreground: bool) -> i32 { let mut previous_status = SUCCESS; let mut previous_kind = JobKind::And; let mut commands = commands.iter_mut(); @@ -148,9 +240,14 @@ fn pipe(shell: &mut Shell, commands: &mut [(Command, JobKind)]) -> i32 { RedirectFrom::Stdout => command.stdout(Stdio::piped()), }; - let child = command.spawn().ok(); + let child = command.before_exec(move || { + unmask_sigtstp(); + create_process_group(); + Ok(()) + }).spawn().ok(); match child { Some(child) => { + if foreground { set_foreground(child.id()); } shell.foreground.push(child.id()); children.push(Some(child)) }, @@ -191,9 +288,14 @@ fn pipe(shell: &mut Shell, commands: &mut [(Command, JobKind)]) -> i32 { command.stdin(Stdio::null()); } } - let child = command.spawn().ok(); + let child = command.before_exec(move || { + unmask_sigtstp(); + create_process_group(); + Ok(()) + }).spawn().ok(); match child { Some(child) => { + if foreground { set_foreground(child.id()); } shell.foreground.push(child.id()); children.push(Some(child)); }, @@ -220,7 +322,7 @@ fn pipe(shell: &mut Shell, commands: &mut [(Command, JobKind)]) -> i32 { } } _ => { - previous_status = execute_command(shell, command); + previous_status = execute_command(shell, command, foreground); previous_kind = kind; } } @@ -239,20 +341,13 @@ fn terminate_fg(shell: &mut Shell) { // TODO: Redox does not support signals } -#[cfg(all(unix, not(target_os = "redox")))] -fn is_sigtstp(signal: i32) -> bool { - signal == libc::SIGTSTP -} - -#[cfg(target_os = "redox")] -fn is_sigtstp(_: i32) -> bool { - // TODO: Redox does not support signals - false -} - -fn execute_command(shell: &mut Shell, command: &mut Command) -> i32 { - match command.spawn() { - Ok(child) => wait_on_child(shell, child), +fn execute_command(shell: &mut Shell, command: &mut Command, foreground: bool) -> i32 { + match command.before_exec(move || { + unmask_sigtstp(); + create_process_group(); + Ok(()) + }).spawn() { + Ok(child) => wait_on_child(shell, child, foreground), Err(_) => { let stderr = io::stderr(); let mut stderr = stderr.lock(); @@ -262,131 +357,25 @@ fn execute_command(shell: &mut Shell, command: &mut Command) -> i32 { } } -fn wait_on_child(shell: &mut Shell, mut child: Child) -> i32 { - loop { - match child.try_wait() { - Ok(Some(status)) => { - if let Some(code) = status.code() { - break code - } else { - let stderr = io::stderr(); - let mut stderr = stderr.lock(); - let _ = stderr.write_all(b"ion: child ended by signal\n"); - break TERMINATED - } - }, - Ok(None) => { - if let Ok(signal) = shell.signals.try_recv() { - if is_sigtstp(signal) { - shell.received_sigtstp = true; - let pid = child.id(); - shell.suspend(pid); - shell.send_child_to_background(pid, ProcessState::Stopped); - break SUCCESS - } else { - if let Err(why) = child.kill() { - let stderr = io::stderr(); - let _ = writeln!(stderr.lock(), "ion: unable to kill child: {}", why); - } - shell.foreground_send(signal); - shell.handle_signal(signal); - break TERMINATED - } - } - thread::sleep(Duration::from_millis(1)); - }, - Err(err) => { - let stderr = io::stderr(); - let mut stderr = stderr.lock(); - let _ = writeln!(stderr, "ion: failed to wait: {}", err); - break 100 // TODO what should we return here? - } - } - } +fn wait_on_child(shell: &mut Shell, child: Child, foreground: bool) -> i32 { + if foreground { set_foreground(child.id()); } + shell.watch_foreground(child.id()) } /// This function will panic if called with an empty vector fn wait(shell: &mut Shell, children: &mut Vec<Option<Child>>) -> i32 { let end = children.len() - 1; for child in children.drain(..end) { - if let Some(mut child) = child { - let status = loop { - match child.try_wait() { - Ok(Some(status)) => { - if let Some(code) = status.code() { - break code - } else { - let stderr = io::stderr(); - let mut stderr = stderr.lock(); - let _ = stderr.write_all(b"ion: child ended by signal\n"); - break TERMINATED - } - }, - Ok(None) => { - if let Ok(signal) = shell.signals.try_recv() { - if is_sigtstp(signal) { - shell.received_sigtstp = true; - let pid = child.id(); - shell.suspend(pid); - shell.send_child_to_background(pid, ProcessState::Stopped); - break SUCCESS - } - shell.foreground_send(signal); - shell.handle_signal(signal); - break TERMINATED - } - thread::sleep(Duration::from_millis(1)); - }, - Err(err) => { - let stderr = io::stderr(); - let mut stderr = stderr.lock(); - let _ = writeln!(stderr, "ion: failed to wait: {}", err); - break 100 // TODO what should we return here? - } - } - }; + if let Some(child) = child { + let status = shell.watch_foreground(child.id()); if status == TERMINATED { return status } } } - if let Some(mut child) = children.pop().unwrap() { - loop { - match child.try_wait() { - Ok(Some(status)) => { - if let Some(code) = status.code() { - break code - } else { - let stderr = io::stderr(); - let mut stderr = stderr.lock(); - let _ = stderr.write_all(b"ion: child ended by signal\n"); - break TERMINATED - } - }, - Ok(None) => { - if let Ok(signal) = shell.signals.try_recv() { - if is_sigtstp(signal) { - shell.received_sigtstp = true; - let pid = child.id(); - shell.suspend(pid); - shell.send_child_to_background(pid, ProcessState::Stopped); - break SUCCESS - } - shell.foreground_send(signal); - shell.handle_signal(signal); - break TERMINATED - } - thread::sleep(Duration::from_millis(1)); - }, - Err(err) => { - let stderr = io::stderr(); - let mut stderr = stderr.lock(); - let _ = writeln!(stderr, "ion: failed to wait: {}", err); - break 100 // TODO what should we return here? - } - } - } + if let Some(child) = children.pop().unwrap() { + shell.watch_foreground(child.id()) } else { NO_SUCH_COMMAND } -- GitLab