diff --git a/examples/builtin_piping.ion b/examples/builtin_piping.ion new file mode 100644 index 0000000000000000000000000000000000000000..47402a93a78c896b6bef0e80e8cce9f33dace157 --- /dev/null +++ b/examples/builtin_piping.ion @@ -0,0 +1,6 @@ +matches Foo '([A-Z])\w+' && echo true +matches foo '([A-Z])\w+' || echo false + +fn foobar x; end + +fn | tr '[a-z]' '[A-Z]' diff --git a/examples/builtin_piping.out b/examples/builtin_piping.out new file mode 100644 index 0000000000000000000000000000000000000000..cf77dde3309b35ced0f45d4df83c57d33ceb756a --- /dev/null +++ b/examples/builtin_piping.out @@ -0,0 +1,4 @@ +true +false +# FUNCTIONS + FOOBAR diff --git a/src/shell/fork.rs b/src/shell/fork.rs index 93ef914bee1e3c7ebb6f8c152aae61bad797d434..36bacc362cdeebc7ed6986b585130c88e5cc69b8 100644 --- a/src/shell/fork.rs +++ b/src/shell/fork.rs @@ -5,8 +5,8 @@ pub fn create_process_group(pgid: u32) { let _ = sys::setpgid(0, pgid); } -use std::process::{Command, exit}; -use super::job::JobKind; +use std::process::exit; +use super::job::{RefinedJob, JobKind}; use super::job_control::{JobControl, ProcessState}; use super::Shell; use super::signals; @@ -17,7 +17,7 @@ use super::pipe::pipe; /// the given commands in the child fork. pub fn fork_pipe ( shell: &mut Shell, - commands: Vec<(Command, JobKind)>, + commands: Vec<(RefinedJob, JobKind)>, command_name: String ) -> i32 { match unsafe { sys::fork() } { diff --git a/src/shell/job.rs b/src/shell/job.rs index 02d75ae2ec2fb9cb7b59c67110f8f166987b1ab9..985a230ecff4940d974becee0646f57f3eacf18b 100644 --- a/src/shell/job.rs +++ b/src/shell/job.rs @@ -1,9 +1,11 @@ -use std::process::Command; +use std::process::{Command, Stdio}; +use std::os::unix::io::{RawFd, FromRawFd}; //use glob::glob; use parser::{expand_string, ExpanderFunctions}; use parser::peg::RedirectFrom; use smallstring::SmallString; +use sys; use types::*; #[derive(Debug, PartialEq, Clone, Copy)] @@ -19,48 +21,152 @@ pub struct Job { impl Job { pub fn new(args: Array, kind: JobKind) -> Self { let command = SmallString::from_str(&args[0]); - Job { - command: command, - args: args, - kind: kind, - } + Job { command, args, kind } } /// Takes the current job's arguments and expands them, one argument at a /// time, returning a new `Job` with the expanded arguments. pub fn expand(&mut self, expanders: &ExpanderFunctions) { - use smallvec::SmallVec; - - let mut expanded = SmallVec::new(); + let mut expanded = Array::new(); expanded.grow(self.args.len()); - { - for arg in self.args.drain().flat_map(|argument| expand_string(&argument, expanders, false)) { + expanded.extend(self.args.drain().flat_map(|arg| { + expand_string(&arg, expanders, false) + })); + self.args = expanded; + } + +} + +/// This represents a job that has been processed and expanded to be run +/// as part of some pipeline +pub enum RefinedJob { + /// An external program that is executed by this shell + External(Command), + /// A procedure embedded into Ion + Builtin { + /// 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<RawFd>, + /// A file corresponding to the standard output for this builtin + stdout: Option<RawFd>, + /// A file corresponding to the standard error for this builtin + stderr: Option<RawFd>, + } +} - expanded.push(arg); +macro_rules! set_field { + ($self:expr, $field:ident, $arg:expr) => { + match *$self { + RefinedJob::External(ref mut command) => { + unsafe { + command.$field(Stdio::from_raw_fd($arg)); + } + } + RefinedJob::Builtin { ref mut $field, .. } => { + *$field = Some($arg); } } + } +} - self.args = expanded; - self.command = self.args.first().map_or("".into(), |c| c.clone().into()); +impl RefinedJob { + + pub fn builtin(name: Identifier, args: Array) -> Self { + RefinedJob::Builtin { + name, + args, + stdin: None, + stdout: None, + stderr: None + } } - pub fn build_command_external(&mut self) -> Command { - let mut command = Command::new(&self.command); - for arg in self.args.drain().skip(1) { - command.arg(arg); + pub fn stdin(&mut self, fd: RawFd) { + set_field!(self, stdin, fd); + } + + pub fn stdout(&mut self, fd: RawFd) { + set_field!(self, stdout, fd); + } + + pub fn stderr(&mut self, fd: RawFd) { + set_field!(self, stderr, fd); + } + + /// Returns a short description of this job: often just the command + /// or builtin name + pub fn short(&self) -> String { + match *self { + RefinedJob::External(ref cmd) => { + format!("{:?}", cmd).split('"').nth(1).unwrap_or("").to_string() + }, + RefinedJob::Builtin { ref name, .. } => { + name.to_string() + } } - command } - pub fn build_command_builtin(&mut self) -> Command { - use std::env; - let process = env::current_exe().unwrap(); - let mut command = Command::new(process); - command.arg("-c"); - command.arg(&self.command); - for arg in self.args.drain().skip(1) { - command.arg(arg); + /// Returns a long description of this job: the commands and arguments + pub 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, .. } => { + format!("{}", args.join(" ")) + } } - command } + +} + +impl Drop for RefinedJob { + + // This is needed in order to ensure that the parent instance of RefinedJob + // cleans up after its own `RawFd`s; otherwise these would never be properly + // closed, never sending EOF, causing any process reading from these + // `RawFd`s to halt indefinitely. + fn drop(&mut self) { + match *self { + RefinedJob::External(ref mut cmd) => { + drop(cmd); + }, + RefinedJob::Builtin { + ref mut name, + ref mut args, + ref mut stdin, + ref mut stdout, + ref mut stderr, + } => { + fn close(fd: Option<RawFd>) { + if let Some(fd) = fd { + if let Err(e) = sys::close(fd) { + eprintln!("ion: failed to close file '{}': {}", fd, e); + } + } + } + drop(name); + drop(args); + close(*stdin); + close(*stdout); + close(*stderr); + } + } + } + } diff --git a/src/shell/pipe.rs b/src/shell/pipe.rs index 1d6dbf098e901fb6dd08b705bc04e9c9ece6c102..d9df241928b4e7453285757cffccf7fc006c4266 100644 --- a/src/shell/pipe.rs +++ b/src/shell/pipe.rs @@ -1,21 +1,30 @@ use std::io::{self, Error, Write}; -use std::process::{Stdio, Command}; -use std::os::unix::io::{FromRawFd, IntoRawFd}; +use std::process::{Command, exit}; +use std::os::unix::io::{FromRawFd, IntoRawFd, RawFd}; use std::os::unix::process::CommandExt; use std::fs::{File, OpenOptions}; use super::flags::*; use super::fork::{fork_pipe, create_process_group}; use super::job_control::JobControl; use super::{JobKind, Shell}; +use super::job::RefinedJob; use super::status::*; use super::signals::{self, SignalHandler}; use parser::peg::{Pipeline, Input, RedirectFrom}; use sys; -/// Create an instance of Stdio from a byte slice that will echo the -/// contents of the slice when read. This can be called with owned or -/// borrowed strings -pub unsafe fn stdin_of<T: AsRef<[u8]>>(input: T) -> Result<Stdio, Error> { +/// Use dup2 to replace `old` with `new` using `old`s file descriptor ID +pub fn redir(old: RawFd, new: RawFd) { + if let Err(e) = sys::dup2(old, new) { + eprintln!("ion: could not duplicate {} to {}: {}", old, new, e); + } +} + + +/// Create an OS pipe and write the contents of a byte slice to one end +/// such that reading from this pipe will produce the byte slice. Return +/// A file descriptor representing the read end of the pipe. +pub unsafe fn stdin_of<T: AsRef<[u8]>>(input: T) -> Result<RawFd, Error> { let (reader, writer) = sys::pipe2(sys::O_CLOEXEC)?; let mut infile = File::from_raw_fd(writer); // Write the contents; make sure to use write_all so that we block until @@ -24,36 +33,7 @@ pub unsafe fn stdin_of<T: AsRef<[u8]>>(input: T) -> Result<Stdio, Error> { infile.flush()?; // `infile` currently owns the writer end RawFd. If we just return the reader end // and let `infile` go out of scope, it will be closed, sending EOF to the reader! - Ok(Stdio::from_raw_fd(reader)) -} - -/// 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) = sys::pipe2(sys::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(()) + Ok(reader) } /// This function serves three purposes: @@ -81,25 +61,39 @@ impl<'a> PipelineExecution for Shell<'a> { fn execute_pipeline(&mut self, pipeline: &mut Pipeline) -> i32 { let background_string = check_if_background_job(&pipeline, self.flags & PRINT_COMMS != 0); - // Generate a list of commands from the given pipeline - let mut piped_commands: Vec<(Command, JobKind)> = pipeline.jobs - .drain(..).map(|mut job| { - if self.builtins.contains_key(&job.command.as_ref()) { - (job.build_command_builtin(), job.kind) - } else { - (job.build_command_external(), job.kind) - } - }).collect(); + let mut piped_commands: Vec<(RefinedJob, JobKind)> = { + pipeline.jobs + .drain(..) + .map(|mut job| { + let refined = { + if self.builtins.contains_key::<str>( + job.command.as_ref() + ) { + RefinedJob::builtin( + job.command, + job.args.drain().collect() + ) + } else { + let mut command = Command::new(job.command); + for arg in job.args.drain().skip(1) { + command.arg(arg); + } + RefinedJob::External(command) + } + }; + (refined, job.kind) + }) + .collect() + }; match pipeline.stdin { None => (), Some(Input::File(ref filename)) => { if let Some(command) = piped_commands.first_mut() { match File::open(filename) { - Ok(file) => unsafe { - command.0.stdin(Stdio::from_raw_fd(file.into_raw_fd())); - }, + Ok(file) => command.0.stdin(file.into_raw_fd()), Err(e) => { - eprintln!("ion: failed to redirect '{}' into stdin: {}", filename, e); + eprintln!("ion: failed to redirect '{}' into stdin: {}", + filename, e) } } } @@ -112,8 +106,11 @@ impl<'a> PipelineExecution for Shell<'a> { command.0.stdin(stdio); }, Err(e) => { - eprintln!("ion: failed to redirect herestring '{}' into stdin: {}", - string, e); + eprintln!( + "ion: failed to redirect herestring '{}' into stdin: {}", + string, + e + ); } } } @@ -128,20 +125,20 @@ impl<'a> PipelineExecution for Shell<'a> { File::create(&stdout.file) }; match file { - Ok(f) => unsafe { - match stdout.from { - RedirectFrom::Both => { - let fd = f.into_raw_fd(); - command.0.stderr(Stdio::from_raw_fd(fd)); - command.0.stdout(Stdio::from_raw_fd(fd)); - }, - RedirectFrom::Stderr => { - command.0.stderr(Stdio::from_raw_fd(f.into_raw_fd())); - }, - RedirectFrom::Stdout => { - command.0.stdout(Stdio::from_raw_fd(f.into_raw_fd())); - }, - } + Ok(f) => match stdout.from { + RedirectFrom::Both => { + match f.try_clone() { + Ok(f_copy) => { + command.0.stdout(f.into_raw_fd()); + command.0.stderr(f_copy.into_raw_fd()); + }, + Err(e) => { + eprintln!("ion: failed to redirect both stderr and stdout into file '{:?}': {}", f, e); + } + } + }, + RedirectFrom::Stderr => command.0.stderr(f.into_raw_fd()), + RedirectFrom::Stdout => command.0.stdout(f.into_raw_fd()), }, Err(err) => { let stderr = io::stderr(); @@ -171,7 +168,7 @@ impl<'a> PipelineExecution for Shell<'a> { /// This function will panic if called with an empty slice pub fn pipe ( shell: &mut Shell, - commands: Vec<(Command, JobKind)>, + commands: Vec<(RefinedJob, JobKind)>, foreground: bool ) -> i32 { let mut previous_status = SUCCESS; @@ -194,9 +191,10 @@ pub fn pipe ( match kind { JobKind::Pipe(mut mode) => { - // We need to remember the commands as they own the file descriptors that are - // created by create_pipe. We purposfully drop the pipes that are - // owned by a given command in `wait` in order to close those pipes, sending + // 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 @@ -205,63 +203,133 @@ pub fn pipe ( let mut pgid = 0; // 0 means the PGID is not set yet. macro_rules! spawn_proc { - ($cmd:expr) => {{ - let child = $cmd.before_exec(move || { - signals::unblock(); - create_process_group(pgid); - Ok(()) - }).spawn(); - match child { - Ok(child) => { - if pgid == 0 { - pgid = child.id(); - if foreground { - let _ = sys::tcsetpgrp(0, pgid); + ($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 { + let _ = sys::tcsetpgrp(0, pgid); + } + } + shell.foreground.push(child.id()); + children.push(child.id()); + }, + Err(e) => { + eprintln!("ion: failed to spawn `{}`: {}", + short, e); + return NO_SUCH_COMMAND + } + } + } + RefinedJob::Builtin { ref name, + ref args, + ref stdout, + ref stderr, + ref stdin, } => + { + match unsafe { sys::fork() } { + Ok(0) => { + signals::unblock(); + create_process_group(pgid); + let args: Vec<&str> = args + .iter() + .map(|x| x as &str).collect(); + builtin(shell, + name, + &args, + *stdout, + *stderr, + *stdin); + }, + Ok(pid) => { + if pgid == 0 { + pgid = pid; + if foreground { + let _ = sys::tcsetpgrp(0, pgid); + } + } + shell.foreground.push(pid); + children.push(pid); + }, + Err(e) => { + eprintln!("ion: failed to fork {}: {}", + short, + e); } } - shell.foreground.push(child.id()); - children.push(child.id()); - }, - Err(e) => { - eprintln!("ion: failed to spawn `{}`: {}", get_command_name($cmd), e); - return NO_SUCH_COMMAND } } - }}; + }; } // Append other jobs until all piped jobs are running while let Some((mut child, ckind)) = commands.next() { - if let Err(e) = unsafe { - create_pipe(&mut parent, &mut child, mode) - } { - eprintln!("ion: failed to create pipe for redirection: {:?}", e); + match sys::pipe2(sys::O_CLOEXEC) { + Err(e) => { + eprintln!("ion: failed to create pipe: {:?}", e); + }, + Ok((reader, writer)) => { + child.stdin(reader); + match mode { + RedirectFrom::Stderr => { + parent.stderr(writer); + }, + RedirectFrom::Stdout => { + parent.stdout(writer); + }, + RedirectFrom::Both => { + let temp = unsafe { + File::from_raw_fd(writer) + }; + match temp.try_clone() { + Err(e) => { + eprintln!("ion: failed to redirect stdout and stderr: {}", e); + } + Ok(duped) => { + parent.stderr(temp.into_raw_fd()); + parent.stdout(duped.into_raw_fd()); + } + } + } + } + } } - spawn_proc!(&mut parent); + spawn_proc!(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 + // 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); + spawn_proc!(child); remember.push(child); break } } - previous_kind = kind; previous_status = wait(shell, children, remember); if previous_status == TERMINATED { - terminate_fg(shell); + shell.foreground_send(sys::SIGTERM); return previous_status; } } _ => { - previous_status = execute_command(shell, &mut parent, foreground); + previous_status = execute(shell, &mut parent, foreground); previous_kind = kind; } } @@ -272,31 +340,53 @@ pub fn pipe ( previous_status } -fn terminate_fg(shell: &mut Shell) { - shell.foreground_send(sys::SIGTERM); -} - -fn execute_command(shell: &mut Shell, command: &mut Command, foreground: bool) -> i32 { - match command.before_exec(move || { - signals::unblock(); - create_process_group(0); - Ok(()) - }).spawn() { - Ok(child) => { - if foreground { - let _ = sys::tcsetpgrp(0, child.id()); +fn execute(shell: &mut Shell, 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 { + let _ = sys::tcsetpgrp(0, child.id()); + } + shell.watch_foreground(child.id(), child.id(), move || long, |_| ()) + }, + Err(e) => { + if e.kind() == io::ErrorKind::NotFound { + eprintln!("ion: command not found: {}", short) + } else { + eprintln!("ion: error spawning process: {}", e) + }; + FAILURE + } + } + } + RefinedJob::Builtin { ref name, ref args, ref stdin, ref stdout, ref stderr } => { + match unsafe { sys::fork() } { + Ok(0) => { + signals::unblock(); + create_process_group(0); + let args: Vec<&str> = args.iter().map(|x| x as &str).collect(); + builtin(shell, name, &args, *stdout, *stderr, *stdin) + }, + Ok(pid) => { + if foreground { + let _ = sys::tcsetpgrp(0, pid); + } + shell.watch_foreground(pid, pid, move || long, |_| ()) + }, + Err(e) => { + eprintln!("ion: fork error for '{}': {}", short, e); + FAILURE + } } - shell.watch_foreground(child.id(), child.id(), || get_full_command(command), |_| ()) - }, - Err(e) => { - let stderr = io::stderr(); - let mut stderr = stderr.lock(); - let _ = if e.kind() == io::ErrorKind::NotFound { - writeln!(stderr, "ion: Command not found: {}", get_command_name(command)) - } else { - writeln!(stderr, "ion: Error spawning process: {}", e) - }; - FAILURE } } } @@ -306,14 +396,17 @@ fn execute_command(shell: &mut Shell, command: &mut Command, foreground: bool) - fn wait ( shell: &mut Shell, mut children: Vec<u32>, - mut commands: Vec<Command> + mut commands: Vec<RefinedJob> ) -> i32 { // TODO: Find a way to only do this when absolutely necessary. - let as_string = commands.iter().map(get_full_command) - .collect::<Vec<String>>().join(" | "); + let as_string = commands.iter() + .map(RefinedJob::long) + .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]; @@ -326,22 +419,47 @@ fn wait ( }) } -fn get_command_name(command: &Command) -> String { - format!("{:?}", command).split('"').nth(1).unwrap_or("").to_string() -} -fn get_full_command(command: &Command) -> String { - let command = format!("{:?}", command); - 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); +/// Execute a builtin in the current process. Note that this will exit +/// the current process with the return code of the builtin +/// # Args +/// * `shell`: A `Shell` that forwards relevant information to the builtin +/// * `name`: Name of the builtin to execute. +/// * `stdin`, `stdout`, `stderr`: File descriptors that will replace the +/// respective standard streams if they are not `None` +/// # Preconditions +/// * `shell.builtins.contains_key(name)`; otherwise this function will panic +fn builtin( + shell: &mut Shell, + name: &str, + args: &[&str], + stdout: Option<RawFd>, + stderr: Option<RawFd>, + stdin: Option<RawFd>, +) -> ! { + /// Close a file descriptor by opening a `File` and letting it drop + fn close(fd: Option<RawFd>) { + if let Some(fd) = fd { + if let Err(e) = sys::close(fd) { + eprintln!("ion: failed to close file '{}': {}", fd, e); + } } } - output + if let Some(fd) = stdin { + redir(fd, sys::STDIN_FILENO); + } + if let Some(fd) = stdout { + redir(fd, sys::STDOUT_FILENO); + } + if let Some(fd) = stderr { + redir(fd, sys::STDERR_FILENO); + } + // The precondition for this function asserts that there exists some `builtin` + // in `shell` named `name`, so we unwrap here + let builtin = shell.builtins.get(name).unwrap(); + let ret = (builtin.main)(args, shell); + close(stderr); + close(stdout); + close(stdin); + exit(ret) } diff --git a/src/sys/redox.rs b/src/sys/redox.rs index 2b891449a34df18c4ff2f7b9139f99c369e3137d..6aafefab001aee6fd10cb24b06339816cf5092a7 100644 --- a/src/sys/redox.rs +++ b/src/sys/redox.rs @@ -15,6 +15,12 @@ pub const SIGCONT: i32 = syscall::SIGCONT as i32; pub const SIGSTOP: i32 = syscall::SIGSTOP as i32; pub const SIGTSTP: i32 = syscall::SIGTSTP as i32; +/// XXX: These values were infered from the Redox port of `newlib` and may be +/// sensitive to changes in redox; +pub const STDIN_FILENO: i32 = 0; +pub const STDOUT_FILENO: i32 = 1; +pub const STDERR_FILENO: i32 = 2; + pub unsafe fn fork() -> io::Result<u32> { cvt(syscall::clone(0)).map(|pid| pid as u32) } @@ -66,6 +72,14 @@ pub fn tcsetpgrp(tty_fd: RawFd, pgid: u32) -> io::Result<()> { cvt(res).and(Ok(())) } +pub fn dup2(old: RawFd, new: RawFd) -> io::Result<RawFd> { + syscall::call::dup2(old, new, &[]).map_err(|e| io::Error::from_raw_os_err(err.errno)) +} + +pub fn close(fd: RawFd) -> io::Result<()> { + cvt(syscall::call::close(fd)).and(Ok()) +} + // Support function for converting syscall error to io error fn cvt(result: Result<usize, syscall::Error>) -> io::Result<usize> { result.map_err(|err| io::Error::from_raw_os_error(err.errno)) diff --git a/src/sys/unix.rs b/src/sys/unix.rs index aee4ced26269e181067280f68f52a7128f930a8f..c6cd3e2125827b5c252d77a3f17eb583c94063c2 100644 --- a/src/sys/unix.rs +++ b/src/sys/unix.rs @@ -14,6 +14,10 @@ pub const SIGCONT: i32 = libc::SIGCONT; pub const SIGSTOP: i32 = libc::SIGSTOP; pub const SIGTSTP: i32 = libc::SIGTSTP; +pub const STDOUT_FILENO: i32 = libc::STDOUT_FILENO; +pub const STDERR_FILENO: i32 = libc::STDERR_FILENO; +pub const STDIN_FILENO: i32 = libc::STDIN_FILENO; + pub unsafe fn fork() -> io::Result<u32> { cvt(libc::fork()).map(|pid| pid as u32) } @@ -58,6 +62,14 @@ pub fn tcsetpgrp(fd: RawFd, pgrp: u32) -> io::Result<()> { cvt(unsafe { libc::tcsetpgrp(fd as c_int, pgrp as pid_t) }).and(Ok(())) } +pub fn dup2(old: RawFd, new: RawFd) -> io::Result<RawFd> { + cvt(unsafe { libc::dup2(old, new) }) +} + +pub fn close(fd: RawFd) -> io::Result<()> { + cvt(unsafe { libc::close(fd) }).and(Ok(())) +} + // Support functions for converting libc return values to io errors { trait IsMinusOne { fn is_minus_one(&self) -> bool; @@ -136,7 +148,7 @@ pub mod job_control { use shell::Shell; use libc::{self, pid_t}; - use nix::sys::wait::{waitpid, WaitStatus, WNOHANG, WUNTRACED}; + use nix::sys::wait::{waitpid, wait, WaitStatus, WNOHANG, WUNTRACED}; #[cfg(not(target_os = "macos"))] use nix::sys::wait::{WCONTINUED}; @@ -213,7 +225,7 @@ pub mod job_control { pub fn watch_foreground<'a, F, D>( shell: &mut Shell<'a>, - pid: u32, + _pid: u32, last_pid: u32, get_command: F, mut drop_command: D, @@ -224,7 +236,7 @@ pub mod job_control { { let mut exit_status = 0; loop { - match waitpid(-(pid as pid_t), Some(WUNTRACED)) { + match wait() { Ok(WaitStatus::Exited(pid, status)) => if pid == (last_pid as i32) { break status as i32; } else {