diff --git a/Cargo.lock b/Cargo.lock index 390f3c1b883727a8c71501d4e425355f615b9399..aa81d891b2da92cb235e2899202fddf543ab2c98 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,11 +9,11 @@ dependencies = [ "futures 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)", - "liner 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "liner 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "nix 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "peg 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", "permutate 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)", "smallstring 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "smallvec 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-core 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", @@ -65,7 +65,7 @@ dependencies = [ [[package]] name = "cfg-if" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -113,7 +113,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "liner" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "termion 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -166,7 +166,7 @@ name = "net2" version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cfg-if 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", @@ -179,7 +179,7 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)", "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -213,7 +213,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "redox_syscall" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -359,7 +359,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" "checksum byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c40977b0ee6b9885c9013cd41d9feffdd22deb3bb4dc3a71d901cc7a77de18c8" "checksum bytes 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8b24f16593f445422331a5eed46b72f7f171f910fead4f2ea8f17e727e9c5c14" -"checksum cfg-if 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d0c47d456a36ebf0536a6705c83c1cbbcb9255fbc1d905a6ded104f479268a29" +"checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de" "checksum fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6cc484842f1e2884faf56f529f960cc12ad8c71ce96cc7abba0a067c98fee344" "checksum futures 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "4b63a4792d4f8f686defe3b39b92127fea6344de5d38202b2ee5a11bbbf29d6a" "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" @@ -367,7 +367,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum lazycell 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce12306c4739d86ee97c23139f3a34ddf0387bbf181bc7929d287025a8c3ef6b" "checksum libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)" = "38f5c2b18a287cf78b4097db62e20f43cace381dc76ae5c0a3073067f78b7ddc" -"checksum liner 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b671c4f2d2f7bfb49c4231220d51f14482adf31dca8c936a037b0a7d40b79d04" +"checksum liner 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1f5c044840b473e0c1a29a05a3b82beeadb688f9cfdaadc31955a5dc90149a39" "checksum log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "880f77541efa6e5cc74e76910c9884d9859683118839d6a1dc3b11e63512565b" "checksum mio 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)" = "9e965267d4d58496fc4f740e9861118367f13570cadf66316ed2c3f2f14d87c7" "checksum mio-uds 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1731a873077147b626d89cc6c2a0db6288d607496c5d10c0cfcf3adc697ec673" @@ -378,7 +378,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum peg 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "36a474cba42744afe0f223e9d4263594b3387f172e512259c72d2011e477c4fb" "checksum permutate 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53b7d5b19a715ffab38693a9dd44b067fdfa2b18eef65bd93562dfe507022fae" "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" -"checksum redox_syscall 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)" = "e4a357d14a12e90a37d658725df0e6468504750b5948b9710f83f94a0c5818e8" +"checksum redox_syscall 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)" = "1eb6b797b89e9c92681e837851e906e9788c748391deaba7f5b66f264e390249" "checksum scoped-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f417c22df063e9450888a7561788e9bd46d3bb3c1466435b4eccb903807f147d" "checksum shell32-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "72f20b8f3c060374edb8046591ba28f62448c369ccbdc7b02075103fb3a9e38d" "checksum slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "17b4fcaed89ab08ef143da37bc52adbcc04d4a69014f4c1208d6b51f0c47bc23" diff --git a/src/parser/pipelines.rs b/src/parser/pipelines.rs index 9a42c00b31bb2458c85eae9d68cbd39ccd9ba2d8..4117d71fa33e0909fe4b562c36219bea10d03c5c 100644 --- a/src/parser/pipelines.rs +++ b/src/parser/pipelines.rs @@ -188,6 +188,10 @@ pub fn collect(possible_error: &mut Option<&str>, args: &str) -> Pipeline { let _ = args_iter.next(); redir_found!(RedirMode::Stdout(RedirectFrom::Both)); }, + Some(&b'|') => { + let _ = args_iter.next(); + job_found!(RedirectFrom::Both, true); + }, _ => job_found!(RedirectFrom::Stdout, false) } }, diff --git a/src/shell/pipe.rs b/src/shell/pipe.rs index cc4e2aa528d5bc1e49f404e9cd4a04f15ddc9935..7f58c8b82b8aa14f4ec7fe334aa407627586f140 100644 --- a/src/shell/pipe.rs +++ b/src/shell/pipe.rs @@ -4,7 +4,7 @@ #[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::io::{FromRawFd, IntoRawFd}; use std::os::unix::process::CommandExt; use std::fs::{File, OpenOptions}; use std::process::exit; @@ -13,86 +13,181 @@ 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; +use self::crossplat::*; + +/// The `crossplat` module contains components that are meant to be abstracted across +/// different platforms +#[cfg(not(target_os = "redox"))] +mod crossplat { + use libc; + use nix::{fcntl, unistd}; + use parser::peg::{RedirectFrom}; + use std::fs::File; + use std::io::Error; + use std::os::unix::io::{IntoRawFd, FromRawFd}; + use std::process::{Stdio, Command}; + + /// 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. + pub struct SignalHandler; + + impl SignalHandler { + pub fn new() -> SignalHandler { + unsafe { let _ = libc::signal(libc::SIGTTOU, libc::SIG_IGN); } + SignalHandler + } + } -impl SignalHandler { - #[cfg(all(unix, not(target_os = "redox")))] - pub fn new() -> SignalHandler { - unsafe { let _ = libc::signal(libc::SIGTTOU, libc::SIG_IGN); } - SignalHandler + impl Drop for SignalHandler { + fn drop(&mut self) { + unsafe { let _ = libc::signal(libc::SIGTTOU, libc::SIG_DFL); } + } } - #[cfg(target_os = "redox")] - pub fn new() -> SignalHandler { - // TODO - SignalHandler + pub 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); + } } -} -impl Drop for SignalHandler { - #[cfg(all(unix, not(target_os = "redox")))] - fn drop(&mut self) { - unsafe { let _ = libc::signal(libc::SIGTTOU, libc::SIG_DFL); } + /// When given a process ID, that process will be assigned to a new process group. + pub fn create_process_group() { + let _ = unistd::setpgid(0, 0); } - #[cfg(target_os = "redox")] - fn drop(&mut self) { - // TODO + /// 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(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); + pub fn get_pid() -> u32 { + unistd::getpid() as u32 + } + + /// Set up pipes such that the relevant output of parent is sent to the stdin of child. + /// The content that is sent depends on `mode` + pub unsafe fn create_pipe(parent: &mut Command, + child: &mut Command, + mode: RedirectFrom) -> Result<(), Error> + { + let (reader, writer) = unistd::pipe2(fcntl::O_CLOEXEC)?; + match mode { + RedirectFrom::Stdout => { + parent.stdout(Stdio::from_raw_fd(writer)); + }, + RedirectFrom::Stderr => { + parent.stderr(Stdio::from_raw_fd(writer)); + }, + RedirectFrom::Both => { + let temp_file = File::from_raw_fd(writer); + let clone = temp_file.try_clone()?; + // We want to make sure that the temp file we created no longer has ownership + // over the raw file descriptor otherwise it gets closed + temp_file.into_raw_fd(); + parent.stdout(Stdio::from_raw_fd(writer)); + parent.stderr(Stdio::from_raw_fd(clone.into_raw_fd())); + } + } + child.stdin(Stdio::from_raw_fd(reader)); + Ok(()) } } #[cfg(target_os = "redox")] -fn unmask_sigtstp() { - // TODO -} +mod crossplat { + use parser::peg::{RedirectFrom}; + use std::fs::File; + use std::io; + use std::os::unix::io::{IntoRawFd, FromRawFd}; + use std::process::{Stdio, Command}; + use syscall; + + /// 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. + pub struct SignalHandler; + + impl SignalHandler { + pub fn new() -> SignalHandler { + // TODO + SignalHandler + } + } -#[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); -} + impl Drop for SignalHandler { + fn drop(&mut self) { + // TODO + } + } -#[cfg(target_os = "redox")] -fn create_process_group() { - // TODO -} + pub fn unmask_sigtstp() { + // 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); -} + pub fn create_process_group() { + // TODO + } -#[cfg(target_os = "redox")] -pub fn set_foreground(pid: u32) { - // TODO -} + pub fn set_foreground(pid: u32) { + // TODO + } -#[cfg(all(unix, not(target_os = "redox")))] -fn get_pid() -> u32 { - unistd::getpid() as u32 -} + pub fn get_pid() -> u32 { + // TODO + } -#[cfg(target_os = "redox")] -fn get_pid() -> u32 { - // TODO + #[derive(Debug)] + pub enum Error { + Io(io::Error), + Sys(syscall::Error) + } + + impl From<io::Error> for Error { + fn from(data: io::Error) -> Error { Error::Io(data) } + } + + impl From<syscall::Error> for Error { + fn from(data: syscall::Error) -> Error { Error::Sys(data) } + } + + /// Set up pipes such that the relevant output of parent is sent to the stdin of child. + /// The content that is sent depends on `mode` + pub unsafe fn create_pipe(parent: &mut Command, + child: &mut Command, + mode: RedirectFrom) -> Result<(), Error> + { + // XXX: Zero probably is a bad default for this, but `pipe2` will error if it fails, so + // one could reason that it isn't dangerous. + let mut fds: [usize; 2] = [0; 2]; + syscall::call::pipe2(&mut fds, syscall::flag::O_CLOEXEC)?; + let (reader, writer) = (fds[0], fds[1]); + match mode { + RedirectFrom::Stdout => { + parent.stdout(Stdio::from_raw_fd(writer)); + }, + RedirectFrom::Stderr => { + parent.stderr(Stdio::from_raw_fd(writer)); + }, + RedirectFrom::Both => { + let temp_file = File::from_raw_fd(writer); + let clone = temp_file.try_clone()?; + // We want to make sure that the temp file we created no longer has ownership + // over the raw file descriptor otherwise it gets closed + temp_file.into_raw_fd(); + parent.stdout(Stdio::from_raw_fd(writer)); + parent.stderr(Stdio::from_raw_fd(clone.into_raw_fd())); + } + } + child.stdin(Stdio::from_raw_fd(reader)); + Ok(()) + } } pub trait PipelineExecution { @@ -155,16 +250,14 @@ 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) + fork_pipe(self, piped_commands) } else { // While active, the SIGTTOU signal will be ignored. - let sig_ignore = SignalHandler::new(); + 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); + let exit_status = pipe(self, 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 } } @@ -193,7 +286,7 @@ fn ion_fork() -> Result<Fork, NixError> { } } -fn fork_pipe(shell: &mut Shell, commands: &mut [(Command, JobKind)]) -> i32 { +fn fork_pipe(shell: &mut Shell, commands: Vec<(Command, JobKind)>) -> i32 { match ion_fork() { Ok(Fork::Parent(pid)) => { shell.send_to_background(pid, ProcessState::Running); @@ -212,122 +305,103 @@ fn fork_pipe(shell: &mut Shell, commands: &mut [(Command, JobKind)]) -> i32 { } /// This function will panic if called with an empty slice -fn pipe(shell: &mut Shell, commands: &mut [(Command, JobKind)], foreground: bool) -> i32 { +fn pipe ( + shell: &mut Shell, + commands: Vec<(Command, JobKind)>, + foreground: bool +) -> i32 { let mut previous_status = SUCCESS; let mut previous_kind = JobKind::And; - let mut commands = commands.iter_mut(); - while let Some(&mut (ref mut command, kind)) = commands.next() { - // When an `&&` or `||` operator is utilized, execute commands based on the previous status. - match previous_kind { - JobKind::And => if previous_status != SUCCESS { - if let JobKind::Or = kind { previous_kind = kind } - continue - }, - JobKind::Or => if previous_status == SUCCESS { - if let JobKind::And = kind { previous_kind = kind } - continue - }, - _ => () - } - - match kind { - JobKind::Pipe(mut from) => { - let mut children: Vec<Option<Child>> = Vec::new(); - - // Initialize the first job - let _ = match from { - RedirectFrom::Both | RedirectFrom::Stderr => command.stderr(Stdio::piped()), // TODO: Fix this - RedirectFrom::Stdout => command.stdout(Stdio::piped()), - }; - - 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)) - }, - None => { - children.push(None); - let stderr = io::stderr(); - let mut stderr = stderr.lock(); - let _ = writeln!(stderr, "ion: command not found: {}", get_command_name(command)); - } - } + let mut commands = commands.into_iter(); + loop { + if let Some((mut parent, mut kind)) = commands.next() { + // When an `&&` or `||` operator is utilized, execute commands based on the previous status. + match previous_kind { + JobKind::And => if previous_status != SUCCESS { + if let JobKind::Or = kind { previous_kind = kind } + commands.next(); + continue + }, + JobKind::Or => if previous_status == SUCCESS { + if let JobKind::And = kind { previous_kind = kind } + commands.next(); + continue + }, + _ => () + } - // Append other jobs until all piped jobs are running. - while let Some(&mut (ref mut command, kind)) = commands.next() { - if let JobKind::Pipe(from) = kind { - let _ = match from { - RedirectFrom::Both | RedirectFrom::Stderr => command.stderr(Stdio::piped()), // TODO: Fix this - RedirectFrom::Stdout => command.stdout(Stdio::piped()), - }; - } - if let Some(spawned) = children.last() { - if let Some(ref child) = *spawned { - unsafe { - match from { - // TODO: Find a way to properly implement this. - RedirectFrom::Both => if let Some(ref stderr) = child.stderr { - command.stdin(Stdio::from_raw_fd(stderr.as_raw_fd())); - }, - RedirectFrom::Stderr => if let Some(ref stderr) = child.stderr { - command.stdin(Stdio::from_raw_fd(stderr.as_raw_fd())); - }, - RedirectFrom::Stdout => if let Some(ref stdout) = child.stdout { - command.stdin(Stdio::from_raw_fd(stdout.as_raw_fd())); - } + match kind { + JobKind::Pipe(mut mode) => { + + // We need to remember the commands as they own the file descriptors that are + // created by crossplat::create_pipe. We purposfully drop the pipes that are + // owned by a given command in `wait` in order to close those pipes, sending + // EOF to the next command + let mut remember = Vec::new(); + let mut children: Vec<Option<Child>> = Vec::new(); + + macro_rules! spawn_proc { + ($cmd:expr) => {{ + let child = $cmd.before_exec(move || { + unmask_sigtstp(); + create_process_group(); + Ok(()) + }).spawn(); + match child { + Ok(child) => { + if foreground { set_foreground(child.id()); } + shell.foreground.push(child.id()); + children.push(Some(child)) + }, + Err(e) => { + children.push(None); + eprintln!("ion: failed to spawn `{}`: {}", + get_command_name($cmd), + e); } } - } else { - // The previous command failed to spawn - command.stdin(Stdio::null()); - } + }}; } - 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)); - }, - None => { - children.push(None); - let stderr = io::stderr(); - let mut stderr = stderr.lock(); - let _ = writeln!(stderr, "ion: command not found: {}", get_command_name(command)); + + // Append other jobs until all piped jobs are running + while let Some((mut child, ckind)) = commands.next() { + if let Err(e) = unsafe { + crossplat::create_pipe(&mut parent, &mut child, mode) + } { + eprintln!("ion: failed to create pipe for redirection: {:?}", e); + } + spawn_proc!(&mut parent); + remember.push(parent); + if let JobKind::Pipe(m) = ckind { + parent = child; + mode = m; + } else { + // We set the kind to the last child kind that was processed. For + // example, the pipeline `foo | bar | baz && zardoz` should have the + // previous kind set to `And` after processing the initial pipeline + kind = ckind; + spawn_proc!(&mut child); + remember.push(child); + break } } - if let JobKind::Pipe(next) = kind { - from = next; - continue - } else { - previous_kind = kind; - break + previous_kind = kind; + previous_status = wait(shell, &mut children, remember); + if previous_status == TERMINATED { + terminate_fg(shell); + return previous_status; } } - previous_status = wait(shell, &mut children); - if previous_status == TERMINATED { - terminate_fg(shell); - return previous_status; + _ => { + previous_status = execute_command(shell, &mut parent, foreground); + previous_kind = kind; } } - _ => { - previous_status = execute_command(shell, command, foreground); - previous_kind = kind; - } + } else { + break } } - previous_status } @@ -363,10 +437,12 @@ fn wait_on_child(shell: &mut Shell, child: Child, foreground: bool) -> i32 { } /// This function will panic if called with an empty vector -fn wait(shell: &mut Shell, children: &mut Vec<Option<Child>>) -> i32 { +fn wait(shell: &mut Shell, children: &mut Vec<Option<Child>>, commands: Vec<Command>) -> i32 { let end = children.len() - 1; - for child in children.drain(..end) { - if let Some(child) = child { + for entry in children.drain(..end).zip(commands.into_iter()) { + // _cmd is never used here, but it is important that it gets dropped at the end of this + // block in order to write EOF to the pipes that it owns. + if let (Some(child), _cmd) = entry { let status = shell.watch_foreground(child.id()); if status == TERMINATED { return status