diff --git a/Cargo.lock b/Cargo.lock index 390f3c1b8..6c63154bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,11 +9,12 @@ 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)", + "os_pipe 0.5.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 +66,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 +114,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 +167,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 +180,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)", ] @@ -193,6 +194,16 @@ dependencies = [ "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "os_pipe" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "nix 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "peg" version = "0.5.4" @@ -213,7 +224,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 +370,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 +378,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" @@ -375,10 +386,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum net2 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)" = "bc01404e7568680f1259aa5729539f221cb1e6d047a0d9053cab4be8a73b5d67" "checksum nix 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "47e49f6982987135c5e9620ab317623e723bd06738fd85377e8d55f57c8b6487" "checksum ole32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2c49021782e5233cd243168edfa8037574afed4eba4bbaf538b3d8d1789d8c" +"checksum os_pipe 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "998bfbb3042e715190fe2a41abfa047d7e8cb81374d2977d7f100eacd8619cb1" "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 9a42c00b3..4117d71fa 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 c3e6636df..b53756650 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::fs::{File, OpenOptions}; use std::process::exit; use std::thread; @@ -14,6 +14,102 @@ use super::{JobKind, Shell}; use super::status::*; use parser::peg::{Pipeline, RedirectFrom}; +/// The `crossplat` module contains components that are meant to be abstracted across +/// different platforms +#[cfg(not(target_os = "redox"))] +mod crossplat { + 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}; + + /// 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")] +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; + + #[derive(Debug)] + pub enum Error { + Io(io::Error), + Sys(syscall::Error) + } + + impl From for Error { + fn from(data: io::Error) -> Error { Error::Io(data) } + } + + impl From 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 { fn execute_pipeline(&mut self, pipeline: &mut Pipeline) -> i32; } @@ -73,9 +169,9 @@ impl<'a> PipelineExecution for Shell<'a> { self.foreground.clear(); if piped_commands[piped_commands.len()-1].1 == JobKind::Background { - fork_pipe(self, &mut piped_commands) + fork_pipe(self, piped_commands) } else { - pipe(self, &mut piped_commands) + pipe(self, piped_commands) } } } @@ -103,7 +199,7 @@ fn ion_fork() -> Result { } } -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_child_to_background(pid, ProcessState::Running); @@ -120,112 +216,94 @@ 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)]) -> i32 { +fn pipe(shell: &mut Shell, commands: Vec<(Command, JobKind)>) -> 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> = Vec::new(); + 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 + }, + _ => () + } - // Initialize the first job - let _ = match from { - RedirectFrom::Both | RedirectFrom::Stderr => command.stderr(Stdio::piped()), // TODO: Fix this - RedirectFrom::Stdout => command.stdout(Stdio::piped()), - }; + match kind { + JobKind::Pipe(mut mode) => { - let child = command.spawn().ok(); - match child { - Some(child) => { - 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)); - } - } + // 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> = Vec::new(); - // 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())); - } + macro_rules! spawn_proc { + ($cmd:expr) => {{ + let child = $cmd.spawn(); + match child { + Ok(child) => { + 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.spawn().ok(); - match child { - Some(child) => { - 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); + previous_kind = kind; } } - _ => { - previous_status = execute_command(shell, command); - previous_kind = kind; - } + } else { + break } } - previous_status } @@ -306,10 +384,12 @@ fn wait_on_child(shell: &mut Shell, mut child: Child) -> i32 { } /// This function will panic if called with an empty vector -fn wait(shell: &mut Shell, children: &mut Vec>) -> i32 { +fn wait(shell: &mut Shell, children: &mut Vec>, commands: Vec) -> i32 { let end = children.len() - 1; - for child in children.drain(..end) { - if let Some(mut 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(mut child), _cmd) = entry { let status = loop { match child.try_wait() { Ok(Some(status)) => {