diff --git a/src/lib/shell/job.rs b/src/lib/shell/job.rs index d905ce7605648af98eb434b2f174bba693ae4099..4ad46d3d7b5d9a48d2d76949cf76482cc906398b 100644 --- a/src/lib/shell/job.rs +++ b/src/lib/shell/job.rs @@ -5,7 +5,6 @@ use parser::pipelines::RedirectFrom; use smallstring::SmallString; use std::fmt; use std::fs::File; -use std::process::{Command, Stdio}; use std::str; use types::*; @@ -79,31 +78,27 @@ fn expand_arg(arg: &str, shell: &Shell) -> Array { /// as part of some pipeline pub(crate) enum RefinedJob { /// An external program that is executed by this shell - External(Command), + External { + name: Identifier, + args: Array, + stdin: Option<File>, + stdout: Option<File>, + stderr: Option<File>, + }, /// A procedure embedded into Ion Builtin { - /// Name of the procedure main: BuiltinFunction, - /// Arguments to pass in to the procedure args: Array, - /// A file corresponding to the standard input for this builtin stdin: Option<File>, - /// A file corresponding to the standard output for this builtin stdout: Option<File>, - /// A file corresponding to the standard error for this builtin stderr: Option<File>, }, /// Functions can act as commands too! Function { - /// Name of the procedure name: Identifier, - /// Arguments to pass in to the procedure args: Array, - /// A file corresponding to the standard input for this builtin stdin: Option<File>, - /// A file corresponding to the standard output for this builtin stdout: Option<File>, - /// A file corresponding to the standard error for this builtin stderr: Option<File>, }, /// Represents redirection into stdin from more than one source @@ -187,10 +182,8 @@ impl TeeItem { macro_rules! set_field { ($self:expr, $field:ident, $arg:expr) => { match *$self { - RefinedJob::External(ref mut command) => { - command.$field(Stdio::from($arg)); - } - RefinedJob::Builtin { ref mut $field, .. } | + RefinedJob::External { ref mut $field, .. } | + RefinedJob::Builtin { ref mut $field, .. } | RefinedJob::Function { ref mut $field, .. } | RefinedJob::Tee { ref mut $field, .. } => { *$field = Some($arg); @@ -202,6 +195,16 @@ macro_rules! set_field { } impl RefinedJob { + pub(crate) fn external(name: Identifier, args: Array) -> Self { + RefinedJob::External { + name, + args, + stdin: None, + stdout: None, + stderr: None, + } + } + pub(crate) fn builtin(main: BuiltinFunction, args: Array) -> Self { RefinedJob::Builtin { main, @@ -263,13 +266,9 @@ impl RefinedJob { /// or builtin name pub(crate) fn short(&self) -> String { match *self { - RefinedJob::External(ref cmd) => format!("{:?}", cmd) - .split('"') - .nth(1) - .unwrap_or("") - .to_string(), RefinedJob::Builtin { .. } => String::from("Shell Builtin"), - RefinedJob::Function { ref name, .. } => name.to_string(), + RefinedJob::Function { ref name, .. } | + RefinedJob::External { ref name, .. } => name.to_string(), // TODO: Print for real RefinedJob::Cat { .. } => "multi-input".into(), RefinedJob::Tee { .. } => "multi-output".into(), @@ -279,24 +278,9 @@ impl RefinedJob { /// Returns a long description of this job: the commands and arguments pub(crate) fn long(&self) -> String { match *self { - RefinedJob::External(ref cmd) => { - let command = format!("{:?}", cmd); - let mut arg_iter = command.split_whitespace(); - let command = arg_iter.next().unwrap(); - let mut output = String::from(&command[1..command.len() - 1]); - for argument in arg_iter { - output.push(' '); - if argument.len() > 2 { - output.push_str(&argument[1..argument.len() - 1]); - } else { - output.push_str(&argument); - } - } - output - } - RefinedJob::Builtin { ref args, .. } | RefinedJob::Function { ref args, .. } => { - format!("{}", args.join(" ")) - } + RefinedJob::External { ref args, .. } | + RefinedJob::Builtin { ref args, .. } | + RefinedJob::Function { ref args, .. } => format!("{}", args.join(" ")), // TODO: Figure out real printing RefinedJob::Cat { .. } | RefinedJob::Tee { .. } => "".into(), } diff --git a/src/lib/shell/mod.rs b/src/lib/shell/mod.rs index 7e56ca6fc77e18df352f577f9f5fa407095c4d78..1ca66d6da52d0a9d965d4e4aa2435cbd506577ed 100644 --- a/src/lib/shell/mod.rs +++ b/src/lib/shell/mod.rs @@ -126,6 +126,12 @@ impl ShellBuilder { let _ = sys::signal(sys::SIGINT, handler); let _ = sys::signal(sys::SIGTERM, handler); + extern "C" fn sigpipe_handler(signal: i32) { + sys::fork_exit(127 + signal); + } + + let _ = sys::signal(sys::SIGPIPE, sigpipe_handler); + self } @@ -328,6 +334,7 @@ impl<'a> Shell { self.set_var("?", &code.to_string()); self.previous_status = code; } + exit_status } diff --git a/src/lib/shell/pipe_exec/job_control.rs b/src/lib/shell/pipe_exec/job_control.rs index e5fe69eb2bec35821a1561165c3102bc0bc66953..854fd97a1c15fd046996fde25cf88ec7007adc8c 100644 --- a/src/lib/shell/pipe_exec/job_control.rs +++ b/src/lib/shell/pipe_exec/job_control.rs @@ -33,16 +33,7 @@ pub(crate) trait JobControl { fn handle_signal(&self, signal: i32) -> bool; fn foreground_send(&self, signal: i32); fn background_send(&self, signal: i32); - fn watch_foreground<F, D>( - &mut self, - pid: u32, - last_pid: u32, - get_command: F, - drop_command: D, - ) -> i32 - where - F: FnOnce() -> String, - D: FnMut(i32); + fn watch_foreground(&mut self, pid: i32, command: &str) -> i32; fn send_to_background(&mut self, child: u32, state: ProcessState, command: String); } @@ -157,18 +148,8 @@ impl JobControl for Shell { self.exit(sigcode); } - fn watch_foreground<F, D>( - &mut self, - pid: u32, - last_pid: u32, - get_command: F, - drop_command: D, - ) -> i32 - where - F: FnOnce() -> String, - D: FnMut(i32), - { - self_sys::watch_foreground(self, pid, last_pid, get_command, drop_command) + fn watch_foreground(&mut self, pid: i32, command: &str) -> i32 { + self_sys::watch_foreground(self, pid, command) } /// Send a kill signal to all running foreground tasks. diff --git a/src/lib/shell/pipe_exec/mod.rs b/src/lib/shell/pipe_exec/mod.rs index 8fb20e9f9cc25e736ea1392d23c252a470c9ebfd..639f70cd9c9151a3598087cdb6c9b519ff0c26df 100644 --- a/src/lib/shell/pipe_exec/mod.rs +++ b/src/lib/shell/pipe_exec/mod.rs @@ -11,8 +11,9 @@ mod fork; pub mod job_control; mod streams; +// TODO: Reintegrate this use self::command_not_found::command_not_found; -use self::fork::{create_process_group, fork_pipe}; +use self::fork::fork_pipe; use self::job_control::{JobControl, ProcessState}; use self::streams::{duplicate_streams, redir, redirect_streams}; use super::{JobKind, Shell}; @@ -27,9 +28,8 @@ use std::fs::{File, OpenOptions}; use std::io::{self, Error, Write}; use std::iter; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; -use std::os::unix::process::CommandExt; use std::path::Path; -use std::process::{self, exit, Command}; +use std::process::{self, exit}; use sys; type RefinedItem = (RefinedJob, JobKind, Vec<Redirection>, Vec<Input>); @@ -348,9 +348,9 @@ pub(crate) trait PipelineExecution { /// associated will be marked as an `&&`, `||`, `|`, or final job. fn generate_commands(&self, pipeline: &mut Pipeline) -> Result<Vec<RefinedItem>, i32>; - /// Waits for all of the children within a pipe to finish exuecting, returning the + /// Waits for all of the children of the assigned pgid to finish executing, returning the /// exit status of the last process in the queue. - fn wait(&mut self, children: Vec<u32>, commands: Vec<RefinedJob>) -> i32; + fn wait(&mut self, pgid: u32, commands: Vec<RefinedJob>) -> i32; /// Executes a `RefinedJob` that was created in the `generate_commands` method. /// @@ -375,6 +375,15 @@ pub(crate) trait PipelineExecution { stdin: &Option<File>, ) -> i32; + fn exec_external( + &mut self, + name: &str, + args: &[&str], + stdout: &Option<File>, + stderr: &Option<File>, + stdin: &Option<File>, + ) -> i32; + fn exec_function( &mut self, name: &str, @@ -474,19 +483,16 @@ impl PipelineExecution for Shell { } else if let Some(builtin) = job.builtin { RefinedJob::builtin(builtin, job.args.drain().collect()) } else { - let mut command = Command::new(job.args[0].clone()); - for arg in job.args.drain().skip(1) { - command.arg(arg); - } - RefinedJob::External(command) + RefinedJob::external(job.args[0].clone().into(), job.args.drain().collect()) } }; results.push((refined, job.kind, outputs, inputs)); } + Ok(results) } - fn wait(&mut self, mut children: Vec<u32>, mut commands: Vec<RefinedJob>) -> i32 { + fn wait(&mut self, pgid: u32, commands: Vec<RefinedJob>) -> i32 { // TODO: Find a way to only do this when absolutely necessary. let as_string = commands .iter() @@ -494,55 +500,24 @@ impl PipelineExecution for Shell { .collect::<Vec<String>>() .join(" | "); - // Each process in the pipe has the same PGID, which is the first process's PID. - let pgid = children[0]; - - // If the last process exits, we know that all processes should exit. - let last_pid = children[children.len() - 1]; - // Watch the foreground group, dropping all commands that exit as they exit. - self.watch_foreground( - pgid, - last_pid, - move || as_string, - move |pid| { - if let Some(id) = children.iter().position(|&x| x as i32 == pid) { - commands.remove(id); - children.remove(id); - } - }, - ) + self.watch_foreground(-(pgid as i32), &as_string) } - fn exec_job(&mut self, job: &mut RefinedJob, foreground: bool) -> i32 { + fn exec_job(&mut self, job: &mut RefinedJob, _foreground: bool) -> i32 { let short = job.short(); let long = job.long(); match *job { - RefinedJob::External(ref mut command) => match { - command - .before_exec(move || { - signals::unblock(); - create_process_group(0); - Ok(()) - }) - .spawn() - } { - Ok(child) => { - if foreground && !self.is_library { - let _ = sys::tcsetpgrp(0, child.id()); - } - self.watch_foreground(child.id(), child.id(), move || long, |_| ()) - } - Err(e) => if e.kind() == io::ErrorKind::NotFound { - if !command_not_found(self, &short) { - eprintln!("ion: command not found: {}", short); - } - NO_SUCH_COMMAND - } else { - eprintln!("ion: error spawning process: {}", e); - COULD_NOT_EXEC - }, - }, + RefinedJob::External { + ref name, + ref args, + ref stdin, + ref stdout, + ref stderr, + } => { + let args: Vec<&str> = args.iter().skip(1).map(|x| x as &str).collect(); + return self.exec_external(&name, &args, stdin, stdout, stderr); + } RefinedJob::Builtin { main, ref args, @@ -727,29 +702,67 @@ impl PipelineExecution for Shell { SUCCESS } } + + fn exec_external( + &mut self, + name: &str, + args: &[&str], + stdin: &Option<File>, + stdout: &Option<File>, + stderr: &Option<File>, + ) -> i32 { + return match unsafe { sys::fork() } { + Ok(0) => { + if let Some(ref file) = *stdin { + redir(file.as_raw_fd(), sys::STDIN_FILENO); + let _ = sys::close(file.as_raw_fd()); + } + + if let Some(ref file) = *stdout { + redir(file.as_raw_fd(), sys::STDOUT_FILENO); + let _ = sys::close(file.as_raw_fd()); + } + + if let Some(ref file) = *stderr { + redir(file.as_raw_fd(), sys::STDERR_FILENO); + let _ = sys::close(file.as_raw_fd()); + } + + prepare_child(false); + if let Err(_why) = sys::execve(name, &args, false) { + command_not_found(self, name); + sys::fork_exit(NO_SUCH_COMMAND); + } + unreachable!() + }, + Ok(pid) => { + close(stdin); + close(stdout); + close(stderr); + // TODO: get long string + self.watch_foreground(pid as i32, "") + } + Err(why) => { + eprintln!("ion: failed to fork: {}", why); + COULD_NOT_EXEC + } + } + } } +/// Executes a piped job `job1 | job2 | job3` +/// /// This function will panic if called with an empty slice pub(crate) fn pipe( shell: &mut Shell, commands: Vec<(RefinedJob, JobKind)>, foreground: bool, ) -> i32 { - 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); - } - } - } - let mut previous_status = SUCCESS; let mut previous_kind = JobKind::And; - let mut commands = commands.into_iter(); - // A vector to hold possible external command stdout/stderr pipes. - // If it is Some, then we close the various file descriptors after - // spawning a child job. - let mut ext_stdio: Option<Vec<RawFd>> = None; + let mut commands = commands.into_iter().peekable(); + let mut possible_external_stdio_pipes: Option<Vec<File>> = None; + loop { if let Some((mut parent, mut kind)) = commands.next() { // When an `&&` or `||` operator is utilized, execute commands based on the @@ -774,219 +787,21 @@ pub(crate) fn pipe( JobKind::Pipe(mut mode) => { // We need to remember the commands as they own the file // descriptors that are created by sys::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(); - // A list of the PIDs in the piped command - let mut children: Vec<u32> = Vec::new(); - // The process group by which all of the PIDs belong to. - let mut pgid = 0; // 0 means the PGID is not set yet. - - macro_rules! spawn_proc { - ($cmd:expr) => { - let short = $cmd.short(); - match $cmd { - RefinedJob::External(ref mut command) => { - match { - command.before_exec(move || { - signals::unblock(); - create_process_group(pgid); - Ok(()) - }).spawn() - } { - Ok(child) => { - if pgid == 0 { - pgid = child.id(); - if foreground && !shell.is_library { - let _ = sys::tcsetpgrp(0, pgid); - } - } - shell.foreground.push(child.id()); - children.push(child.id()); - }, - Err(e) => { - return if e.kind() == io::ErrorKind::NotFound { - if !command_not_found(shell, &short) { - eprintln!("ion: command not found: {}", short); - } - NO_SUCH_COMMAND - } else { - eprintln!("ion: error spawning process: {}", e); - COULD_NOT_EXEC - } - } - } - } - RefinedJob::Builtin { main, - ref args, - ref stdout, - ref stderr, - ref stdin, } => - { - match unsafe { sys::fork() } { - Ok(0) => { - signals::unblock(); - let _ = sys::reset_signal(sys::SIGINT); - let _ = sys::reset_signal(sys::SIGHUP); - let _ = sys::reset_signal(sys::SIGTERM); - create_process_group(pgid); - let args: Vec<&str> = args - .iter() - .map(|x| x as &str).collect(); - let ret = shell.exec_builtin(main, - &args, - stdout, - stderr, - stdin); - close(stdout); - close(stderr); - close(stdin); - exit(ret) - }, - Ok(pid) => { - close(stdout); - close(stderr); - if pgid == 0 { - pgid = pid; - if foreground && !shell.is_library { - let _ = sys::tcsetpgrp(0, pgid); - } - } - shell.foreground.push(pid); - children.push(pid); - }, - Err(e) => { - eprintln!("ion: failed to fork {}: {}", - short, - e); - } - } - } - RefinedJob::Function { ref name, - ref args, - ref stdout, - ref stderr, - ref stdin, } => - { - match unsafe { sys::fork() } { - Ok(0) => { - // signals::unblock(); - let _ = sys::reset_signal(sys::SIGINT); - let _ = sys::reset_signal(sys::SIGHUP); - let _ = sys::reset_signal(sys::SIGTERM); - create_process_group(pgid); - let args: Vec<&str> = args - .iter() - .map(|x| x as &str).collect(); - let ret = shell.exec_function(name, - &args, - stdout, - stderr, - stdin); - close(stdout); - close(stderr); - close(stdin); - exit(ret) - }, - Ok(pid) => { - close(stdout); - close(stderr); - if pgid == 0 { - pgid = pid; - if foreground && !shell.is_library { - let _ = sys::tcsetpgrp(0, pgid); - } - } - shell.foreground.push(pid); - children.push(pid); - }, - Err(e) => { - eprintln!("ion: failed to fork {}: {}", - short, - e); - } - } - } - RefinedJob::Cat { ref mut sources, - ref stdout, - ref mut stdin } => { - match unsafe { sys::fork() } { - Ok(0) => { - let _ = sys::reset_signal(sys::SIGINT); - let _ = sys::reset_signal(sys::SIGHUP); - let _ = sys::reset_signal(sys::SIGTERM); - create_process_group(pgid); - let ret = shell.exec_multi_in( - sources, - stdout, - stdin, - ); - close(stdout); - close(stdin); - exit(ret); - } - Ok(pid) => { - close(stdout); - if pgid == 0 { - pgid = pid; - if foreground && !shell.is_library { - let _ = sys::tcsetpgrp(0, pgid); - } - } - shell.foreground.push(pid); - children.push(pid); - } - Err(e) => eprintln!("ion: failed to fork {}: {}", short, e), - } - }, - RefinedJob::Tee { ref mut items, - ref stdout, - ref stderr, - ref stdin } => { - match unsafe { sys::fork() } { - Ok(0) => { - let _ = sys::reset_signal(sys::SIGINT); - let _ = sys::reset_signal(sys::SIGHUP); - let _ = sys::reset_signal(sys::SIGTERM); - create_process_group(pgid); - let ret = shell.exec_multi_out( - items, - stdout, - stderr, - stdin, - kind, - ); - close(stdout); - close(stderr); - close(stdin); - exit(ret); - }, - Ok(pid) => { - close(stdout); - close(stderr); - if pgid == 0 { - pgid = pid; - if foreground && !shell.is_library { - let _ = sys::tcsetpgrp(0, pgid); - } - } - shell.foreground.push(pid); - children.push(pid); - } - Err(e) => eprintln!("ion: failed to fork {}: {}", short, e), - } - } - } - }; - } - // Append other jobs until all piped jobs are running + let mut pgid = 0; + let mut last_pid = 0; + let mut current_pid = 0; + + // When set to true, this command will be SIGSTOP'd before it executes. + let mut child_blocked; + + // Append jobs until all piped jobs are running while let Some((mut child, ckind)) = commands.next() { // 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 RefinedJob::External { .. } = parent { true } else { false @@ -1006,7 +821,7 @@ pub(crate) fn pipe( Some(unsafe { File::from_raw_fd(out_reader) }); parent.stdout(unsafe { File::from_raw_fd(out_writer) }); if is_external { - ext_stdio.get_or_insert(vec![]).push(out_writer); + possible_external_stdio_pipes.get_or_insert(vec![]).push(unsafe { File::from_raw_fd(out_writer) }); } } } @@ -1017,7 +832,7 @@ pub(crate) fn pipe( Some(unsafe { File::from_raw_fd(err_reader) }); parent.stderr(unsafe { File::from_raw_fd(err_writer) }); if is_external { - ext_stdio.get_or_insert(vec![]).push(err_writer); + possible_external_stdio_pipes.get_or_insert(vec![]).push(unsafe { File::from_raw_fd(err_writer) }); } } } @@ -1028,7 +843,7 @@ pub(crate) fn pipe( } Ok((reader, writer)) => { if is_external { - ext_stdio.get_or_insert(vec![]).push(writer); + possible_external_stdio_pipes.get_or_insert(vec![]).push(unsafe { File::from_raw_fd(writer) }); } child.stdin(unsafe { File::from_raw_fd(reader) }); match mode { @@ -1062,15 +877,26 @@ pub(crate) fn pipe( } } } - spawn_proc!(parent); - remember.push(parent); - if let Some(fds) = ext_stdio.take() { - for fd in fds { - if let Err(e) = sys::close(fd) { - eprintln!("ion: failed to close file '{:?}': {}", fd, e); - } - } + + child_blocked = match ckind { + JobKind::Pipe(_) | JobKind::Last => true, + _ => false + }; + + match spawn_proc(shell, parent, kind, child_blocked, &mut last_pid, &mut current_pid) { + SUCCESS => (), + error_code => return error_code + } + + // remember.push(parent); + possible_external_stdio_pipes = None; + + if set_process_group(&mut pgid, current_pid) && foreground && !shell.is_library { + let _ = sys::tcsetpgrp(0, pgid); } + + resume_prior_process(&mut last_pid, current_pid, child_blocked); + if let JobKind::Pipe(m) = ckind { parent = child; mode = m; @@ -1081,15 +907,32 @@ pub(crate) fn pipe( // previous kind set to `And` after processing the // initial pipeline kind = ckind; - spawn_proc!(child); - remember.push(child); + + child_blocked = match commands.peek() { + Some(&(_, JobKind::Pipe(_))) => true, + Some(&(_, JobKind::Last)) => true, + _ => false + }; + + match spawn_proc(shell, child, kind, child_blocked, &mut last_pid, &mut current_pid) { + SUCCESS => (), + error_code => return error_code + } + + set_process_group(&mut pgid, current_pid) && foreground && !shell.is_library; + + // remember.push(child); + resume_prior_process(&mut last_pid, current_pid, child_blocked); + break; } } previous_kind = kind; - previous_status = shell.wait(children, remember); + previous_status = shell.wait(pgid, remember); if previous_status == TERMINATED { - shell.foreground_send(sys::SIGTERM); + if let Err(why) = sys::killpg(pgid, sys::SIGTERM) { + eprintln!("ion: failed to terminate foreground jobs: {}", why); + } return previous_status; } } @@ -1104,3 +947,182 @@ pub(crate) fn pipe( } previous_status } + +fn spawn_proc( + shell: &mut Shell, + mut cmd: RefinedJob, + kind: JobKind, + child_blocked: bool, + last_pid: &mut u32, + current_pid: &mut u32 +) -> i32 { + let short = cmd.short(); + match cmd { + RefinedJob::External { ref name, ref args, ref stdout, ref stderr, ref stdin} => { + let args: Vec<&str> = args.iter().skip(1).map(|x| x as &str).collect(); + match unsafe { sys::fork() } { + Ok(0) => { + if let Some(ref file) = *stdin { + redir(file.as_raw_fd(), sys::STDIN_FILENO); + let _ = sys::close(file.as_raw_fd()); + } + if let Some(ref file) = *stdout { + redir(file.as_raw_fd(), sys::STDOUT_FILENO); + let _ = sys::close(file.as_raw_fd()); + } + if let Some(ref file) = *stderr { + redir(file.as_raw_fd(), sys::STDERR_FILENO); + let _ = sys::close(file.as_raw_fd()); + } + + prepare_child(child_blocked); + if let Err(_why) = sys::execve(&name, &args, false) { + command_not_found(shell, name); + sys::fork_exit(NO_SUCH_COMMAND); + } + }, + Ok(pid) => { + close(stdin); + close(stdout); + close(stderr); + shell.foreground.push(pid); + *last_pid = *current_pid; + *current_pid = pid; + }, + Err(e) => { + eprintln!("ion: failed to fork {}: {}", short, e); + } + } + } + RefinedJob::Builtin { main, ref args, ref stdout, ref stderr, ref stdin } => { + let args: Vec<&str> = args.iter().map(|x| x as &str).collect(); + match unsafe { sys::fork() } { + Ok(0) => { + prepare_child(child_blocked); + let ret = shell.exec_builtin(main, &args, stdout, stderr, stdin); + close(stdout); + close(stderr); + close(stdin); + exit(ret) + }, + Ok(pid) => { + close(stdout); + close(stderr); + shell.foreground.push(pid); + *last_pid = *current_pid; + *current_pid = pid; + }, + Err(e) => { + eprintln!("ion: failed to fork {}: {}", short, e); + } + } + } + RefinedJob::Function { ref name, ref args, ref stdout, ref stderr, ref stdin, } => { + let args: Vec<&str> = args.iter().map(|x| x as &str).collect(); + match unsafe { sys::fork() } { + Ok(0) => { + prepare_child(child_blocked); + let ret = shell.exec_function(name, &args, stdout, stderr, stdin); + close(stdout); + close(stderr); + close(stdin); + exit(ret) + }, + Ok(pid) => { + close(stdout); + close(stderr); + shell.foreground.push(pid); + *last_pid = *current_pid; + *current_pid = pid; + }, + Err(e) => { + eprintln!("ion: failed to fork {}: {}", short, e); + } + } + } + RefinedJob::Cat { ref mut sources, ref stdout, ref mut stdin } => { + match unsafe { sys::fork() } { + Ok(0) => { + prepare_child(child_blocked); + + let ret = shell.exec_multi_in(sources, stdout, stdin); + close(stdout); + close(stdin); + exit(ret); + } + Ok(pid) => { + close(stdout); + shell.foreground.push(pid); + *last_pid = *current_pid; + *current_pid = pid; + } + Err(e) => eprintln!("ion: failed to fork {}: {}", short, e), + } + }, + RefinedJob::Tee { ref mut items, ref stdout, ref stderr, ref stdin } => { + match unsafe { sys::fork() } { + Ok(0) => { + prepare_child(child_blocked); + + let ret = shell.exec_multi_out(items, stdout, stderr, stdin, kind); + close(stdout); + close(stderr); + close(stdin); + exit(ret); + }, + Ok(pid) => { + close(stdout); + close(stderr); + shell.foreground.push(pid); + *last_pid = *current_pid; + *current_pid = pid; + } + Err(e) => eprintln!("ion: failed to fork {}: {}", short, e), + } + } + } + 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); + } + } +} + +fn prepare_child(child_blocked: bool) { + signals::unblock(); + let _ = sys::reset_signal(sys::SIGINT); + let _ = sys::reset_signal(sys::SIGHUP); + let _ = sys::reset_signal(sys::SIGTERM); + + if child_blocked { + let _ = sys::kill(process::id(), sys::SIGSTOP); + } else { + } +} + +fn resume_prior_process(last_pid: &mut u32, current_pid: u32, child_blocked: bool) { + if child_blocked { + // Ensure that the process is stopped before continuing. + if let Err(why) = sys::wait_for_interrupt(current_pid) { + eprintln!("ion: error waiting for sigstop: {}", why); + } + } + + if *last_pid != 0 { + let _ = sys::kill(*last_pid, sys::SIGCONT); + } + + *last_pid = current_pid; +} + +fn set_process_group(pgid: &mut u32, pid: u32) -> bool { + let pgid_set = *pgid == 0; + if pgid_set { *pgid = pid; } + let _ = sys::setpgid(pid, *pgid); + pgid_set +} \ No newline at end of file diff --git a/src/lib/shell/plugins/library_iter/mod.rs b/src/lib/shell/plugins/library_iter/mod.rs index 286d5815aedba6635c57f138c25635754fa83cdc..2c5a0f74d3e884913d0250e0c67399ad9e3c0317 100644 --- a/src/lib/shell/plugins/library_iter/mod.rs +++ b/src/lib/shell/plugins/library_iter/mod.rs @@ -1,7 +1,7 @@ #[cfg(target_os = "redox")] mod redox; -#[cfg(target_os = "redox")] -pub(crate) use self::redox::*; +// #[cfg(target_os = "redox")] +// pub(crate) use self::redox::*; #[cfg(all(unix, not(target_os = "redox")))] mod unix; diff --git a/src/lib/shell/plugins/mod.rs b/src/lib/shell/plugins/mod.rs index 527d0f44d6e3b954b24ba1505f9935b87cb04ddf..d792595cd5d4caa4cb2a52dd2dcc4ab5c4fdb155 100644 --- a/src/lib/shell/plugins/mod.rs +++ b/src/lib/shell/plugins/mod.rs @@ -3,6 +3,7 @@ pub mod namespaces; mod library_iter; mod string; +#[cfg(not(target_os = "redox"))] pub(crate) use self::library_iter::*; pub(crate) use self::string::StringError; diff --git a/src/lib/sys/redox.rs b/src/lib/sys/redox.rs index d39c574c9bd7959dcc27919aaa63d55ef2f1a398..1b19fd5ace7a561e22768f88468cf5cb013a68e8 100644 --- a/src/lib/sys/redox.rs +++ b/src/lib/sys/redox.rs @@ -5,9 +5,9 @@ use std::env; use std::os::unix::ffi::OsStrExt; use std::os::unix::io::RawFd; use std::path::PathBuf; -use std::process::exit; - -use syscall::SigAction; +use std::process::{exit, ExitStatus}; +use std::os::unix::process::ExitStatusExt; +use syscall::{EINTR, SigAction, waitpid}; pub(crate) const PATH_SEPARATOR: &str = ";"; pub(crate) const NULL_PATH: &str = "null:"; @@ -19,6 +19,7 @@ pub(crate) const SIGTERM: i32 = syscall::SIGTERM as i32; pub(crate) const SIGCONT: i32 = syscall::SIGCONT as i32; pub(crate) const SIGSTOP: i32 = syscall::SIGSTOP as i32; pub(crate) const SIGTSTP: i32 = syscall::SIGTSTP as i32; +pub(crate) const SIGPIPE: i32 = syscall::SIGPIPE as i32; pub(crate) const STDIN_FILENO: RawFd = 0; pub(crate) const STDOUT_FILENO: RawFd = 1; @@ -34,21 +35,33 @@ pub unsafe fn fork() -> io::Result<u32> { cvt(syscall::clone(0)).map(|pid| pid a pub fn fork_exit(status: i32) -> ! { exit(status) } +pub fn wait_for_interrupt(pid: u32) -> io::Result<()> { + let mut status = 0; + + loop { + match waitpid(pid as usize, &mut status, 0) { + Err(ref error) if error.errno == EINTR => continue, + Err(ref error) => break Err(io::Error::from_raw_os_error(error.errno)), + Ok(_) => break Ok(()), + } + } +} + pub fn wait_for_child(pid: u32) -> io::Result<u8> { let mut status; use syscall::{waitpid, ECHILD}; loop { status = 0; - match unsafe { waitpid(pid as usize, &mut status, 0) } { + match waitpid(pid as usize, &mut status, 0) { Err(ref error) if error.errno == ECHILD => break, Err(error) => return Err(io::Error::from_raw_os_error(error.errno)), _ => () } } - // Ok(WEXITSTATUS(status) as u8) - Ok(0) + let status = ExitStatus::from_raw(status as i32); + Ok(status.code().unwrap_or(0) as u8) } pub(crate) fn getpid() -> io::Result<u32> { cvt(syscall::getpid()).map(|pid| pid as u32) } @@ -192,11 +205,12 @@ pub mod job_control { use shell::Shell; use shell::foreground::ForegroundSignals; - use shell::status::{FAILURE, TERMINATED}; + use shell::status::FAILURE; use std::os::unix::process::ExitStatusExt; use std::process::ExitStatus; use std::sync::{Arc, Mutex}; - use syscall; + use syscall::{ECHILD, waitpid}; + use super::{SIGINT, SIGPIPE}; pub(crate) fn watch_background( _fg: Arc<ForegroundSignals>, @@ -207,54 +221,62 @@ pub mod job_control { // TODO: Implement this using syscall::call::waitpid } - pub(crate) fn watch_foreground<F, D>( - shell: &mut Shell, - _pid: u32, - last_pid: u32, - _get_command: F, - mut drop_command: D, - ) -> i32 - where - F: FnOnce() -> String, - D: FnMut(i32), - { + pub(crate) fn watch_foreground(shell: &mut Shell, pid: i32, command: &str ) -> i32 { + let mut signaled = 0; let mut exit_status = 0; + let mut status; + + fn get_pid_value(pid: i32) -> usize { + if pid < 0 { + !(pid.abs() as usize) + } else { + pid as usize + } + } + loop { - let mut status_raw = 0; - match syscall::waitpid(0, &mut status_raw, 0) { + status = 0; + let result = waitpid(get_pid_value(pid), &mut status, 0); + match result { + Err(error) => { + match error.errno { + ECHILD if signaled == 0 => break exit_status, + ECHILD => break signaled, + _ => { + eprintln!("ion: waitpid error: {}", error); + break FAILURE; + } + } + } + Ok(0) => (), Ok(pid) => { - let status = ExitStatus::from_raw(status_raw as i32); - if let Some(code) = status.code() { - if pid == (last_pid as usize) { - break code; - } else { - drop_command(pid as i32); - exit_status = code; + let es = ExitStatus::from_raw(status as i32); + match es.signal() { + Some(SIGPIPE) => continue, + Some(signal) => { + eprintln!("ion: process ended by signal {}", signal); + match signal { + SIGINT => { + shell.foreground_send(signal as i32); + shell.break_flow = true; + } + _ => { + shell.handle_signal(signal); + } + } + signaled = 128 + signal as i32; } - } else if let Some(signal) = status.signal() { - eprintln!("ion: process ended by signal: {}", signal); - if signal == syscall::SIGTERM as i32 { - shell.handle_signal(signal); - shell.exit(TERMINATED); - } else if signal == syscall::SIGHUP as i32 { - shell.handle_signal(signal); - shell.exit(TERMINATED); - } else if signal == syscall::SIGINT as i32 { - shell.foreground_send(signal); - shell.break_flow = true; + None => { + exit_status = es.code().unwrap(); } - break TERMINATED; - } else { - eprintln!("ion: process ended with unknown status: {}", status); - break TERMINATED; } } - Err(err) => if err.errno == syscall::ECHILD { - break exit_status; - } else { - eprintln!("ion: process doesn't exist: {}", err); - break FAILURE; - }, + // TODO: Background job control for Redox + // _pid if WIFSTOPPED(status) => { + // shell.send_to_background(pid as u32, ProcessState::Stopped, command.into()); + // shell.break_flow = true; + // break 128 + signal as i32; + // } } } } diff --git a/src/lib/sys/unix/job_control.rs b/src/lib/sys/unix/job_control.rs index 4e1c5995d9e7b35dec1e7435513597afad8d5205..fee78255e074f5ec1e6f32c1af70a85ceb7448bc 100644 --- a/src/lib/sys/unix/job_control.rs +++ b/src/lib/sys/unix/job_control.rs @@ -78,58 +78,34 @@ pub(crate) fn watch_background( } } -const FIRST: u8 = 1; -const LAST: u8 = 2; - -pub(crate) fn watch_foreground<F, D>( - shell: &mut Shell, - first_pid: u32, - last_pid: u32, - get_command: F, - mut drop_command: D, -) -> i32 -where - F: FnOnce() -> String, - D: FnMut(i32), -{ +pub(crate) fn watch_foreground(shell: &mut Shell, pid: i32, command: &str) -> i32 { + let mut signaled = 0; let mut exit_status = 0; - let mut found = 0; + let mut status; + loop { unsafe { - let mut status = 0; - let pid = waitpid(-1, &mut status, WUNTRACED); - match pid { + status = 0; + match waitpid(pid, &mut status, WUNTRACED) { -1 => { let error = errno(); match error.0 { - ECHILD => break exit_status, + ECHILD if signaled == 0 => break exit_status, + ECHILD => break signaled, _ => { - eprintln!("ion: {}", error); + eprintln!("ion: waitpid error: {}", error); break FAILURE; } } } 0 => (), - _ if WIFEXITED(status) => { - let status = WEXITSTATUS(status) as i32; - if pid == (last_pid as i32) { - found |= LAST; - } - - if pid == (first_pid as i32) { - found |= FIRST; - } - - if found == FIRST + LAST { - break status; - } else { - drop_command(pid); - exit_status = status; - } + _pid if WIFEXITED(status) => { + exit_status = WEXITSTATUS(status) as i32; } - _ if WIFSIGNALED(status) => { - eprintln!("ion: process ended by signal"); + _pid if WIFSIGNALED(status) => { let signal = WTERMSIG(status); + if signal == SIGPIPE { continue } + eprintln!("ion: process ended by signal {}", signal); match signal { SIGINT => { shell.foreground_send(signal as i32); @@ -137,15 +113,15 @@ where } _ => { shell.handle_signal(signal); - shell.exit(TERMINATED); } } - break TERMINATED; + signaled = 128 + signal as i32; } - _ if WIFSTOPPED(status) => { - shell.send_to_background(pid as u32, ProcessState::Stopped, get_command()); + _pid if WIFSTOPPED(status) => { + // TODO: Rework background control + shell.send_to_background(pid as u32, ProcessState::Stopped, command.into()); shell.break_flow = true; - break TERMINATED; + break 128 + signal as i32; } _ => (), } diff --git a/src/lib/sys/unix/mod.rs b/src/lib/sys/unix/mod.rs index 2d191fa88c6bb945b48b7abcbdcec97545b1c7bf..073abbc38b2b9fe538c644b947fa582f396dfcbf 100644 --- a/src/lib/sys/unix/mod.rs +++ b/src/lib/sys/unix/mod.rs @@ -3,7 +3,7 @@ extern crate libc; pub mod job_control; pub mod signals; -use libc::{c_char, c_int, pid_t, sighandler_t}; +use libc::{c_char, c_int, pid_t, sighandler_t, waitpid, ECHILD, EINTR, WEXITSTATUS, WUNTRACED}; use std::{io, ptr}; use std::env; use std::ffi::CString; @@ -19,6 +19,7 @@ pub(crate) const SIGTERM: i32 = libc::SIGTERM; pub(crate) const SIGCONT: i32 = libc::SIGCONT; pub(crate) const SIGSTOP: i32 = libc::SIGSTOP; pub(crate) const SIGTSTP: i32 = libc::SIGTSTP; +pub(crate) const SIGPIPE: i32 = libc::SIGPIPE; pub(crate) const STDOUT_FILENO: i32 = libc::STDOUT_FILENO; pub(crate) const STDERR_FILENO: i32 = libc::STDERR_FILENO; @@ -34,20 +35,35 @@ pub(crate) fn is_root() -> bool { unsafe { libc::geteuid() == 0 } } pub unsafe fn fork() -> io::Result<u32> { cvt(libc::fork()).map(|pid| pid as u32) } +pub fn wait_for_interrupt(pid: u32) -> io::Result<()> { + let mut status = 0; + let mut result; + + loop { + result = unsafe { waitpid(pid as i32, &mut status, WUNTRACED) }; + if result == -1 { + if errno() == EINTR { continue } + break Err(io::Error::from_raw_os_error(errno())); + } + break Ok(()); + } +} + pub fn wait_for_child(pid: u32) -> io::Result<u8> { let mut status; - use libc::{waitpid, ECHILD, WEXITSTATUS}; + let mut result; loop { status = 0; - match unsafe { waitpid(pid as i32, &mut status, 0) } { - -1 if errno() == ECHILD => break, - -1 => return Err(io::Error::from_raw_os_error(errno())), - _ => () + result = unsafe { waitpid(pid as i32, &mut status, WUNTRACED) }; + if result == -1 { + break if errno() == ECHILD { + Ok(unsafe { WEXITSTATUS(status) as u8 }) + } else { + Err(io::Error::from_raw_os_error(errno())) + }; } } - - Ok(unsafe { WEXITSTATUS(status) as u8 }) } pub fn fork_exit(exit_status: i32) -> ! { unsafe { libc::_exit(exit_status) } }