diff --git a/Cargo.lock b/Cargo.lock index 2e09e0b2a9d831749cd7f7e25de6997167b90e02..c293587ed6ef32f11dc9e5d94809a2bab5e51611 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,6 +12,7 @@ dependencies = [ "nix 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "peg 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", "permutate 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)", "smallstring 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "smallvec 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-core 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", @@ -189,6 +190,11 @@ name = "quote" version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "redox_syscall" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "scoped-tls" version = "0.1.0" @@ -335,6 +341,7 @@ dependencies = [ "checksum peg 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "36a474cba42744afe0f223e9d4263594b3387f172e512259c72d2011e477c4fb" "checksum permutate 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53b7d5b19a715ffab38693a9dd44b067fdfa2b18eef65bd93562dfe507022fae" "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" +"checksum redox_syscall 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)" = "e4a357d14a12e90a37d658725df0e6468504750b5948b9710f83f94a0c5818e8" "checksum scoped-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f417c22df063e9450888a7561788e9bd46d3bb3c1466435b4eccb903807f147d" "checksum slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "17b4fcaed89ab08ef143da37bc52adbcc04d4a69014f4c1208d6b51f0c47bc23" "checksum smallstring 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "30950abdb5b38f56a0e181ae56ed64a539b64fa77ea6325147203dc7faeb087f" diff --git a/Cargo.toml b/Cargo.toml index 0ea584f32433a25da6cd19aa5d90e9debed20cb1..5b9b807e818e90341025bf4dc56071a7f1fda05f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,16 +27,17 @@ unicode-segmentation = "1.2" smallvec = "0.4" smallstring = "0.1" -[target.'cfg(not(target_os = "redox"))'.dependencies] +[target.'cfg(target_os = "redox")'.dependencies] +redox_syscall = "0.1" + +[target.'cfg(all(unix, not(target_os = "redox")))'.dependencies] +users = "0.5.1" futures = "0.1" libc = "0.2" nix = "0.8" tokio-core = "0.1" tokio-signal = "0.1" -[target.'cfg(all(unix, not(target_os = "redox")))'.dependencies] -users = "0.5.1" - [build-dependencies] peg = "0.5" ansi_term = "0.9" diff --git a/src/builtins/job_control.rs b/src/builtins/job_control.rs index f4238a1bacae1a1d81ac7f0b93b3a589edc3b0fc..ba542fe9b16080a8bac1bec0461dacfd8d28c955 100644 --- a/src/builtins/job_control.rs +++ b/src/builtins/job_control.rs @@ -26,12 +26,9 @@ fn fg_listen(shell: &mut Shell, job: u32) { let job = &mut (*shell.background.lock().unwrap())[job as usize]; if let ProcessState::Empty = job.state { break } if let Ok(signal) = shell.signals.try_recv() { - let stderr = stderr(); - let _ = writeln!(stderr.lock(), "ion: fg_listen: signal {} ", signal); match signal { libc::SIGTSTP => { let _ = signal::kill(job.pid as pid_t, Some(Signal::SIGTSTP)); - job.state = ProcessState::Stopped; break }, libc::SIGTERM => { @@ -39,7 +36,6 @@ fn fg_listen(shell: &mut Shell, job: u32) { }, libc::SIGINT => { let _ = signal::kill(job.pid as pid_t, Some(Signal::SIGINT)); - job.state = ProcessState::Empty; break }, _ => unimplemented!() @@ -110,7 +106,6 @@ pub fn bg(shell: &mut Shell, args: &[&str]) -> i32 { }, ProcessState::Stopped => { let _ = signal::kill(job.pid as pid_t, Some(Signal::SIGCONT)); - job.state = ProcessState::Running; let _ = writeln!(stderr, "[{}] {} {}", njob, job.pid, job.state); }, ProcessState::Empty => { diff --git a/src/main.rs b/src/main.rs index 5f873fd4de29cbfad5619b7da4e3e76b9a03df75..9f8776fe511782c7f255519f9bbe1b43a62a8824 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,14 +13,13 @@ extern crate liner; extern crate smallvec; extern crate smallstring; -#[cfg(not(target_os = "redox"))] extern crate futures; -#[cfg(not(target_os = "redox"))] extern crate libc; -#[cfg(not(target_os = "redox"))] extern crate nix; -#[cfg(not(target_os = "redox"))] extern crate tokio_core; -#[cfg(not(target_os = "redox"))] extern crate tokio_signal; - -#[cfg(all(unix, not(target_os = "redox")))] -extern crate users as users_unix; +#[cfg(all(unix, not(target_os = "redox")))] extern crate futures; +#[cfg(all(unix, not(target_os = "redox")))] extern crate libc; +#[cfg(all(unix, not(target_os = "redox")))] extern crate nix; +#[cfg(all(unix, not(target_os = "redox")))] extern crate tokio_core; +#[cfg(all(unix, not(target_os = "redox")))] extern crate tokio_signal; +#[cfg(all(unix, not(target_os = "redox")))] extern crate users as users_unix; +#[cfg(target_os = "redox")] extern crate redox_syscall; #[macro_use] mod parser; mod builtins; @@ -33,9 +32,9 @@ use std::io::{stderr, Write, ErrorKind}; use builtins::Builtin; use shell::Shell; -#[cfg(not(target_os = "redox"))] use tokio_core::reactor::Core; -#[cfg(not(target_os = "redox"))] use futures::{Future, Stream}; -#[cfg(not(target_os = "redox"))] use tokio_signal::unix::{self as unix_signal, Signal}; +#[cfg(all(unix, not(target_os = "redox")))] use tokio_core::reactor::Core; +#[cfg(all(unix, not(target_os = "redox")))] use futures::{Future, Stream}; +#[cfg(all(unix, not(target_os = "redox")))] use tokio_signal::unix::{self as unix_signal, Signal}; use std::sync::mpsc; use std::thread; diff --git a/src/shell/flow.rs b/src/shell/flow.rs index baefa0ae4d8eb3fdb09333dd697d2ee969a2c6ae..29c2b5f341a4bbb09d02d8abbc7973ddbddcf805 100644 --- a/src/shell/flow.rs +++ b/src/shell/flow.rs @@ -10,8 +10,7 @@ use parser::{ForExpression, StatementSplitter, check_statement, expand_string, S use parser::peg::Pipeline; use super::assignments::{let_assignment, export_variable}; use types::Array; -#[cfg(not(target_os = "redox"))] -use libc; +#[cfg(all(unix, not(target_os = "redox")))] use libc; pub enum Condition { Continue, diff --git a/src/shell/job_control.rs b/src/shell/job_control.rs index c41adceab6865a38b3ac25cbf3f4c2bbbf7677dc..91f607115efd29d28d3e751673e88bada1fa7334 100644 --- a/src/shell/job_control.rs +++ b/src/shell/job_control.rs @@ -1,10 +1,10 @@ -#[cfg(not(target_os = "redox"))] use libc::{self, pid_t, c_int}; -#[cfg(not(target_os = "redox"))] use nix::sys::signal::{self, Signal as NixSignal}; +#[cfg(all(unix, not(target_os = "redox")))] use libc::{self, pid_t, c_int}; +#[cfg(all(unix, not(target_os = "redox")))] use nix::sys::signal::{self, Signal as NixSignal}; use std::fmt; -use std::io::{stderr, Write}; use std::thread::{sleep, spawn}; use std::time::Duration; -use std::process::{self, Child}; +use std::process; +use std::sync::{Arc, Mutex}; use super::status::*; use super::Shell; @@ -14,7 +14,7 @@ pub trait JobControl { fn handle_signal(&self, signal: i32); fn foreground_send(&self, signal: i32); fn background_send(&self, signal: i32); - fn send_child_to_background(&mut self, child: Child, state: ProcessState); + fn send_child_to_background(&mut self, child: u32, state: ProcessState); } #[derive(Clone)] @@ -35,6 +35,76 @@ impl fmt::Display for ProcessState { } } +#[cfg(target_os = "redox")] +pub fn watch_pid(processes: Arc<Mutex<Vec<BackgroundProcess>>>, pid: u32) { + // TODO: Implement this using syscall::call::waitpid +} + +#[cfg(all(unix, not(target_os = "redox")))] +pub fn watch_pid ( + processes: Arc<Mutex<Vec<BackgroundProcess>>>, + pid: u32, + njob: usize) +{ + use nix::sys::wait::{waitpid, WaitStatus}; + loop { + match waitpid(pid as pid_t, None) { + Ok(WaitStatus::Exited(_, status)) => { + eprintln!("ion: background process ([{}] {}) exited with {}", njob, pid, status); + let mut processes = processes.lock().unwrap(); + let process = &mut processes.iter_mut().nth(njob).unwrap(); + process.state = ProcessState::Empty; + break + }, + Ok(WaitStatus::Stopped(_, _)) => { + let mut processes = processes.lock().unwrap(); + let process = &mut processes.iter_mut().nth(njob).unwrap(); + process.state = ProcessState::Stopped; + }, + Ok(WaitStatus::Continued(_)) => { + let mut processes = processes.lock().unwrap(); + let process = &mut processes.iter_mut().nth(njob).unwrap(); + process.state = ProcessState::Running; + }, + Ok(_) => (), + Err(why) => { + eprintln!("ion: background process ([{}] {}) errored: {}", njob, pid, why); + let mut processes = processes.lock().unwrap(); + let process = &mut processes.iter_mut().nth(njob).unwrap(); + process.state = ProcessState::Empty; + break + } + } + } +} + +pub fn add_to_background ( + processes: Arc<Mutex<Vec<BackgroundProcess>>>, + pid: u32, + state: ProcessState +) -> usize { + let mut processes = processes.lock().unwrap(); + match (*processes).iter().position(|x| { + if let ProcessState::Empty = x.state { true } else { false } + }) { + Some(id) => { + (*processes)[id] = BackgroundProcess { + pid: pid, + state: state + }; + id + }, + None => { + let njobs = (*processes).len(); + (*processes).push(BackgroundProcess { + pid: pid, + state: state + }); + njobs + } + } +} + #[derive(Clone)] /// A background process is a process that is attached to, but not directly managed /// by the shell. The shell will only retain information about the process, such @@ -48,7 +118,7 @@ pub struct BackgroundProcess { } impl<'a> JobControl for Shell<'a> { - #[cfg(not(target_os = "redox"))] + #[cfg(all(unix, not(target_os = "redox")))] /// Suspends a given process by it's process ID. fn suspend(&mut self, pid: u32) { let _ = signal::kill(pid as pid_t, Some(NixSignal::SIGTSTP)); @@ -59,7 +129,7 @@ impl<'a> JobControl for Shell<'a> { // TODO: Redox doesn't support signals yet. } - #[cfg(not(target_os = "redox"))] + #[cfg(all(unix, not(target_os = "redox")))] /// Waits until all running background tasks have completed, and listens for signals in the /// event that a signal is sent to kill the running tasks. fn wait_for_background(&mut self) { @@ -85,7 +155,7 @@ impl<'a> JobControl for Shell<'a> { // TODO: Redox doesn't support signals yet. } - #[cfg(not(target_os = "redox"))] + #[cfg(all(unix, not(target_os = "redox")))] /// Send a kill signal to all running foreground tasks. fn foreground_send(&self, signal: i32) { for process in self.foreground.iter() { @@ -98,7 +168,7 @@ impl<'a> JobControl for Shell<'a> { // TODO: Redox doesn't support signals yet } - #[cfg(not(target_os = "redox"))] + #[cfg(all(unix, not(target_os = "redox")))] /// Send a kill signal to all running background tasks. fn background_send(&self, signal: i32) { for process in self.background.lock().unwrap().iter() { @@ -113,52 +183,12 @@ impl<'a> JobControl for Shell<'a> { // TODO: Redox doesn't support signals yet } - fn send_child_to_background(&mut self, mut child: Child, state: ProcessState) { - let pid = child.id(); + fn send_child_to_background(&mut self, pid: u32, state: ProcessState) { let processes = self.background.clone(); let _ = spawn(move || { - let njob; - { - let mut processes = processes.lock().unwrap(); - njob = match (*processes).iter().position(|x| { - if let ProcessState::Empty = x.state { true } else { false } - }) { - Some(id) => { - (*processes)[id] = BackgroundProcess { - pid: pid, - state: state - }; - id - }, - None => { - let njobs = (*processes).len(); - (*processes).push(BackgroundProcess { - pid: pid, - state: state - }); - njobs - } - }; - - let stderr = stderr(); - let _ = writeln!(stderr.lock(), "ion: bg: [{}] {}", njob, pid); - } - - // Wait for the child to complete before removing it from the process list. - let status = child.wait(); - - // Notify the user that the background task has completed. - let stderr = stderr(); - let mut stderr = stderr.lock(); - let _ = match status { - Ok(status) => writeln!(stderr, "ion: bg: [{}] {} completed: {}", njob, pid, status), - Err(why) => writeln!(stderr, "ion: bg: [{}] {} errored: {}", njob, pid, why) - }; - - // Remove the process from the background processes list. - let mut processes = processes.lock().unwrap(); - let process = &mut processes.iter_mut().nth(njob).unwrap(); - process.state = ProcessState::Empty; + let njob = add_to_background(processes.clone(), pid, state); + eprintln!("ion: bg [{}] {}", njob, pid); + watch_pid(processes, pid, njob); }); } diff --git a/src/shell/pipe.rs b/src/shell/pipe.rs index 525027e608a1f32402e42f505a40aeb1fcdc777c..afc35ec2c5b6a73b0bd69a2b78e79f58b3c3378b 100644 --- a/src/shell/pipe.rs +++ b/src/shell/pipe.rs @@ -1,8 +1,12 @@ -#[cfg(not(target_os = "redox"))] use libc; +#[cfg(all(unix, not(target_os = "redox")))] use libc; +#[cfg(all(unix, not(target_os = "redox")))] use nix::unistd::{fork, ForkResult}; +#[cfg(all(unix, not(target_os = "redox")))] use nix::Error as NixError; +#[cfg(target_os = "redox")] use std::error::Error; use std::io::{self, Write}; use std::process::{Stdio, Command, Child}; use std::os::unix::io::{FromRawFd, AsRawFd, IntoRawFd}; use std::fs::{File, OpenOptions}; +use std::process::exit; use std::thread; use std::time::Duration; use super::job_control::{JobControl, ProcessState}; @@ -18,7 +22,9 @@ impl<'a> PipelineExecution for Shell<'a> { fn execute_pipeline(&mut self, pipeline: &mut Pipeline) -> i32 { // Generate a list of commands from the given pipeline let mut piped_commands: Vec<(Command, JobKind)> = pipeline.jobs - .drain(..).map(|mut job| (job.build_command(), job.kind)).collect(); + .drain(..).map(|mut job| { + (job.build_command(), job.kind) + }).collect(); if let Some(ref stdin) = pipeline.stdin { if let Some(command) = piped_commands.first_mut() { @@ -66,7 +72,48 @@ impl<'a> PipelineExecution for Shell<'a> { } self.foreground.clear(); - pipe(self, &mut piped_commands) + if piped_commands[piped_commands.len()-1].1 == JobKind::Background { + fork_pipe(self, &mut piped_commands) + } else { + pipe(self, &mut piped_commands) + } + } +} + +enum Fork { + Parent(u32), + Child +} + +#[cfg(target_os = "redox")] +fn ion_fork() -> Result<Fork, Error> { + use redox_syscall::call::clone; + unsafe { + clone(0).map(|pid| if pid == 0 { Fork::Child } else { Fork::Parent(pid as u32)})? + } +} + +#[cfg(all(unix, not(target_os = "redox")))] +fn ion_fork() -> Result<Fork, NixError> { + match fork()? { + ForkResult::Parent{ child: pid } => Ok(Fork::Parent(pid as u32)), + ForkResult::Child => Ok(Fork::Child) + } +} + +fn fork_pipe(shell: &mut Shell, commands: &mut [(Command, JobKind)]) -> i32 { + match ion_fork() { + Ok(Fork::Parent(pid)) => { + shell.send_child_to_background(pid, ProcessState::Running); + SUCCESS + }, + Ok(Fork::Child) => { + exit(pipe(shell, commands)); + }, + Err(why) => { + eprintln!("ion: background job: {}", why); + FAILURE + } } } @@ -90,15 +137,6 @@ fn pipe(shell: &mut Shell, commands: &mut [(Command, JobKind)]) -> i32 { } match kind { - JobKind::Background => { - if let Err(_) = command.spawn() - .map(|child| shell.send_child_to_background(child, ProcessState::Running)) - { - let stderr = io::stderr(); - let mut stderr = stderr.lock(); - let _ = writeln!(stderr, "ion: command not found: {}", get_command_name(command)); - } - }, JobKind::Pipe(mut from) => { let mut children: Vec<Option<Child>> = Vec::new(); @@ -185,6 +223,7 @@ fn pipe(shell: &mut Shell, commands: &mut [(Command, JobKind)]) -> i32 { } } } + previous_status } @@ -217,8 +256,9 @@ fn wait_on_child(shell: &mut Shell, mut child: Child) -> i32 { if let Ok(signal) = shell.signals.try_recv() { if signal == libc::SIGTSTP { shell.received_sigtstp = true; - shell.suspend(child.id()); - shell.send_child_to_background(child, ProcessState::Stopped); + let pid = child.id(); + shell.suspend(pid); + shell.send_child_to_background(pid, ProcessState::Stopped); break SUCCESS } else { if let Err(why) = child.kill() { @@ -263,8 +303,9 @@ fn wait(shell: &mut Shell, children: &mut Vec<Option<Child>>) -> i32 { if let Ok(signal) = shell.signals.try_recv() { if signal == libc::SIGTSTP { shell.received_sigtstp = true; - shell.suspend(child.id()); - shell.send_child_to_background(child, ProcessState::Stopped); + let pid = child.id(); + shell.suspend(pid); + shell.send_child_to_background(pid, ProcessState::Stopped); break SUCCESS } shell.foreground_send(signal); @@ -304,8 +345,9 @@ fn wait(shell: &mut Shell, children: &mut Vec<Option<Child>>) -> i32 { if let Ok(signal) = shell.signals.try_recv() { if signal == libc::SIGTSTP { shell.received_sigtstp = true; - shell.suspend(child.id()); - shell.send_child_to_background(child, ProcessState::Stopped); + let pid = child.id(); + shell.suspend(pid); + shell.send_child_to_background(pid, ProcessState::Stopped); break SUCCESS } shell.foreground_send(signal);