diff --git a/src/lib/shell/job.rs b/src/lib/shell/job.rs index 1a7fe26540163ff63b9bafbd79c962200c66dec7..e609af43b10467c11a2dc00cb0ecde47d567a701 100644 --- a/src/lib/shell/job.rs +++ b/src/lib/shell/job.rs @@ -71,43 +71,36 @@ fn expand_arg(arg: &str, shell: &Shell) -> types::Array { /// This represents a job that has been processed and expanded to be run /// as part of some pipeline -pub(crate) enum RefinedJob { +pub struct RefinedJob { + pub stdin: Option<File>, + pub stdout: Option<File>, + pub stderr: Option<File>, + pub var: JobVariant +} + +pub enum JobVariant { /// An external program that is executed by this shell External { - name: types::Str, - args: types::Array, - stdin: Option<File>, - stdout: Option<File>, - stderr: Option<File>, + name: types::Str, + args: types::Array }, /// A procedure embedded into Ion Builtin { - main: BuiltinFunction, - args: types::Array, - stdin: Option<File>, - stdout: Option<File>, - stderr: Option<File>, + main: BuiltinFunction, + args: types::Array, }, /// Functions can act as commands too! Function { - name: types::Str, - args: types::Array, - stdin: Option<File>, - stdout: Option<File>, - stderr: Option<File>, + name: types::Str, + args: types::Array, }, /// Represents redirection into stdin from more than one source Cat { sources: Vec<File>, - stdin: Option<File>, - stdout: Option<File>, }, Tee { /// 0 for stdout, 1 for stderr items: (Option<TeeItem>, Option<TeeItem>), - stdin: Option<File>, - stdout: Option<File>, - stderr: Option<File>, }, } @@ -177,138 +170,113 @@ impl TeeItem { } } -macro_rules! set_field { - ($self:expr, $field:ident, $arg:expr) => { - match *$self { - RefinedJob::External { ref mut $field, .. } - | RefinedJob::Builtin { ref mut $field, .. } - | RefinedJob::Function { ref mut $field, .. } - | RefinedJob::Tee { ref mut $field, .. } => { - *$field = Some($arg); - } - // Do nothing for Cat - _ => {} - } - }; -} - impl RefinedJob { /// Returns a long description of this job: the commands and arguments pub(crate) fn long(&self) -> String { - match *self { - RefinedJob::External { ref args, .. } - | RefinedJob::Builtin { ref args, .. } - | RefinedJob::Function { ref args, .. } => args.join(" ").to_owned(), + match self.var { + JobVariant::External { ref args, .. } + | JobVariant::Builtin { ref args, .. } + | JobVariant::Function { ref args, .. } => args.join(" ").to_owned(), // TODO: Figure out real printing - RefinedJob::Cat { .. } | RefinedJob::Tee { .. } => "".into(), + JobVariant::Cat { .. } | JobVariant::Tee { .. } => "".into(), } } /// Returns a short description of this job: often just the command /// or builtin name pub(crate) fn short(&self) -> String { - match *self { - RefinedJob::Builtin { .. } => String::from("Shell Builtin"), - RefinedJob::Function { ref name, .. } | RefinedJob::External { ref name, .. } => { + match self.var { + JobVariant::Builtin { .. } => String::from("Shell Builtin"), + JobVariant::Function { ref name, .. } | JobVariant::External { ref name, .. } => { name.to_string() } // TODO: Print for real - RefinedJob::Cat { .. } => "multi-input".into(), - RefinedJob::Tee { .. } => "multi-output".into(), + JobVariant::Cat { .. } => "multi-input".into(), + JobVariant::Tee { .. } => "multi-output".into(), } } pub(crate) fn exec<S: PipelineExecution>(&self, shell: &mut S) -> i32 { - match *self { - RefinedJob::External { + let stdin = &self.stdin; + let stdout = &self.stdout; + let stderr = &self.stderr; + match self.var { + JobVariant::External { ref name, ref args, - ref stdin, - ref stdout, - ref stderr, } => shell.exec_external(&name, &args[1..], stdin, stdout, stderr), - RefinedJob::Builtin { + JobVariant::Builtin { main, ref args, - ref stdin, - ref stdout, - ref stderr, } => shell.exec_builtin(main, &**args, stdout, stderr, stdin), - RefinedJob::Function { + JobVariant::Function { ref name, ref args, - ref stdin, - ref stdout, - ref stderr, } => shell.exec_function(name, args, stdout, stderr, stdin), _ => panic!("exec job should not be able to be called on Cat or Tee jobs"), } } pub(crate) fn stderr(&mut self, file: File) { - set_field!(self, stderr, file); + if let JobVariant::Cat { .. } = self.var { + return; + } + + self.stderr = Some(file); } pub(crate) fn stdout(&mut self, file: File) { - if let RefinedJob::Cat { ref mut stdout, .. } = *self { - *stdout = Some(file); - } else { - set_field!(self, stdout, file); - } + self.stdout = Some(file); } pub(crate) fn stdin(&mut self, file: File) { - if let &mut RefinedJob::Cat { ref mut stdin, .. } = self { - *stdin = Some(file); - } else { - set_field!(self, stdin, file); - } + self.stdin = Some(file); } pub(crate) fn tee(tee_out: Option<TeeItem>, tee_err: Option<TeeItem>) -> Self { - RefinedJob::Tee { - items: (tee_out, tee_err), + RefinedJob { stdin: None, stdout: None, stderr: None, + var: JobVariant::Tee { + items: (tee_out, tee_err), + } } } pub(crate) fn cat(sources: Vec<File>) -> Self { - RefinedJob::Cat { - sources, - stdin: None, + RefinedJob { + stdin: None, stdout: None, + stderr: None, + var: JobVariant::Cat { sources } } } pub(crate) fn function(name: types::Str, args: types::Array) -> Self { - RefinedJob::Function { - name, - args, - stdin: None, + RefinedJob { + stdin: None, stdout: None, stderr: None, + var: JobVariant::Function { name, args } } } pub(crate) fn builtin(main: BuiltinFunction, args: types::Array) -> Self { - RefinedJob::Builtin { - main, - args, - stdin: None, + RefinedJob { + stdin: None, stdout: None, stderr: None, + var: JobVariant::Builtin { main, args } } } pub(crate) fn external(name: types::Str, args: types::Array) -> Self { - RefinedJob::External { - name, - args, - stdin: None, + RefinedJob { + stdin: None, stdout: None, stderr: None, + var: JobVariant::External { name, args } } } } diff --git a/src/lib/shell/pipe_exec/mod.rs b/src/lib/shell/pipe_exec/mod.rs index 1229519a006725267fed6afa2d0180e79201101e..d5d9dd3f23dc60ec492695d04d6f8ab64e74bdaf 100644 --- a/src/lib/shell/pipe_exec/mod.rs +++ b/src/lib/shell/pipe_exec/mod.rs @@ -9,6 +9,7 @@ pub mod foreground; mod fork; pub mod job_control; pub mod streams; +mod pipes; use self::{ fork::fork_pipe, @@ -19,7 +20,7 @@ use super::{ flags::*, flow_control::{Function, FunctionError}, fork_function::command_not_found, - job::{RefinedJob, TeeItem}, + job::{JobVariant, RefinedJob, TeeItem}, signals::{self, SignalHandler}, status::*, JobKind, Shell, @@ -37,6 +38,7 @@ use std::{ process::{self, exit}, }; use sys; +use self::pipes::TeePipe; type RefinedItem = (RefinedJob, JobKind, Vec<Redirection>, Vec<Input>); @@ -757,7 +759,7 @@ pub(crate) fn pipe( ) -> i32 { let mut previous_status = SUCCESS; let mut commands = commands.into_iter().peekable(); - let mut possible_external_stdio_pipes: Option<Vec<File>> = None; + let mut ext_stdio_pipes: Option<Vec<File>> = None; loop { if let Some((mut parent, mut kind)) = commands.next() { @@ -774,7 +776,7 @@ pub(crate) fn pipe( // If parent is a RefindJob::External, then we need to keep track of the // output pipes, so we can properly close them after the job has been // spawned. - let is_external = if let RefinedJob::External { .. } = parent { + let is_external = if let JobVariant::External { .. } = parent.var { true } else { false @@ -783,59 +785,27 @@ pub(crate) fn pipe( // TODO: Refactor this part // If we need to tee both stdout and stderr, we directly connect pipes to // the relevant sources in both of them. - if let RefinedJob::Tee { + if let JobVariant::Tee { items: (Some(ref mut tee_out), Some(ref mut tee_err)), .. - } = child - { - match sys::pipe2(sys::O_CLOEXEC) { - Err(e) => eprintln!("ion: failed to create pipe: {:?}", e), - Ok((out_reader, out_writer)) => { - (*tee_out).source = - Some(unsafe { File::from_raw_fd(out_reader) }); - parent.stdout(unsafe { File::from_raw_fd(out_writer) }); - if is_external { - possible_external_stdio_pipes - .get_or_insert(vec![]) - .push(unsafe { File::from_raw_fd(out_writer) }); - } - } - } - match sys::pipe2(sys::O_CLOEXEC) { - Err(e) => eprintln!("ion: failed to create pipe: {:?}", e), - Ok((err_reader, err_writer)) => { - (*tee_err).source = - Some(unsafe { File::from_raw_fd(err_reader) }); - parent.stderr(unsafe { File::from_raw_fd(err_writer) }); - if is_external { - possible_external_stdio_pipes - .get_or_insert(vec![]) - .push(unsafe { File::from_raw_fd(err_writer) }); - } - } - } + } = child.var { + TeePipe::new(&mut parent, &mut ext_stdio_pipes, is_external) + .connect(tee_out, tee_err); } else { + // Pipe the previous command's stdin to this commands stdout/stderr. match sys::pipe2(sys::O_CLOEXEC) { - Err(e) => { - eprintln!("ion: failed to create pipe: {:?}", e); - } + Err(e) => pipe_fail(e), Ok((reader, writer)) => { if is_external { - possible_external_stdio_pipes - .get_or_insert(vec![]) - .push(unsafe { File::from_raw_fd(writer) }); + append_external_stdio_pipe(&mut ext_stdio_pipes, writer); } child.stdin(unsafe { File::from_raw_fd(reader) }); + let writer = unsafe { File::from_raw_fd(writer) }; match mode { - RedirectFrom::Stderr => { - parent.stderr(unsafe { File::from_raw_fd(writer) }); - } - RedirectFrom::Stdout => { - parent.stdout(unsafe { File::from_raw_fd(writer) }); - } + RedirectFrom::Stderr => parent.stderr(writer), + RedirectFrom::Stdout => parent.stdout(writer), RedirectFrom::Both => { - let temp = unsafe { File::from_raw_fd(writer) }; - match temp.try_clone() { + match writer.try_clone() { Err(e) => { eprintln!( "ion: failed to redirect stdout and \ @@ -844,7 +814,7 @@ pub(crate) fn pipe( ); } Ok(duped) => { - parent.stderr(temp); + parent.stderr(writer); parent.stdout(duped); } } @@ -867,7 +837,7 @@ pub(crate) fn pipe( error_code => return error_code, } - possible_external_stdio_pipes = None; + ext_stdio_pipes = None; if set_process_group(&mut pgid, current_pid) && foreground @@ -882,6 +852,8 @@ pub(crate) fn pipe( parent = child; mode = m; } else { + + kind = ckind; block_child = false; match spawn_proc( @@ -905,8 +877,6 @@ pub(crate) fn pipe( set_process_group(&mut pgid, current_pid); previous_status = shell.wait(pgid, remember); - let _ = io::stdout().flush(); - let _ = io::stderr().flush(); if previous_status == TERMINATED { if let Err(why) = sys::killpg(pgid, sys::SIGTERM) { eprintln!("ion: failed to terminate foreground jobs: {}", why); @@ -916,14 +886,16 @@ pub(crate) fn pipe( } _ => { previous_status = shell.exec_job(&mut parent, foreground); - let _ = io::stdout().flush(); - let _ = io::stderr().flush(); } } } else { break; } } + + let _ = io::stdout().flush(); + let _ = io::stderr().flush(); + previous_status } @@ -936,34 +908,18 @@ fn spawn_proc( current_pid: &mut u32, pgid: u32, ) -> i32 { - let short = cmd.short(); - match cmd { - RefinedJob::External { - ref name, - ref args, - ref stdout, - ref stderr, - ref stdin, - } => { + let stdin = &mut cmd.stdin; + let stdout = &mut cmd.stdout; + let stderr = &mut cmd.stderr; + match cmd.var { + JobVariant::External { ref mut name, ref mut args } => { let args: Vec<&str> = args.iter().skip(1).map(|x| x as &str).collect(); - let result = sys::fork_and_exec( + let mut result = sys::fork_and_exec( name, &args, - if let Some(ref f) = *stdin { - Some(f.as_raw_fd()) - } else { - None - }, - if let Some(ref f) = *stdout { - Some(f.as_raw_fd()) - } else { - None - }, - if let Some(ref f) = *stderr { - Some(f.as_raw_fd()) - } else { - None - }, + stdin.as_mut().map(|f| f.as_raw_fd()), + stdout.as_mut().map(|f| f.as_raw_fd()), + stderr.as_mut().map(|f| f.as_raw_fd()), false, || prepare_child(block_child, pgid), ); @@ -973,123 +929,97 @@ fn spawn_proc( *last_pid = *current_pid; *current_pid = pid; } - Err(ref err) if err.kind() == io::ErrorKind::NotFound => { + Err(ref mut err) if err.kind() == io::ErrorKind::NotFound => { if !command_not_found(shell, &name) { eprintln!("ion: command not found: {}", name); } } - Err(ref err) => { + Err(ref mut err) => { eprintln!("ion: command exec error: {}", err); } } } - RefinedJob::Builtin { - main, - ref args, - ref stdout, - ref stderr, - ref stdin, - } => match unsafe { sys::fork() } { - Ok(0) => { - prepare_child(block_child, pgid); - let ret = shell.exec_builtin(main, args, stdout, stderr, stdin); - close(stdout); - close(stderr); - close(stdin); - exit(ret) - } - Ok(pid) => { - close(stdin); - close(stdout); - close(stderr); - *last_pid = *current_pid; - *current_pid = pid; - } - Err(e) => { - eprintln!("ion: failed to fork {}: {}", short, e); - } + JobVariant::Builtin { main, ref mut args } => { + fork_exec_internal( + stdout, + stderr, + stdin, + block_child, + last_pid, + current_pid, + pgid, + |stdout, stderr, stdin| shell.exec_builtin(main, args, stdout, stderr, stdin) + ); }, - RefinedJob::Function { - ref name, - ref args, - ref stdout, - ref stderr, - ref stdin, - } => match unsafe { sys::fork() } { - Ok(0) => { - prepare_child(block_child, pgid); - let ret = shell.exec_function(name, &args, stdout, stderr, stdin); - close(stdout); - close(stderr); - close(stdin); - exit(ret) - } - Ok(pid) => { - close(stdin); - close(stdout); - close(stderr); - *last_pid = *current_pid; - *current_pid = pid; - } - Err(e) => { - eprintln!("ion: failed to fork {}: {}", short, e); - } + JobVariant::Function { ref mut name, ref mut args } => { + fork_exec_internal( + stdout, + stderr, + stdin, + block_child, + last_pid, + current_pid, + pgid, + |stdout, stderr, stdin| shell.exec_function(name, &args, stdout, stderr, stdin) + ); }, - RefinedJob::Cat { - ref mut sources, - ref stdout, - ref mut stdin, - } => match unsafe { sys::fork() } { - Ok(0) => { - prepare_child(block_child, pgid); - - let ret = shell.exec_multi_in(sources, stdout, stdin); - close(stdout); - close(stdin); - exit(ret); - } - Ok(pid) => { - close(stdin); - close(stdout); - *last_pid = *current_pid; - *current_pid = pid; - } - Err(e) => eprintln!("ion: failed to fork {}: {}", short, e), + JobVariant::Cat { ref mut sources } => { + fork_exec_internal( + stdout, + &mut None, + stdin, + block_child, + last_pid, + current_pid, + pgid, + |stdout, _, stdin| shell.exec_multi_in(sources, stdout, stdin) + ); }, - RefinedJob::Tee { - ref mut items, - ref stdout, - ref stderr, - ref stdin, - } => match unsafe { sys::fork() } { - Ok(0) => { - prepare_child(block_child, pgid); - - let ret = shell.exec_multi_out(items, stdout, stderr, stdin, kind); - close(stdout); - close(stderr); - close(stdin); - exit(ret); - } - Ok(pid) => { - close(stdin); - close(stdout); - close(stderr); - *last_pid = *current_pid; - *current_pid = pid; - } - Err(e) => eprintln!("ion: failed to fork {}: {}", short, e), + JobVariant::Tee { ref mut items } => { + fork_exec_internal( + stdout, + stderr, + stdin, + block_child, + last_pid, + current_pid, + pgid, + |stdout, stderr, stdin| shell.exec_multi_out(items, stdout, stderr, stdin, kind) + ); }, } SUCCESS } -// TODO: Don't require this. -fn close(file: &Option<File>) { - if let Some(ref file) = *file { - if let Err(e) = sys::close(file.as_raw_fd()) { - eprintln!("ion: failed to close file '{:?}': {}", file, e); +// TODO: Integrate this better within the RefinedJob type. +fn fork_exec_internal<F>( + stdout: &mut Option<File>, + stderr: &mut Option<File>, + stdin: &mut Option<File>, + block_child: bool, + last_pid: &mut u32, + current_pid: &mut u32, + pgid: u32, + mut exec_action: F, +) where F: FnMut(&mut Option<File>, &mut Option<File>, &mut Option<File>) -> i32 { + match unsafe { sys::fork() } { + Ok(0) => { + prepare_child(block_child, pgid); + + let exit_status = exec_action(stdout, stderr, stdin); + stdout.take(); + stderr.take(); + stdin.take(); + exit(exit_status) + } + Ok(pid) => { + stdin.take(); + stdout.take(); + stderr.take(); + *last_pid = *current_pid; + *current_pid = pid; } + Err(e) => pipe_fail(e) } } @@ -1139,3 +1069,11 @@ pub fn wait_for_interrupt(pid: u32) -> io::Result<()> { } } } + +pub fn pipe_fail(why: io::Error) { + eprintln!("ion: failed to create pipe: {:?}", why); +} + +pub fn append_external_stdio_pipe(pipes: &mut Option<Vec<File>>, file: RawFd) { + pipes.get_or_insert_with(|| Vec::with_capacity(4)).push(unsafe { File::from_raw_fd(file) }); +} diff --git a/src/lib/shell/pipe_exec/pipes.rs b/src/lib/shell/pipe_exec/pipes.rs new file mode 100644 index 0000000000000000000000000000000000000000..59e22fa6c040dc44926d7abbfaf09fda467a4a8b --- /dev/null +++ b/src/lib/shell/pipe_exec/pipes.rs @@ -0,0 +1,49 @@ + +use super::{ + append_external_stdio_pipe, + pipe_fail, +}; +use super::super::job::{RefinedJob, TeeItem}; + +use std::{ + fs::File, + io, + os::unix::io::FromRawFd, +}; +use sys; + +pub(crate) struct TeePipe<'a> { + parent: &'a mut RefinedJob, + ext_stdio_pipes: &'a mut Option<Vec<File>>, + is_external: bool +} + +impl<'a> TeePipe<'a> { + pub(crate) fn new( + parent: &'a mut RefinedJob, + ext_stdio_pipes: &'a mut Option<Vec<File>>, + is_external: bool + ) -> TeePipe<'a> { + TeePipe { parent, ext_stdio_pipes, is_external } + } + + fn inner_connect<F>(&mut self, tee: &mut TeeItem, mut action: F) + where F: FnMut(&mut RefinedJob, File) + { + match sys::pipe2(sys::O_CLOEXEC) { + Err(e) => pipe_fail(e), + Ok((reader, writer)) => { + (*tee).source = Some(unsafe { File::from_raw_fd(reader) }); + action(self.parent, unsafe { File::from_raw_fd(writer) }); + if self.is_external { + append_external_stdio_pipe(self.ext_stdio_pipes, writer); + } + } + } + } + + pub(crate) fn connect(&mut self, out: &mut TeeItem, err: &mut TeeItem) { + self.inner_connect(out, |parent, writer| parent.stdout(writer)); + self.inner_connect(err, |parent, writer| parent.stderr(writer)); + } +}