From ab6a6d95eaf0d06bc0897f02f973ff2a9a43eb47 Mon Sep 17 00:00:00 2001 From: Xavier L'Heureux <xavier.lheureux@icloud.com> Date: Thu, 27 Jun 2019 09:58:19 -0400 Subject: [PATCH] Use nix as a safe abstraction layer Breaking change: if a signal is terminates a subprocess, the last status is no more set to 128 + signal --- Cargo.lock | 15 +- Cargo.toml | 2 +- src/binary/builtins.rs | 8 +- src/binary/mod.rs | 5 +- src/lib/builtins/mod.rs | 10 +- src/lib/lib.rs | 2 + src/lib/parser/lexers/assignments/operator.rs | 4 +- .../parser/lexers/assignments/primitive.rs | 2 +- src/lib/shell/flow.rs | 21 ++- src/lib/shell/fork.rs | 49 ++++--- src/lib/shell/fork_function.rs | 6 +- src/lib/shell/mod.rs | 26 ++-- src/lib/shell/pipe_exec/foreground.rs | 12 +- src/lib/shell/pipe_exec/fork.rs | 44 +++--- src/lib/shell/pipe_exec/job_control.rs | 120 ++++++++-------- src/lib/shell/pipe_exec/mod.rs | 133 +++++++++-------- src/lib/shell/pipe_exec/pipes.rs | 8 +- src/lib/shell/pipe_exec/streams.rs | 20 +-- src/lib/shell/shell_expand.rs | 12 +- src/lib/shell/signals.rs | 12 +- src/lib/shell/sys/mod.rs | 135 ------------------ src/lib/shell/sys/signals.rs | 35 ++--- src/lib/shell/variables.rs | 20 +-- src/main.rs | 12 +- 24 files changed, 298 insertions(+), 415 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6ff03bf5..9bf4b393 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -417,8 +417,8 @@ dependencies = [ "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", "lexical 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", "liner 0.5.0 (git+https://gitlab.redox-os.org/redox-os/liner)", + "nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)", "object-pool 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "permutate 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -524,6 +524,18 @@ name = "memoffset" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "nix" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", + "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "nodrop" version = "0.1.13" @@ -1225,6 +1237,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c" "checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39" "checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3" +"checksum nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce" "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" "checksum num 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cf4825417e1e1406b3782a8ce92f4d53f26ec055e3622e1881ca8e9f5f9e08db" "checksum num-bigint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "57450397855d951f1a41305e54851b1a7b8f5d2e349543a02a2effe25459f718" diff --git a/Cargo.toml b/Cargo.toml index c6fd60b6..8796581d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,10 +80,10 @@ lexical = "2.0" object-pool = "0.3.1" auto_enums = "0.5.5" structopt = "^0.2" -libc = "0.2" atty = "0.2" permutate = "0.3" dirs = "1.0" +nix = "0.14" [target."cfg(all(unix, not(target_os = \"redox\")))".dependencies] users = "0.9" diff --git a/src/binary/builtins.rs b/src/binary/builtins.rs index 14f0c2aa..a700ef1c 100644 --- a/src/binary/builtins.rs +++ b/src/binary/builtins.rs @@ -1,5 +1,5 @@ -use ion_shell::{builtin, builtins::Status, types::Str, Shell}; -use libc::SIGTERM; +use ion_shell::{builtin, builtins::Status, types::Str, Shell, Signal}; +use nix::{sys::signal, unistd::Pid}; use std::{error::Error, os::unix::process::CommandExt, process::Command}; #[builtin( @@ -13,7 +13,7 @@ DESCRIPTION returning to the parent process. It can be resumed by sending it SIGCONT." )] pub fn suspend(args: &[Str], _shell: &mut Shell<'_>) -> Status { - let _ = unsafe { libc::kill(0, libc::SIGSTOP) }; + signal::kill(Pid::this(), Signal::SIGSTOP).unwrap(); Status::SUCCESS } @@ -28,7 +28,7 @@ DESCRIPTION )] pub fn exit(args: &[Str], shell: &mut Shell<'_>) -> Status { // Kill all active background tasks before exiting the shell. - shell.background_send(SIGTERM); + shell.background_send(Signal::SIGTERM).expect("Could not terminate background jobs"); let exit_code = args .get(1) .and_then(|status| status.parse::<i32>().ok()) diff --git a/src/binary/mod.rs b/src/binary/mod.rs index a8720b0b..d2f1edf2 100644 --- a/src/binary/mod.rs +++ b/src/binary/mod.rs @@ -11,10 +11,9 @@ use ion_shell::{ builtins::{man_pages, Status}, expansion::Expander, parser::Terminator, - types, Shell, + types, Shell, Signal, }; use itertools::Itertools; -use libc::SIGHUP; use liner::{Buffer, Context, KeyBindings}; use std::{cell::RefCell, fs::OpenOptions, io, path::Path, rc::Rc}; use xdg::BaseDirectories; @@ -123,7 +122,7 @@ impl<'a> InteractiveShell<'a> { // and waiting for the history thread in the background to finish. if shell.opts().huponexit { shell.resume_stopped(); - shell.background_send(SIGHUP); + shell.background_send(Signal::SIGHUP).expect("Failed to prepare for exit"); } context_bis.borrow_mut().history.commit_to_file(); }; diff --git a/src/lib/builtins/mod.rs b/src/lib/builtins/mod.rs index 5c8dec67..3a11d102 100644 --- a/src/lib/builtins/mod.rs +++ b/src/lib/builtins/mod.rs @@ -35,7 +35,7 @@ pub use self::{ }; use crate as ion_shell; use crate::{ - shell::{sys, Capture, Shell, Value}, + shell::{Capture, Shell, Value}, types, }; use builtins_proc::builtin; @@ -806,13 +806,7 @@ pub fn isatty(args: &[types::Str], _: &mut Shell<'_>) -> Status { let pid = args[1].parse::<i32>(); match pid { - Ok(r) => { - if sys::isatty(r) { - Status::TRUE - } else { - Status::FALSE - } - } + Ok(r) => nix::unistd::isatty(r).unwrap().into(), Err(_) => Status::error("ion: isatty given bad number"), } } else { diff --git a/src/lib/lib.rs b/src/lib/lib.rs index 094a9a28..2babeb11 100644 --- a/src/lib/lib.rs +++ b/src/lib/lib.rs @@ -79,6 +79,8 @@ pub mod expansion; mod memory; mod shell; +pub use nix::sys::signal::Signal; + pub(crate) use self::memory::IonPool; pub use crate::{ builtins::{BuiltinFunction, BuiltinMap}, diff --git a/src/lib/parser/lexers/assignments/operator.rs b/src/lib/parser/lexers/assignments/operator.rs index 4c75956e..5dc43421 100644 --- a/src/lib/parser/lexers/assignments/operator.rs +++ b/src/lib/parser/lexers/assignments/operator.rs @@ -28,7 +28,7 @@ pub enum Operator { } impl Operator { - pub(crate) fn parse_single(data: u8) -> Option<Operator> { + pub(crate) fn parse_single(data: u8) -> Option<Self> { match data { b'+' => Some(Operator::Add), b'-' => Some(Operator::Subtract), @@ -39,7 +39,7 @@ impl Operator { } } - pub(crate) fn parse_double(data: &[u8]) -> Option<Operator> { + pub(crate) fn parse_double(data: &[u8]) -> Option<Self> { match data { b"//" => Some(Operator::IntegerDivide), b"**" => Some(Operator::Exponent), diff --git a/src/lib/parser/lexers/assignments/primitive.rs b/src/lib/parser/lexers/assignments/primitive.rs index 1980dbb9..571badd1 100644 --- a/src/lib/parser/lexers/assignments/primitive.rs +++ b/src/lib/parser/lexers/assignments/primitive.rs @@ -28,7 +28,7 @@ pub enum Primitive { } impl Primitive { - pub(crate) fn parse(data: &str) -> Option<Primitive> { + pub(crate) fn parse(data: &str) -> Option<Self> { match data { "str" => Some(Primitive::Str), "[str]" => Some(Primitive::StrArray), diff --git a/src/lib/shell/flow.rs b/src/lib/shell/flow.rs index 85885905..05665785 100644 --- a/src/lib/shell/flow.rs +++ b/src/lib/shell/flow.rs @@ -15,6 +15,7 @@ use crate::{ }; use err_derive::Error; use itertools::Itertools; +use nix::unistd::Pid; #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] pub enum Condition { @@ -155,12 +156,11 @@ impl<'a> Shell<'a> { Self::insert_into_block(block, last_statement)?; // Merge last Case back and pop off Match too let match_stm = block.pop().unwrap(); - if !block.is_empty() { + if block.is_empty() { + Ok(Some(match_stm)) + } else { Self::insert_into_block(block, match_stm)?; - Ok(None) - } else { - Ok(Some(match_stm)) } } else { Self::insert_into_block(block, last_statement)?; @@ -440,8 +440,8 @@ impl<'a> Shell<'a> { _ => {} } if let Some(signal) = signals::SignalHandler.next() { - self.handle_signal(signal); - Err(IonError::from(PipelineError::Interrupted(0, signal))) + self.handle_signal(signal).map_err(PipelineError::KillFailed)?; + Err(IonError::from(PipelineError::Interrupted(Pid::this(), signal))) } else { Ok(Condition::NoOp) } @@ -682,7 +682,7 @@ mod tests { assert_eq!(cases.len(), 2); assert_eq!(cases.last().unwrap().statements.len(), 1); } else { - assert!(false); + panic!(); } } @@ -699,7 +699,7 @@ mod tests { flow_control.clear(); assert_eq!(flow_control.len(), 0); } else { - assert!(false); + panic!(); } } @@ -720,10 +720,7 @@ mod tests { let errs = vec![Statement::Else, Statement::End, Statement::Break, Statement::Continue]; for err in errs { - let res = Shell::insert_statement(&mut flow_control, err); - if res.is_ok() { - assert!(false); - } + assert!(Shell::insert_statement(&mut flow_control, err).is_err()); } } } diff --git a/src/lib/shell/fork.rs b/src/lib/shell/fork.rs index 8a3994c4..e36f7e33 100644 --- a/src/lib/shell/fork.rs +++ b/src/lib/shell/fork.rs @@ -1,19 +1,18 @@ use super::{sys, IonError, Shell}; +use nix::{ + fcntl::OFlag, + sys::wait::{self, WaitPidFlag, WaitStatus}, + unistd::{self, ForkResult, Pid}, +}; use std::{ fs::File, - io, os::unix::io::{AsRawFd, FromRawFd}, }; -pub fn wait_for_child(pid: u32) -> io::Result<u8> { +pub fn wait_for_child(pid: unistd::Pid) -> nix::Result<i32> { loop { - let mut status = 0; - if let Err(errno) = sys::waitpid(pid as i32, &mut status, libc::WUNTRACED) { - break if errno == libc::ECHILD { - Ok(sys::wexitstatus(status) as u8) - } else { - Err(io::Error::from_raw_os_error(errno)) - }; + if let WaitStatus::Exited(_, status) = wait::waitpid(pid, Some(WaitPidFlag::WUNTRACED))? { + break Ok(status); } } } @@ -58,10 +57,10 @@ pub struct Fork<'a, 'b: 'a> { /// in the future, once there's a better means of obtaining the exit status without having to /// wait on the PID. pub struct IonResult { - pub pid: u32, + pub child: Pid, pub stdout: Option<File>, pub stderr: Option<File>, - pub status: u8, + pub status: i32, } impl<'a, 'b> Fork<'a, 'b> { @@ -75,7 +74,7 @@ impl<'a, 'b> Fork<'a, 'b> { // If we are to capture stdout, create a pipe for capturing outputs. let outs = if self.capture as u8 & Capture::Stdout as u8 != 0 { - let fds = sys::pipe2(libc::O_CLOEXEC)?; + let fds = unistd::pipe2(OFlag::O_CLOEXEC)?; Some(unsafe { (File::from_raw_fd(fds.0), File::from_raw_fd(fds.1)) }) } else { None @@ -83,7 +82,7 @@ impl<'a, 'b> Fork<'a, 'b> { // And if we are to capture stderr, create a pipe for that as well. let errs = if self.capture as u8 & Capture::Stderr as u8 != 0 { - let fds = sys::pipe2(libc::O_CLOEXEC)?; + let fds = unistd::pipe2(OFlag::O_CLOEXEC)?; Some(unsafe { (File::from_raw_fd(fds.0), File::from_raw_fd(fds.1)) }) } else { None @@ -94,27 +93,27 @@ impl<'a, 'b> Fork<'a, 'b> { // be repeated. let null_file = File::open(sys::NULL_PATH); - match unsafe { sys::fork() }? { - 0 => { + match unistd::fork()? { + ForkResult::Child => { // Allow the child to handle it's own signal handling. sys::signals::unblock(); // Redirect standard output to a pipe, or /dev/null, if needed. if self.capture as u8 & Capture::IgnoreStdout as u8 != 0 { if let Ok(null) = null_file.as_ref() { - let _ = sys::dup2(null.as_raw_fd(), libc::STDOUT_FILENO); + let _ = unistd::dup2(null.as_raw_fd(), nix::libc::STDOUT_FILENO); } } else if let Some((_, write)) = outs { - let _ = sys::dup2(write.as_raw_fd(), libc::STDOUT_FILENO); + let _ = unistd::dup2(write.as_raw_fd(), nix::libc::STDOUT_FILENO); } // Redirect standard error to a pipe, or /dev/null, if needed. if self.capture as u8 & Capture::IgnoreStderr as u8 != 0 { if let Ok(null) = null_file.as_ref() { - let _ = sys::dup2(null.as_raw_fd(), libc::STDERR_FILENO); + let _ = unistd::dup2(null.as_raw_fd(), nix::libc::STDERR_FILENO); } } else if let Some((_, write)) = errs { - let _ = sys::dup2(write.as_raw_fd(), libc::STDERR_FILENO); + let _ = unistd::dup2(write.as_raw_fd(), nix::libc::STDERR_FILENO); } // Drop all the file descriptors that we no longer need. @@ -122,19 +121,19 @@ impl<'a, 'b> Fork<'a, 'b> { // Obtain ownership of the child's copy of the shell, and then configure it. let mut shell: Shell<'b> = unsafe { (self.shell as *const Shell<'b>).read() }; - shell.variables_mut().set("PID", sys::getpid().unwrap_or(0).to_string()); + shell.variables_mut().set("PID", unistd::getpid().to_string()); // Execute the given closure within the child's shell. if let Err(why) = child_func(&mut shell) { eprintln!("{}", why); - sys::fork_exit(-1); + unsafe { nix::libc::_exit(-1) }; } else { - sys::fork_exit(shell.previous_status.as_os_code()); + unsafe { nix::libc::_exit(shell.previous_status.as_os_code()) }; } } - pid => { + ForkResult::Parent { child } => { Ok(IonResult { - pid, + child, stdout: outs.map(|(read, write)| { drop(write); read @@ -144,7 +143,7 @@ impl<'a, 'b> Fork<'a, 'b> { read }), // `waitpid()` is required to reap the child. - status: wait_for_child(pid)?, + status: wait_for_child(child)?, }) } } diff --git a/src/lib/shell/fork_function.rs b/src/lib/shell/fork_function.rs index 157aaba4..cd45f1db 100644 --- a/src/lib/shell/fork_function.rs +++ b/src/lib/shell/fork_function.rs @@ -1,5 +1,5 @@ -use super::{fork::IonResult, sys, variables::Value, Capture, Shell}; -use std::process; +use super::{fork::IonResult, variables::Value, Capture, Shell}; +use nix::unistd::{self, Pid}; impl<'a> Shell<'a> { /// High-level function for executing a function programmatically. @@ -25,7 +25,7 @@ impl<'a> Shell<'a> { .and_then(result); // Ensure that the parent retains ownership of the terminal before exiting. - let _ = sys::tcsetpgrp(libc::STDIN_FILENO, process::id()); + let _ = unistd::tcsetpgrp(nix::libc::STDIN_FILENO, Pid::this()); output } else { Err(()) diff --git a/src/lib/shell/mod.rs b/src/lib/shell/mod.rs index bb186228..4e2acff6 100644 --- a/src/lib/shell/mod.rs +++ b/src/lib/shell/mod.rs @@ -39,6 +39,7 @@ use crate::{ }; use err_derive::Error; use itertools::Itertools; +use nix::sys::signal::{self, SigHandler}; use std::{ fs, io::{self, Write}, @@ -53,7 +54,7 @@ use std::{ pub enum IonError { /// The fork failed #[error(display = "failed to fork: {}", _0)] - Fork(#[error(cause)] io::Error), + Fork(#[error(cause)] nix::Error), /// Failed to setup capturing for function #[error(display = "error reading stdout of child: {}", _0)] CaptureFailed(#[error(cause)] io::Error), @@ -117,8 +118,8 @@ impl From<PipelineError> for IonError { fn from(cause: PipelineError) -> Self { IonError::PipelineExecutionError(cause) } } -impl From<io::Error> for IonError { - fn from(cause: io::Error) -> Self { IonError::Fork(cause) } +impl From<nix::Error> for IonError { + fn from(cause: nix::Error) -> Self { IonError::Fork(cause) } } impl From<expansion::Error<IonError>> for IonError { @@ -181,10 +182,11 @@ impl<'a> Shell<'a> { /// Install signal handlers necessary for the shell to work fn install_signal_handler() { extern "C" fn handler(signal: i32) { + let signal = signal::Signal::from_c_int(signal).unwrap(); let signal = match signal { - libc::SIGINT => signals::SIGINT, - libc::SIGHUP => signals::SIGHUP, - libc::SIGTERM => signals::SIGTERM, + signal::Signal::SIGINT => signals::SIGINT, + signal::Signal::SIGHUP => signals::SIGHUP, + signal::Signal::SIGTERM => signals::SIGTERM, _ => unreachable!(), }; @@ -194,13 +196,15 @@ impl<'a> Shell<'a> { extern "C" fn sigpipe_handler(signal: i32) { let _ = io::stdout().flush(); let _ = io::stderr().flush(); - sys::fork_exit(127 + signal); + unsafe { nix::libc::_exit(127 + signal) }; } - let _ = sys::signal(libc::SIGHUP, handler); - let _ = sys::signal(libc::SIGINT, handler); - let _ = sys::signal(libc::SIGTERM, handler); - let _ = sys::signal(libc::SIGPIPE, sigpipe_handler); + unsafe { + let _ = signal::signal(signal::Signal::SIGHUP, SigHandler::Handler(handler)); + let _ = signal::signal(signal::Signal::SIGINT, SigHandler::Handler(handler)); + let _ = signal::signal(signal::Signal::SIGTERM, SigHandler::Handler(handler)); + let _ = signal::signal(signal::Signal::SIGPIPE, SigHandler::Handler(sigpipe_handler)); + } } /// Create a new shell with default settings diff --git a/src/lib/shell/pipe_exec/foreground.rs b/src/lib/shell/pipe_exec/foreground.rs index 6e439ca0..a17bf89b 100644 --- a/src/lib/shell/pipe_exec/foreground.rs +++ b/src/lib/shell/pipe_exec/foreground.rs @@ -1,5 +1,7 @@ //! Contains the logic for enabling foreground management. +use nix::unistd::Pid; + // use std::sync::atomic::{AtomicU32, AtomicU8, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -24,7 +26,9 @@ pub struct Signals { } impl Signals { - pub fn was_grabbed(&self, pid: u32) -> bool { self.grab.load(Ordering::SeqCst) as u32 == pid } + pub fn was_grabbed(&self, pid: Pid) -> bool { + self.grab.load(Ordering::SeqCst) == pid.as_raw() as usize + } pub fn was_processed(&self) -> Option<BackgroundResult> { let reply = self.reply.load(Ordering::SeqCst) as u8; @@ -43,13 +47,15 @@ impl Signals { self.reply.store(ERRORED as usize, Ordering::SeqCst); } - pub fn reply_with(&self, status: i8) { + pub fn reply_with(&self, status: i32) { self.grab.store(0, Ordering::SeqCst); self.status.store(status as usize, Ordering::SeqCst); self.reply.store(REPLIED as usize, Ordering::SeqCst); } - pub fn signal_to_grab(&self, pid: u32) { self.grab.store(pid as usize, Ordering::SeqCst); } + pub fn signal_to_grab(&self, pid: Pid) { + self.grab.store(pid.as_raw() as usize, Ordering::SeqCst); + } pub const fn new() -> Self { Self { diff --git a/src/lib/shell/pipe_exec/fork.rs b/src/lib/shell/pipe_exec/fork.rs index 01f9a7fd..37e10e69 100644 --- a/src/lib/shell/pipe_exec/fork.rs +++ b/src/lib/shell/pipe_exec/fork.rs @@ -1,42 +1,48 @@ use super::{ - super::{sys, Shell}, + super::Shell, job_control::{BackgroundProcess, ProcessState}, }; use crate::{builtins::Status, expansion::pipelines::Pipeline}; +use nix::{ + sys::signal::{self, SigHandler, Signal}, + unistd::{self, ForkResult, Pid}, +}; impl<'a> Shell<'a> { /// Ensures that the forked child is given a unique process ID. - fn create_process_group(pgid: u32) { let _ = sys::setpgid(0, pgid); } + fn create_process_group() { unistd::setpgid(Pid::this(), Pid::this()).unwrap(); } /// Forks the shell, adding the child to the parent's background list, and executing /// the given commands in the child fork. pub(super) fn fork_pipe(&mut self, pipeline: Pipeline<'a>, state: ProcessState) -> Status { - match unsafe { sys::fork() } { - Ok(0) => { + match unistd::fork() { + Ok(ForkResult::Child) => { self.opts_mut().is_background_shell = true; - let _ = sys::reset_signal(libc::SIGINT); - let _ = sys::reset_signal(libc::SIGHUP); - let _ = sys::reset_signal(libc::SIGTERM); - let _ = sys::close(libc::STDIN_FILENO); + unsafe { + signal::signal(Signal::SIGINT, SigHandler::SigDfl).unwrap(); + signal::signal(Signal::SIGHUP, SigHandler::SigDfl).unwrap(); + signal::signal(Signal::SIGTERM, SigHandler::SigDfl).unwrap(); + } + unistd::close(nix::libc::STDIN_FILENO).unwrap(); // This ensures that the child fork has a unique PGID. - Self::create_process_group(0); + Self::create_process_group(); // After execution of it's commands, exit with the last command's status. - sys::fork_exit( - self.pipe(pipeline) - .unwrap_or_else(|err| { - eprintln!("{}", err); - Status::COULD_NOT_EXEC - }) - .as_os_code(), - ); + let code = self + .pipe(pipeline) + .unwrap_or_else(|err| { + eprintln!("{}", err); + Status::COULD_NOT_EXEC + }) + .as_os_code(); + unsafe { nix::libc::_exit(code) }; } - Ok(pid) => { + Ok(ForkResult::Parent { child }) => { if state != ProcessState::Empty { // The parent process should add the child fork's PID to the background. self.send_to_background(BackgroundProcess::new( - pid, + child, state, pipeline.to_string(), )); diff --git a/src/lib/shell/pipe_exec/job_control.rs b/src/lib/shell/pipe_exec/job_control.rs index 5b18d376..6c4bb000 100644 --- a/src/lib/shell/pipe_exec/job_control.rs +++ b/src/lib/shell/pipe_exec/job_control.rs @@ -1,23 +1,23 @@ use super::{ super::{signals, Shell}, foreground::{BackgroundResult, Signals}, - sys::{ - self, kill, strerror, waitpid, wcoredump, wexitstatus, wifcontinued, wifexited, - wifsignaled, wifstopped, wstopsig, wtermsig, - }, PipelineError, }; use crate::builtins::Status; -use libc::{ECHILD, SIGINT, SIGPIPE, WCONTINUED, WNOHANG, WUNTRACED}; +use nix::{ + sys::{ + signal::{self, Signal}, + wait::{self, WaitPidFlag, WaitStatus}, + }, + unistd::{self, Pid}, +}; use std::{ - fmt, process, + fmt, sync::Mutex, thread::{sleep, spawn}, time::Duration, }; -const OPTS: i32 = WUNTRACED | WCONTINUED | WNOHANG; - #[derive(Clone, Copy, Hash, Debug, PartialEq)] /// Defines whether the background process is running or stopped. pub enum ProcessState { @@ -43,19 +43,19 @@ impl fmt::Display for ProcessState { /// process is executing. Note that it is necessary to check if the process exists with the exists /// method. pub struct BackgroundProcess { - pid: u32, + pid: Pid, ignore_sighup: bool, state: ProcessState, name: String, } impl BackgroundProcess { - pub(super) const fn new(pid: u32, state: ProcessState, name: String) -> Self { + pub(super) const fn new(pid: Pid, state: ProcessState, name: String) -> Self { Self { pid, ignore_sighup: false, state, name } } /// Get the pid associated with the job - pub const fn pid(&self) -> u32 { self.pid } + pub const fn pid(&self) -> Pid { self.pid } /// Check if the process is still running pub fn is_running(&self) -> bool { self.state == ProcessState::Running } @@ -82,12 +82,12 @@ impl fmt::Display for BackgroundProcess { impl<'a> Shell<'a> { /// If a SIGTERM is received, a SIGTERM will be sent to all background processes /// before the shell terminates itself. - pub fn handle_signal(&self, signal: i32) -> bool { - if signal == libc::SIGTERM || signal == libc::SIGHUP { - self.background_send(signal); - true + pub fn handle_signal(&self, signal: Signal) -> nix::Result<bool> { + if signal == Signal::SIGTERM || signal == Signal::SIGHUP { + self.background_send(signal)?; + Ok(true) } else { - false + Ok(false) } } @@ -106,7 +106,7 @@ impl<'a> Shell<'a> { fn watch_background( fg: &Signals, processes: &Mutex<Vec<BackgroundProcess>>, - pgid: u32, + pgid: Pid, njob: usize, ) { let mut exit_status = 0; @@ -121,17 +121,19 @@ impl<'a> Shell<'a> { loop { let fg_was_grabbed = fg.was_grabbed(pgid); - let mut status = 0; - match waitpid(-(pgid as i32), &mut status, OPTS) { - Err(errno) if errno == ECHILD => { + let mut opts = WaitPidFlag::WUNTRACED; + opts.insert(WaitPidFlag::WCONTINUED); + opts.insert(WaitPidFlag::WNOHANG); + match wait::waitpid(Pid::from_raw(-pgid.as_raw()), Some(opts)) { + Err(nix::Error::Sys(nix::errno::Errno::ECHILD)) => { if !fg_was_grabbed { - eprintln!("ion: ([{}] {}) exited with {}", njob, pgid, status); + eprintln!("ion: ([{}] {}) exited with {}", njob, pgid, exit_status); } get_process!(|process| { process.forget(); if fg_was_grabbed { - fg.reply_with(exit_status as i8); + fg.reply_with(exit_status); } }); @@ -149,21 +151,20 @@ impl<'a> Shell<'a> { break; } - Ok(0) => (), - Ok(_) if wifexited(status) => exit_status = wexitstatus(status), - Ok(_) if wifstopped(status) => { + Ok(WaitStatus::Exited(_, status)) => exit_status = status, + Ok(WaitStatus::Stopped(..)) => { if !fg_was_grabbed { eprintln!("ion: ([{}] {}) Stopped", njob, pgid); } get_process!(|process| { if fg_was_grabbed { - fg.reply_with(Status::TERMINATED.as_os_code() as i8); + fg.reply_with(Status::TERMINATED.as_os_code()); } process.state = ProcessState::Stopped; }); } - Ok(_) if wifcontinued(status) => { + Ok(WaitStatus::Continued(_)) => { if !fg_was_grabbed { eprintln!("ion: ([{}] {}) Running", njob, pgid); } @@ -196,12 +197,15 @@ impl<'a> Shell<'a> { } /// Send a kill signal to all running background tasks. - pub fn background_send(&self, signal: i32) { + pub fn background_send(&self, signal: Signal) -> nix::Result<()> { let filter: fn(&&BackgroundProcess) -> bool = - if signal == libc::SIGHUP { |p| !p.ignore_sighup } else { |p| p.is_running() }; - self.background_jobs().iter().filter(filter).for_each(|p| { - let _ = sys::killpg(p.pid(), signal); - }) + if signal == Signal::SIGHUP { |p| !p.ignore_sighup } else { |p| p.is_running() }; + self.background_jobs() + .iter() + .filter(filter) + .map(|p| signal::killpg(p.pid(), signal)) + .find(Result::is_err) + .unwrap_or_else(|| Ok(())) } /// Resumes all stopped background jobs @@ -212,48 +216,44 @@ impl<'a> Shell<'a> { } /// Wait for the job in foreground - pub fn watch_foreground(&mut self, pgid: u32) -> Result<Status, PipelineError> { + pub fn watch_foreground(&mut self, group: Pid) -> Result<Status, PipelineError> { let mut signaled = None; let mut exit_status = Status::SUCCESS; loop { - let mut status = 0; - match waitpid(-(pgid as i32), &mut status, WUNTRACED) { - Err(errno) => match errno { - ECHILD => { + match wait::waitpid(Pid::from_raw(-group.as_raw()), Some(WaitPidFlag::WUNTRACED)) { + Err(err) => match err { + nix::Error::Sys(nix::errno::Errno::ECHILD) => { if let Some(signal) = signaled { break Err(signal); } else { break Ok(exit_status); } } - errno => break Err(PipelineError::WaitPid(strerror(errno))), + err => break Err(PipelineError::WaitPid(err)), }, - Ok(0) => (), - Ok(_) if wifexited(status) => { - exit_status = Status::from_exit_code(wexitstatus(status)) - } - Ok(pid) if wifsignaled(status) => { - let signal = wtermsig(status); - if signal == SIGPIPE { - } else if wcoredump(status) { - signaled = Some(PipelineError::CoreDump(pid as u32)); + Ok(WaitStatus::Exited(_, status)) => exit_status = Status::from_exit_code(status), + Ok(WaitStatus::Signaled(pid, signal, core_dumped)) => { + if signal == signal::Signal::SIGPIPE { + } else if core_dumped { + signaled = Some(PipelineError::CoreDump(pid)); } else { - if signal == SIGINT { - let _ = kill(pid as u32, signal as i32); + if signal == Signal::SIGINT { + signal::kill(pid, signal) } else { - self.handle_signal(signal); + self.handle_signal(signal).map(|_| ()) } - signaled = Some(PipelineError::Interrupted(pid as u32, signal)); + .map_err(PipelineError::KillFailed)?; + signaled = Some(PipelineError::Interrupted(pid, signal)); } } - Ok(pid) if wifstopped(status) => { + Ok(WaitStatus::Stopped(pid, signal)) => { self.send_to_background(BackgroundProcess::new( - pid.abs() as u32, + pid, ProcessState::Stopped, "".to_string(), )); - break Err(PipelineError::Interrupted(pid as u32, wstopsig(status))); + break Err(PipelineError::Interrupted(pid, signal)); } Ok(_) => (), } @@ -264,8 +264,8 @@ impl<'a> Shell<'a> { /// event that a signal is sent to kill the running tasks. pub fn wait_for_background(&mut self) -> Result<(), PipelineError> { while let Some(p) = { self.background_jobs().iter().find(|p| p.is_running()) } { - if let Some(signal) = signals::SignalHandler.find(|&s| s != libc::SIGTSTP) { - self.background_send(signal); + if let Some(signal) = signals::SignalHandler.find(|&s| s != Signal::SIGTSTP) { + self.background_send(signal).map_err(PipelineError::KillFailed)?; return Err(PipelineError::Interrupted(p.pid(), signal)); } sleep(Duration::from_millis(100)); @@ -275,16 +275,16 @@ impl<'a> Shell<'a> { /// When given a process ID, that process's group will be assigned as the /// foreground process group. - fn set_foreground_as(pid: u32) { + fn set_foreground_as(pid: Pid) { signals::block(); - let _ = sys::tcsetpgrp(0, pid); + unistd::tcsetpgrp(0, pid).unwrap(); signals::unblock(); } /// Takes a background tasks's PID and whether or not it needs to be continued; resumes the /// task and sets it as the foreground process. Once the task exits or stops, the exit status /// will be returned, and ownership of the TTY given back to the shell. - pub fn set_bg_task_in_foreground(&self, pid: u32, cont: bool) -> Status { + pub fn set_bg_task_in_foreground(&self, pid: Pid, cont: bool) -> Status { // Pass the TTY to the background job Self::set_foreground_as(pid); // Signal the background thread that is waiting on this process to stop waiting. @@ -305,7 +305,7 @@ impl<'a> Shell<'a> { } }; // Have the shell reclaim the TTY - Self::set_foreground_as(process::id()); + Self::set_foreground_as(Pid::this()); status } } diff --git a/src/lib/shell/pipe_exec/mod.rs b/src/lib/shell/pipe_exec/mod.rs index 4f8f12f7..f604fb6a 100644 --- a/src/lib/shell/pipe_exec/mod.rs +++ b/src/lib/shell/pipe_exec/mod.rs @@ -16,7 +16,7 @@ use super::{ flow_control::FunctionError, job::{Job, RefinedJob, TeeItem, Variant}, signals::{self, SignalHandler}, - sys, Shell, Value, + Shell, Value, }; use crate::{ builtins::{self, Status}, @@ -24,26 +24,30 @@ use crate::{ types, }; use err_derive::Error; +use nix::{ + fcntl::OFlag, + sys::signal::{self, Signal}, + unistd::{self, ForkResult, Pid}, +}; use smallvec::SmallVec; use std::{ fmt, fs::{File, OpenOptions}, io::{self, Write}, iter, - os::unix::{ - io::{FromRawFd, RawFd}, - process::CommandExt, - }, + os::unix::{io::FromRawFd, process::CommandExt}, path::Path, - process::{self, exit, Command, Stdio}, + process::{exit, Command, Stdio}, }; #[derive(Debug, Error)] pub enum InputError { - #[error(display = "failed to redirect '{}' to stdin: {}", file, why)] - File { file: String, why: io::Error }, - #[error(display = "failed to redirect herestring '{}' to stdin: {}", string, why)] - HereString { string: String, why: io::Error }, + #[error(display = "failed to redirect '{}' to stdin: {}", _0, _1)] + File(String, #[error(cause)] io::Error), + #[error(display = "failed to redirect herestring '{}' to stdin: {}", _0, _1)] + HereString(String, #[error(cause)] nix::Error), + #[error(display = "failed to redirect herestring '{}' to stdin: {}", _0, _1)] + WriteError(String, #[error(cause)] io::Error), } #[derive(Debug)] @@ -69,16 +73,16 @@ pub enum PipelineError { RedirectPipeError(#[error(cause)] RedirectError), /// Failed to create a pipe #[error(display = "could not create pipe: {}", _0)] - CreatePipeError(#[error(cause)] io::Error), + CreatePipeError(#[error(cause)] nix::Error), /// Failed to create a fork #[error(display = "could not fork: {}", _0)] - CreateForkError(#[error(cause)] io::Error), + CreateForkError(#[error(cause)] nix::Error), /// Failed to run function #[error(display = "could not run function: {}", _0)] RunFunctionError(#[error(cause)] FunctionError), /// Failed to terminate the jobs after a termination #[error(display = "failed to terminate foreground jobs: {}", _0)] - TerminateJobsError(#[error(cause)] io::Error), + TerminateJobsError(#[error(cause)] nix::Error), /// Could not execute the command #[error(display = "command exec error: {}", _0)] CommandExecError(#[error(cause)] io::Error), @@ -88,13 +92,13 @@ pub enum PipelineError { /// A signal interrupted a child process #[error(display = "process ({}) ended by signal {}", _0, _1)] - Interrupted(u32, i32), + Interrupted(Pid, Signal), /// A subprocess had a core dump #[error(display = "process ({}) had a core dump", _0)] - CoreDump(u32), + CoreDump(Pid), /// WaitPID errored #[error(display = "waitpid error: {}", _0)] - WaitPid(&'static str), + WaitPid(nix::Error), /// This will stop execution when the exit_on_error option is set #[error(display = "early exit: pipeline failed")] @@ -106,12 +110,12 @@ pub enum PipelineError { /// Failed to grab the tty #[error(display = "could not grab the terminal: {}", _0)] - TerminalGrabFailed(#[error(cause)] io::Error), + TerminalGrabFailed(#[error(cause)] nix::Error), /// Failed to send signal to a process group. This typically happens when trying to start the /// pipeline after it's creation #[error(display = "could not start the processes: {}", _0)] - KillFailed(#[error(cause)] io::Error), + KillFailed(#[error(cause)] nix::Error), } impl fmt::Display for OutputError { @@ -154,17 +158,21 @@ impl From<FunctionError> for PipelineError { /// 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, io::Error> { - let (reader, writer) = sys::pipe2(libc::O_CLOEXEC)?; +pub unsafe fn stdin_of<T: AsRef<str>>(input: &T) -> Result<File, InputError> { + let string = input.as_ref(); + let (reader, writer) = unistd::pipe2(OFlag::O_CLOEXEC) + .map_err(|err| InputError::HereString(string.into(), err))?; let mut infile = File::from_raw_fd(writer); // Write the contents; make sure to use write_all so that we block until // the entire string is written - infile.write_all(input.as_ref())?; - infile.flush()?; + infile + .write_all(string.as_bytes()) + .map_err(|err| InputError::WriteError(string.into(), err))?; + infile.flush().map_err(|err| InputError::WriteError(string.into(), err))?; // `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(reader) + Ok(File::from_raw_fd(reader)) } impl Input { @@ -172,17 +180,14 @@ impl Input { match self { Input::File(ref filename) => match File::open(filename.as_str()) { Ok(file) => Ok(file), - Err(why) => Err(InputError::File { file: filename.to_string(), why }), + Err(why) => Err(InputError::File(filename.to_string(), why)), }, Input::HereString(ref mut string) => { if !string.ends_with('\n') { string.push('\n'); } - match unsafe { stdin_of(&string) } { - Ok(stdio) => Ok(unsafe { File::from_raw_fd(stdio) }), - Err(why) => Err(InputError::HereString { string: string.to_string(), why }), - } + unsafe { stdin_of(&string) } } } } @@ -453,7 +458,7 @@ impl<'b> Shell<'b> { let exit_status = self.pipe(pipeline); // Set the shell as the foreground process again to regain the TTY. if !self.opts.is_background_shell { - let _ = sys::tcsetpgrp(0, process::id()); + let _ = unistd::tcsetpgrp(0, Pid::this()); } exit_status } @@ -475,7 +480,7 @@ impl<'b> Shell<'b> { status } else { - let (mut pgid, mut last_pid, mut current_pid) = (0, 0, 0); + let (mut pgid, mut last_pid, mut current_pid) = (None, None, Pid::this()); // Append jobs until all piped jobs are running for (mut child, ckind) in commands { @@ -500,8 +505,8 @@ impl<'b> Shell<'b> { .connect(tee_out, tee_err)?; } else { // Pipe the previous command's stdin to this commands stdout/stderr. - let (reader, writer) = - sys::pipe2(libc::O_CLOEXEC).map_err(PipelineError::CreatePipeError)?; + let (reader, writer) = unistd::pipe2(nix::fcntl::OFlag::O_CLOEXEC) + .map_err(PipelineError::CreatePipeError)?; if is_external { ext_stdio_pipes .get_or_insert_with(|| Vec::with_capacity(4)) @@ -529,7 +534,7 @@ impl<'b> Shell<'b> { spawn_proc(self, parent, kind, &mut last_pid, &mut current_pid, &mut pgid)?; - last_pid = current_pid; + last_pid = Some(current_pid); parent = child; kind = ckind; if ckind == RedirectFrom::None { @@ -539,17 +544,19 @@ impl<'b> Shell<'b> { spawn_proc(self, parent, kind, &mut last_pid, &mut current_pid, &mut pgid)?; if !self.opts.is_background_shell { - sys::tcsetpgrp(libc::STDIN_FILENO, pgid) + unistd::tcsetpgrp(nix::libc::STDIN_FILENO, pgid.unwrap()) .map_err(PipelineError::TerminalGrabFailed)?; } - sys::killpg(pgid, libc::SIGCONT).map_err(PipelineError::KillFailed)?; + signal::killpg(pgid.unwrap(), signal::Signal::SIGCONT) + .map_err(PipelineError::KillFailed)?; // Waits for all of the children of the assigned pgid to finish executing, // returning the exit status of the last process in the queue. // Watch the foreground group, dropping all commands that exit as they exit. - let status = self.watch_foreground(pgid)?; + let status = self.watch_foreground(pgid.unwrap())?; if status == Status::TERMINATED { - sys::killpg(pgid, libc::SIGTERM).map_err(PipelineError::TerminateJobsError)?; + signal::killpg(pgid.unwrap(), signal::Signal::SIGTERM) + .map_err(PipelineError::TerminateJobsError)?; } else { let _ = io::stdout().flush(); let _ = io::stderr().flush(); @@ -566,9 +573,9 @@ fn spawn_proc( shell: &mut Shell<'_>, cmd: RefinedJob<'_>, redirection: RedirectFrom, - last_pid: &mut u32, - current_pid: &mut u32, - pgid: &mut u32, + last_pid: &mut Option<Pid>, + current_pid: &mut Pid, + group: &mut Option<Pid>, ) -> Result<(), PipelineError> { let RefinedJob { mut var, args, stdin, stdout, stderr } = cmd; let pid = match var { @@ -580,10 +587,13 @@ fn spawn_proc( command.stdout(stdout.map_or_else(Stdio::inherit, Into::into)); command.stderr(stderr.map_or_else(Stdio::inherit, Into::into)); - let pgid_copy = *pgid; - command.before_exec(move || sys::setpgid(0, pgid_copy)); + let grp = *group; + command.before_exec(move || { + let _ = unistd::setpgid(Pid::this(), grp.unwrap_or_else(Pid::this)); + Ok(()) + }); match command.spawn() { - Ok(child) => Ok(child.id()), + Ok(child) => Ok(Pid::from_raw(child.id() as i32)), Err(err) => { if err.kind() == io::ErrorKind::NotFound { Err(PipelineError::CommandNotFound(args[0].to_string())) @@ -594,28 +604,27 @@ fn spawn_proc( } } Variant::Builtin { main } => { - fork_exec_internal(stdout, stderr, stdin, *pgid, |_, _, _| main(&args, shell)) + fork_exec_internal(stdout, stderr, stdin, *group, |_, _, _| main(&args, shell)) } - Variant::Function => fork_exec_internal(stdout, stderr, stdin, *pgid, |_, _, _| { + Variant::Function => fork_exec_internal(stdout, stderr, stdin, *group, |_, _, _| { shell.exec_function(&args[0], &args) }), Variant::Cat { ref mut sources } => { - fork_exec_internal(stdout, None, stdin, *pgid, |_, _, mut stdin| { + fork_exec_internal(stdout, None, stdin, *group, |_, _, mut stdin| { shell.exec_multi_in(sources, &mut stdin) }) } Variant::Tee { ref mut items } => { - fork_exec_internal(stdout, stderr, stdin, *pgid, |_, _, _| { + fork_exec_internal(stdout, stderr, stdin, *group, |_, _, _| { shell.exec_multi_out(items, redirection) }) } }?; - *last_pid = *current_pid; - *current_pid = pid; - if *pgid == 0 { - *pgid = pid; + *last_pid = Some(std::mem::replace(current_pid, pid)); + if group.is_none() { + *group = Some(pid); } - let _ = sys::setpgid(pid, *pgid); // try in the parent too to avoid race conditions + let _ = unistd::setpgid(pid, group.unwrap()); // try in the parent too to avoid race conditions Ok(()) } @@ -624,24 +633,26 @@ fn fork_exec_internal<F>( stdout: Option<File>, stderr: Option<File>, stdin: Option<File>, - pgid: u32, + pgid: Option<Pid>, mut exec_action: F, -) -> Result<u32, PipelineError> +) -> Result<Pid, PipelineError> where F: FnMut(Option<File>, Option<File>, Option<File>) -> Status, { - match unsafe { sys::fork() }.map_err(PipelineError::CreateForkError)? { - 0 => { - let _ = sys::reset_signal(libc::SIGINT); - let _ = sys::reset_signal(libc::SIGHUP); - let _ = sys::reset_signal(libc::SIGTERM); + match unistd::fork().map_err(PipelineError::CreateForkError)? { + ForkResult::Child => { + unsafe { + signal::signal(signal::Signal::SIGINT, signal::SigHandler::SigIgn).unwrap(); + signal::signal(signal::Signal::SIGHUP, signal::SigHandler::SigIgn).unwrap(); + signal::signal(signal::Signal::SIGTERM, signal::SigHandler::SigIgn).unwrap(); + } signals::unblock(); - sys::setpgid(0, pgid).expect(" aaaa"); + unistd::setpgid(Pid::this(), pgid.unwrap_or_else(Pid::this)).unwrap(); streams::redirect(&stdin, &stdout, &stderr); let exit_status = exec_action(stdout, stderr, stdin); exit(exit_status.as_os_code()) } - pid => Ok(pid), + ForkResult::Parent { child } => Ok(child), } } diff --git a/src/lib/shell/pipe_exec/pipes.rs b/src/lib/shell/pipe_exec/pipes.rs index f656c78c..ac0f8c40 100644 --- a/src/lib/shell/pipe_exec/pipes.rs +++ b/src/lib/shell/pipe_exec/pipes.rs @@ -1,11 +1,9 @@ use super::{ - super::{ - job::{RefinedJob, TeeItem}, - sys, - }, + super::job::{RefinedJob, TeeItem}, PipelineError, }; +use nix::{fcntl::OFlag, unistd}; use std::{fs::File, os::unix::io::FromRawFd}; pub struct TeePipe<'a, 'b> { @@ -28,7 +26,7 @@ impl<'a, 'b> TeePipe<'a, 'b> { F: FnMut(&mut RefinedJob<'b>, File), { let (reader, writer) = - sys::pipe2(libc::O_CLOEXEC).map_err(PipelineError::CreatePipeError)?; + unistd::pipe2(OFlag::O_CLOEXEC).map_err(PipelineError::CreatePipeError)?; (*tee).source = Some(unsafe { File::from_raw_fd(reader) }); action(self.parent, unsafe { File::from_raw_fd(writer) }); if self.is_external { diff --git a/src/lib/shell/pipe_exec/streams.rs b/src/lib/shell/pipe_exec/streams.rs index 632bdd82..5495297a 100644 --- a/src/lib/shell/pipe_exec/streams.rs +++ b/src/lib/shell/pipe_exec/streams.rs @@ -1,14 +1,13 @@ -use super::sys; +use nix::unistd; use std::{ fs::File, - io, os::unix::io::{AsRawFd, FromRawFd, RawFd}, }; /// Use dup2 to replace `old` with `new` using `old`s file descriptor ID fn redir(old: &Option<File>, new: RawFd) { if let Some(old) = old.as_ref().map(AsRawFd::as_raw_fd) { - if let Err(e) = sys::dup2(old, new) { + if let Err(e) = unistd::dup2(old, new) { eprintln!("ion: could not duplicate {} to {}: {}", old, new, e); } } @@ -17,19 +16,20 @@ fn redir(old: &Option<File>, new: RawFd) { /// Duplicates STDIN, STDOUT, and STDERR; in that order; and returns them as `File`s. /// Why, you ask? A simple safety mechanism to ensure that the duplicated FDs are closed /// when dropped. -pub fn duplicate() -> io::Result<(Option<File>, File, File)> { +pub fn duplicate() -> nix::Result<(Option<File>, File, File)> { // STDIN may have been closed for a background shell, so it is ok if it cannot be duplicated. - let stdin = sys::dup(libc::STDIN_FILENO).ok().map(|fd| unsafe { File::from_raw_fd(fd) }); + let stdin = + unistd::dup(nix::libc::STDIN_FILENO).ok().map(|fd| unsafe { File::from_raw_fd(fd) }); - let stdout = unsafe { File::from_raw_fd(sys::dup(libc::STDOUT_FILENO)?) }; - let stderr = unsafe { File::from_raw_fd(sys::dup(libc::STDERR_FILENO)?) }; + let stdout = unsafe { File::from_raw_fd(unistd::dup(nix::libc::STDOUT_FILENO)?) }; + let stderr = unsafe { File::from_raw_fd(unistd::dup(nix::libc::STDERR_FILENO)?) }; // And then meld stderr alongside stdin and stdout Ok((stdin, stdout, stderr)) } #[inline] pub fn redirect(inp: &Option<File>, out: &Option<File>, err: &Option<File>) { - redir(inp, libc::STDIN_FILENO); - redir(out, libc::STDOUT_FILENO); - redir(err, libc::STDERR_FILENO); + redir(inp, nix::libc::STDIN_FILENO); + redir(out, nix::libc::STDOUT_FILENO); + redir(err, nix::libc::STDERR_FILENO); } diff --git a/src/lib/shell/shell_expand.rs b/src/lib/shell/shell_expand.rs index 80fc4cfb..f9eb6f6c 100644 --- a/src/lib/shell/shell_expand.rs +++ b/src/lib/shell/shell_expand.rs @@ -1,14 +1,10 @@ -use super::{ - fork::Capture, - sys::{self, variables}, - variables::Value, - IonError, Shell, -}; +use super::{fork::Capture, sys::variables, variables::Value, IonError, Shell}; use crate::{ expansion::{Error, Expander, Result, Select}, types, }; -use std::{env, io::Read, iter::FromIterator, process}; +use nix::unistd::{tcsetpgrp, Pid}; +use std::{env, io::Read, iter::FromIterator}; impl<'a, 'b> Expander for Shell<'b> { type Error = IonError; @@ -26,7 +22,7 @@ impl<'a, 'b> Expander for Shell<'b> { }); // Ensure that the parent retains ownership of the terminal before exiting. - let _ = sys::tcsetpgrp(libc::STDIN_FILENO, process::id()); + let _ = tcsetpgrp(nix::libc::STDIN_FILENO, Pid::this()); output.map(Into::into).map_err(|err| Error::Subprocess(Box::new(err))) } diff --git a/src/lib/shell/signals.rs b/src/lib/shell/signals.rs index 34d5d2ff..4567457f 100644 --- a/src/lib/shell/signals.rs +++ b/src/lib/shell/signals.rs @@ -6,8 +6,8 @@ // use std::sync::atomic::{ATOMIC_U8_INIT, AtomicU8}; use std::sync::atomic::{AtomicUsize, Ordering}; -use super::sys; pub use super::sys::signals::{block, unblock}; +use nix::{sys::signal, unistd::Pid}; pub static PENDING: AtomicUsize = AtomicUsize::new(0); pub const SIGINT: u8 = 1; @@ -15,7 +15,7 @@ pub const SIGHUP: u8 = 2; pub const SIGTERM: u8 = 4; /// Resumes a given process by it's process ID. -pub fn resume(pid: u32) { let _ = sys::killpg(pid, libc::SIGCONT); } +pub fn resume(pid: Pid) { let _ = signal::killpg(pid, signal::Signal::SIGCONT); } /// The purpose of the signal handler is to ignore signals when it is active, and then continue /// listening to signals once the handler is dropped. @@ -33,14 +33,14 @@ impl Drop for SignalHandler { } impl Iterator for SignalHandler { - type Item = i32; + type Item = signal::Signal; fn next(&mut self) -> Option<Self::Item> { match PENDING.swap(0, Ordering::SeqCst) as u8 { 0 => None, - SIGINT => Some(libc::SIGINT), - SIGHUP => Some(libc::SIGHUP), - SIGTERM => Some(libc::SIGTERM), + SIGINT => Some(signal::Signal::SIGINT), + SIGHUP => Some(signal::Signal::SIGHUP), + SIGTERM => Some(signal::Signal::SIGTERM), _ => unreachable!(), } } diff --git a/src/lib/shell/sys/mod.rs b/src/lib/shell/sys/mod.rs index 26400a3c..082b1a87 100644 --- a/src/lib/shell/sys/mod.rs +++ b/src/lib/shell/sys/mod.rs @@ -1,6 +1,3 @@ -use libc::{c_int, pid_t, sighandler_t}; -use std::{ffi::CStr, io, os::unix::io::RawFd}; - pub mod signals; #[cfg(target_os = "redox")] @@ -8,103 +5,7 @@ pub const NULL_PATH: &str = "null:"; #[cfg(unix)] pub const NULL_PATH: &str = "/dev/null"; -// Why each platform wants to be unique in this regard is anyone's guess. -#[cfg(target_os = "linux")] -fn errno() -> i32 { unsafe { *libc::__errno_location() } } - -#[cfg(any(target_os = "openbsd", target_os = "bitrig", target_os = "android"))] -fn errno() -> i32 { unsafe { *libc::__errno() } } - -#[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd"))] -fn errno() -> i32 { unsafe { *libc::__error() } } - -#[cfg(target_os = "dragonfly")] -fn errno() -> i32 { unsafe { *errno_dragonfly::errno_location() } } - -pub fn strerror(errno: i32) -> &'static str { - unsafe { - let ptr = libc::strerror(errno); - if ptr.is_null() { - return "Unknown Error"; - } - - CStr::from_ptr(ptr).to_str().unwrap_or("Unknown Error") - } -} - -pub fn waitpid(pid: i32, status: &mut i32, options: i32) -> Result<i32, i32> { - match unsafe { libc::waitpid(pid, status, options) } { - -1 => Err(errno()), - pid => Ok(pid), - } -} - -pub fn wexitstatus(status: i32) -> i32 { unsafe { libc::WEXITSTATUS(status) } } -pub fn wifexited(status: i32) -> bool { unsafe { libc::WIFEXITED(status) } } -pub fn wifstopped(status: i32) -> bool { unsafe { libc::WIFSTOPPED(status) } } -pub fn wifcontinued(status: i32) -> bool { unsafe { libc::WIFCONTINUED(status) } } -pub fn wifsignaled(status: i32) -> bool { unsafe { libc::WIFSIGNALED(status) } } -pub fn wcoredump(status: i32) -> bool { unsafe { libc::WCOREDUMP(status) } } -pub fn wtermsig(status: i32) -> i32 { unsafe { libc::WTERMSIG(status) } } -pub fn wstopsig(status: i32) -> i32 { unsafe { libc::WSTOPSIG(status) } } - -pub fn getpid() -> io::Result<u32> { cvt(unsafe { libc::getpid() }).map(|pid| pid as u32) } -pub fn geteuid() -> io::Result<u32> { Ok(unsafe { libc::geteuid() } as u32) } -pub fn getuid() -> io::Result<u32> { Ok(unsafe { libc::getuid() } as u32) } - -pub unsafe fn fork() -> io::Result<u32> { cvt(libc::fork()).map(|pid| pid as u32) } -pub fn fork_exit(exit_status: i32) -> ! { unsafe { libc::_exit(exit_status) } } - -pub fn kill(pid: u32, signal: i32) -> io::Result<()> { - cvt(unsafe { libc::kill(pid as pid_t, signal as c_int) }).and(Ok(())) -} -pub fn killpg(pgid: u32, signal: i32) -> io::Result<()> { - cvt(unsafe { libc::kill(-(pgid as pid_t), signal as c_int) }).and(Ok(())) -} - -pub fn setpgid(pid: u32, pgid: u32) -> io::Result<()> { - cvt(unsafe { libc::setpgid(pid as pid_t, pgid as pid_t) }).and(Ok(())) -} - -pub fn signal(signal: i32, handler: extern "C" fn(i32)) -> io::Result<()> { - if unsafe { libc::signal(signal as c_int, handler as sighandler_t) } == libc::SIG_ERR { - Err(io::Error::last_os_error()) - } else { - Ok(()) - } -} - -pub fn reset_signal(signal: i32) -> io::Result<()> { - if unsafe { libc::signal(signal as c_int, libc::SIG_DFL) } == libc::SIG_ERR { - Err(io::Error::last_os_error()) - } else { - Ok(()) - } -} - -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 dup(fd: RawFd) -> io::Result<RawFd> { cvt(unsafe { libc::dup(fd) }) } -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(())) } -pub fn isatty(fd: RawFd) -> bool { unsafe { libc::isatty(fd) == 1 } } - -pub fn pipe2(flags: i32) -> io::Result<(RawFd, RawFd)> { - let mut fds = [0; 2]; - - #[cfg(not(target_os = "macos"))] - cvt(unsafe { libc::pipe2(fds.as_mut_ptr(), flags as c_int) })?; - - #[cfg(target_os = "macos")] - cvt(unsafe { libc::pipe(fds.as_mut_ptr()) })?; - - Ok((fds[0], fds[1])) -} - pub mod variables { - use libc::c_char; use users::{get_user_by_name, os::unix::UserExt}; pub fn get_user_home(username: &str) -> Option<String> { @@ -113,40 +14,4 @@ pub mod variables { None => None, } } - - pub fn get_host_name() -> Option<String> { - let mut host_name = [0_u8; 512]; - - if unsafe { libc::gethostname(&mut host_name as *mut _ as *mut c_char, host_name.len()) } - == 0 - { - let len = host_name.iter().position(|i| *i == 0).unwrap_or_else(|| host_name.len()); - - Some(unsafe { String::from_utf8_unchecked(host_name[..len].to_owned()) }) - } else { - None - } - } -} - -trait IsMinusOne { - fn is_minus_one(&self) -> bool; -} - -macro_rules! impl_is_minus_one { - ($($t:ident)*) => ($(impl IsMinusOne for $t { - fn is_minus_one(&self) -> bool { - *self == -1 - } - })*) - } - -impl_is_minus_one! { i8 i16 i32 i64 isize } - -fn cvt<T: IsMinusOne>(t: T) -> io::Result<T> { - if t.is_minus_one() { - Err(io::Error::last_os_error()) - } else { - Ok(t) - } } diff --git a/src/lib/shell/sys/signals.rs b/src/lib/shell/sys/signals.rs index 00a80262..db15ca05 100644 --- a/src/lib/shell/sys/signals.rs +++ b/src/lib/shell/sys/signals.rs @@ -1,31 +1,26 @@ -use libc::*; -use std::{mem, ptr}; +use nix::sys::signal; /// Blocks the SIGTSTP/SIGTTOU/SIGTTIN/SIGCHLD signals so that the shell never receives /// them. pub fn block() { - unsafe { - let mut sigset = mem::uninitialized::<sigset_t>(); - sigemptyset(&mut sigset as *mut sigset_t); - sigaddset(&mut sigset as *mut sigset_t, SIGTSTP); - sigaddset(&mut sigset as *mut sigset_t, SIGTTOU); - sigaddset(&mut sigset as *mut sigset_t, SIGTTIN); - sigaddset(&mut sigset as *mut sigset_t, SIGCHLD); - sigprocmask(SIG_BLOCK, &sigset as *const sigset_t, ptr::null_mut() as *mut sigset_t); - } + let mut sigset = signal::SigSet::empty(); + sigset.add(signal::Signal::SIGTSTP); + sigset.add(signal::Signal::SIGTTOU); + sigset.add(signal::Signal::SIGTTIN); + sigset.add(signal::Signal::SIGCHLD); + signal::sigprocmask(signal::SigmaskHow::SIG_BLOCK, Some(&sigset), None) + .expect("Could not block the signals"); } /// Unblocks the SIGTSTP/SIGTTOU/SIGTTIN/SIGCHLD signals so children processes can be /// controlled /// by the shell. pub fn unblock() { - unsafe { - let mut sigset = mem::uninitialized::<sigset_t>(); - sigemptyset(&mut sigset as *mut sigset_t); - sigaddset(&mut sigset as *mut sigset_t, SIGTSTP); - sigaddset(&mut sigset as *mut sigset_t, SIGTTOU); - sigaddset(&mut sigset as *mut sigset_t, SIGTTIN); - sigaddset(&mut sigset as *mut sigset_t, SIGCHLD); - sigprocmask(SIG_UNBLOCK, &sigset as *const sigset_t, ptr::null_mut() as *mut sigset_t); - } + let mut sigset = signal::SigSet::empty(); + sigset.add(signal::Signal::SIGTSTP); + sigset.add(signal::Signal::SIGTTOU); + sigset.add(signal::Signal::SIGTTIN); + sigset.add(signal::Signal::SIGCHLD); + signal::sigprocmask(signal::SigmaskHow::SIG_UNBLOCK, Some(&sigset), None) + .expect("Could not block the signals"); } diff --git a/src/lib/shell/variables.rs b/src/lib/shell/variables.rs index 099bf392..6584c1be 100644 --- a/src/lib/shell/variables.rs +++ b/src/lib/shell/variables.rs @@ -1,12 +1,10 @@ use super::{colors::Colors, flow_control::Function}; use crate::{ expansion, - shell::{ - sys::{geteuid, getpid, getuid, variables as self_sys}, - IonError, - }, + shell::IonError, types::{self, Array}, }; +use nix::unistd::{geteuid, gethostname, getpid, getuid}; use scopes::{Namespace, Scope, Scopes}; use std::env; use types_rs::array; @@ -245,9 +243,9 @@ impl<'a> Default for Variables<'a> { ); // Set the PID, UID, and EUID variables. - map.set("PID", Value::Str(getpid().ok().map_or("?".into(), |id| id.to_string().into()))); - map.set("UID", Value::Str(getuid().ok().map_or("?".into(), |id| id.to_string().into()))); - map.set("EUID", Value::Str(geteuid().ok().map_or("?".into(), |id| id.to_string().into()))); + map.set("PID", Value::Str(getpid().to_string().into())); + map.set("UID", Value::Str(getuid().to_string().into())); + map.set("EUID", Value::Str(geteuid().to_string().into())); // Initialize the HISTFILE variable if let Ok(base_dirs) = BaseDirectories::with_prefix("ion") { @@ -271,7 +269,13 @@ impl<'a> Default for Variables<'a> { ); // Initialize the HOST variable - env::set_var("HOST", &self_sys::get_host_name().unwrap_or_else(|| "?".to_owned())); + let mut host_name = [0_u8; 512]; + env::set_var( + "HOST", + &gethostname(&mut host_name) + .ok() + .map_or("?", |hostname| hostname.to_str().unwrap_or("?")), + ); Variables(map) } diff --git a/src/main.rs b/src/main.rs index 871fbc2d..d63d835f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ use self::binary::{builtins, InteractiveShell}; use atty::Stream; -use ion_shell::{BuiltinMap, IonError, PipelineError, Shell, Value}; +use ion_shell::{BuiltinMap, Shell, Value}; use liner::KeyBindings; use std::{ io::{self, stdin, BufReader}, @@ -187,17 +187,11 @@ fn main() { }; if let Err(why) = err { eprintln!("ion: {}", why); - process::exit( - if let IonError::PipelineExecutionError(PipelineError::Interrupted(_, signal)) = why { - signal - } else { - 1 - }, - ); + process::exit(1); } if let Err(why) = shell.wait_for_background() { eprintln!("ion: {}", why); - process::exit(if let PipelineError::Interrupted(_, signal) = why { signal } else { 1 }); + process::exit(1); } process::exit(shell.previous_status().as_os_code()); } -- GitLab