diff --git a/README.md b/README.md index 82e393021e6bf56ec096ed3ec5b9ae0dcd5e0131..4eedf34aef7b383b3df84150cd57c8502832b3cb 100644 --- a/README.md +++ b/README.md @@ -99,9 +99,34 @@ the internal built-in cd command with that path as the argument. examples/ # cd examples/ ``` -### Exiting the Shell +### Job Control -The `exit` command will exit the shell, but unlike Bash, this will send a `SIGTERM` to any background tasks that are still active. +#### Disowning Processes + +Ion features a `disown` command which supports the following flags: + +- **-r**: Remove all running jobs from the background process list. +- **-h**: Specifies that each job supplied will not receive the `SIGHUP` signal when the shell receives a `SIGHUP`. +- **-a**: If no job IDs were supplied, remove all jobs from the background process list. + +Unlike Bash, job arguments are their specified job IDs. + +#### Foreground & Background Tasks + +This area is still a work in progress. When a foreground task is stopped with the **Ctrl+Z** signal, that process will +be added to the background process list as a stopped job. When a supplied command ends with the **&** operator, this +will specify to run the task the background as a running job. To resume a stopped job, executing the `bg <job_id>` +command will send a `SIGCONT` to the specified job ID, hence resuming the job. The `fg` command doesn't work at the +moment though (coming soon). + +#### Exiting the Shell + +The `exit` command will exit the shell, sending a `SIGTERM` to any background tasks that are still active. + +#### Suspending the Shell + +While the shell ignores `SIGTSTP` signals, you can forcefully suspend the shell by executing the `suspend` command, +which forcefully stops the shell via a `SIGSTOP` signal. ### Defining Variables diff --git a/src/builtins/job_control.rs b/src/builtins/job_control.rs index e0a0396edbcf38a2429b01406b06e03676096dc1..b53b10f0645a18031d3492ba68a517ff860b7f5f 100644 --- a/src/builtins/job_control.rs +++ b/src/builtins/job_control.rs @@ -21,8 +21,8 @@ pub fn set_foreground(pid: u32) { #[cfg(all(unix, not(target_os = "redox")))] /// Suspends a given process by it's process ID. -fn suspend(pid: u32) { - let _ = signal::kill(-(pid as pid_t), Some(Signal::SIGTSTP)); +pub fn suspend(pid: u32) { + let _ = signal::kill(-(pid as pid_t), Some(Signal::SIGSTOP)); } #[cfg(all(unix, not(target_os = "redox")))] @@ -31,8 +31,72 @@ fn resume(pid: u32) { let _ = signal::kill(-(pid as pid_t), Some(Signal::SIGCONT)); } +pub fn disown(shell: &mut Shell, args: &[&str]) -> i32 { + let stderr = stderr(); + let mut stderr = stderr.lock(); + const NO_SIGHUP: u8 = 1; + const ALL_JOBS: u8 = 2; + const RUN_JOBS: u8 = 4; + + let mut jobspecs = Vec::new(); + let mut flags = 0u8; + for &arg in args { + match arg { + "-a" => flags |= ALL_JOBS, + "-h" => flags |= NO_SIGHUP, + "-r" => flags |= RUN_JOBS, + _ => match arg.parse::<u32>() { + Ok(jobspec) => jobspecs.push(jobspec), + Err(_) => { + let _ = writeln!(stderr, "ion: disown: invalid jobspec: '{}'", arg); + return FAILURE + }, + } + } + } + + let mut processes = shell.background.lock().unwrap(); + if jobspecs.is_empty() && flags & ALL_JOBS != 0 { + if flags & NO_SIGHUP != 0 { + for process in processes.iter_mut() { + process.ignore_sighup = true; + } + } else { + for process in processes.iter_mut() { + process.state = ProcessState::Empty; + } + } + } else { + jobspecs.sort(); + + let mut jobspecs = jobspecs.into_iter(); + let mut current_jobspec = jobspecs.next().unwrap(); + for (id, process) in processes.iter_mut().enumerate() { + if id == current_jobspec as usize { + if flags & NO_SIGHUP != 0 { process.ignore_sighup = true; } + process.state = ProcessState::Empty; + match jobspecs.next() { + Some(jobspec) => current_jobspec = jobspec, + None => break + } + } + } + + if flags & RUN_JOBS != 0 { + for process in processes.iter_mut() { + if process.state == ProcessState::Running { + process.state = ProcessState::Empty; + } + } + } + } + + SUCCESS +} + + #[cfg(target_os = "redox")] -fn suspend(pid: u32) { +pub fn suspend(pid: u32) { use syscall; let _ = syscall::kill(pid as usize, syscall::SIGSTOP); } diff --git a/src/builtins/mod.rs b/src/builtins/mod.rs index 264f3ffd9807dc642a7e5a5c9423007b0a564a5f..75076252530b2c8562ec749ef14128692eb1b558 100644 --- a/src/builtins/mod.rs +++ b/src/builtins/mod.rs @@ -21,7 +21,7 @@ use std::process; use std::error::Error; use parser::QuoteTerminator; -use shell::job_control::JobControl; +use shell::job_control::{JobControl, ProcessState}; use shell::{Shell, FlowLogic, ShellHistory}; use shell::status::*; @@ -46,9 +46,11 @@ fn exit_builtin() -> Builtin { use nix::sys::signal::{self, Signal as NixSignal}; use libc::pid_t; - // Kill all background tasks before exiting the shell. + // Kill all active background tasks before exiting the shell. for process in shell.background.lock().unwrap().iter() { - let _ = signal::kill(process.pid as pid_t, Some(NixSignal::SIGTERM)); + if process.state != ProcessState::Empty { + let _ = signal::kill(process.pid as pid_t, Some(NixSignal::SIGTERM)); + } } process::exit(args.get(1).and_then(|status| status.parse::<i32>().ok()) @@ -256,6 +258,23 @@ impl Builtin { }) }); + commands.insert("suspend", Builtin { + name: "suspend", + help: "Suspends the shell with a SIGTSTOP signal", + main: Box::new(|args: &[&str], shell: &mut Shell| -> i32 { + job_control::suspend(0); + SUCCESS + }) + }); + + commands.insert("disown", Builtin { + name: "disown", + help: "Disowning a process removes that process from the shell's background process table.", + main: Box::new(|args: &[&str], shell: &mut Shell| -> i32 { + job_control::disown(shell, &args[1..]) + }) + }); + commands.insert("history", Builtin { name: "history", diff --git a/src/main.rs b/src/main.rs index 7bf6860a8e58d66b04c9c1b0f7e6ad3c36cb4f6b..6b604effbf7171a5ca30a656475bcf040dd6005d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -81,13 +81,14 @@ fn main() { let mut core = Core::new().unwrap(); let handle = core.handle(); - // Mask the SIGTSTP signal -- prevents the shell from being stopped + // Block the SIGTSTP signal -- prevents the shell from being stopped // when the foreground group is changed during command execution. - mask_sigstp(); + block_signals(); - // Create a stream that will select over SIGINT and SIGTERM signals. + // Create a stream that will select over SIGINT, SIGTERM, and SIGHUP signals. let signals = Signal::new(unix_signal::SIGINT, &handle).flatten_stream() - .select(Signal::new(unix_signal::SIGTERM, &handle).flatten_stream()); + .select(Signal::new(unix_signal::SIGTERM, &handle).flatten_stream()) + .select(Signal::new(unix_signal::SIGHUP, &handle).flatten_stream()); // Execute the event loop that will listen for and transmit received // signals to the shell. @@ -104,19 +105,22 @@ fn main() { } #[cfg(all(unix, not(target_os = "redox")))] -fn mask_sigstp() { +fn block_signals() { unsafe { - use libc::{sigset_t, SIGTSTP, SIG_BLOCK, sigemptyset, sigaddset, sigprocmask}; + use libc::*; use std::mem; use std::ptr; 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); } } #[cfg(target_os = "redox")] -fn mask_sigstp() { +fn block_signals() { // TODO } diff --git a/src/shell/job_control.rs b/src/shell/job_control.rs index 1dfa4df15f793b6b52938c4e8ce46d43eb6c8326..6ea857756a7b11ba0189d52bb1df5738bfaee1fe 100644 --- a/src/shell/job_control.rs +++ b/src/shell/job_control.rs @@ -17,7 +17,7 @@ pub trait JobControl { fn send_to_background(&mut self, child: u32, state: ProcessState); } -#[derive(Clone)] +#[derive(Clone, Copy, Debug, PartialEq)] /// Defines whether the background process is running or stopped. pub enum ProcessState { Running, @@ -46,9 +46,9 @@ pub fn watch_background_pid ( pid: u32, njob: usize) { - use nix::sys::wait::{waitpid, WaitStatus, WUNTRACED, WNOHANG}; + use nix::sys::wait::*; loop { - match waitpid(-(pid as pid_t), Some(WUNTRACED | WNOHANG)) { + match waitpid(-(pid as pid_t), Some(WUNTRACED)) { Ok(WaitStatus::Exited(_, status)) => { eprintln!("ion: ([{}] {}) exited with {}", njob, pid, status); let mut processes = processes.lock().unwrap(); @@ -77,7 +77,6 @@ pub fn watch_background_pid ( break } } - sleep(Duration::from_millis(100)); } } @@ -93,6 +92,7 @@ pub fn add_to_background ( Some(id) => { (*processes)[id] = BackgroundProcess { pid: pid, + ignore_sighup: false, state: state }; id @@ -101,6 +101,7 @@ pub fn add_to_background ( let njobs = (*processes).len(); (*processes).push(BackgroundProcess { pid: pid, + ignore_sighup: false, state: state }); njobs @@ -115,6 +116,7 @@ pub fn add_to_background ( /// process is executing. pub struct BackgroundProcess { pub pid: u32, + pub ignore_sighup: bool, pub state: ProcessState // TODO: Each process should have the command registered to it // pub command: String @@ -160,6 +162,8 @@ impl<'a> JobControl for Shell<'a> { eprintln!("ion: process ended by signal"); if signal == Signal::SIGTERM { self.handle_signal(libc::SIGTERM); + } else if signal == Signal::SIGHUP { + self.handle_signal(libc::SIGHUP); } else if signal == Signal::SIGINT { self.foreground_send(libc::SIGINT as i32); } @@ -230,9 +234,17 @@ impl<'a> JobControl for Shell<'a> { #[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() { - if let ProcessState::Running = process.state { - let _ = signal::kill(-(process.pid as pid_t), NixSignal::from_c_int(signal as c_int).ok()); + if signal == libc::SIGHUP { + for process in self.background.lock().unwrap().iter() { + if !process.ignore_sighup { + let _ = signal::kill(-(process.pid as pid_t), NixSignal::from_c_int(signal as c_int).ok()); + } + } + } else { + for process in self.background.lock().unwrap().iter() { + if let ProcessState::Running = process.state { + let _ = signal::kill(-(process.pid as pid_t), NixSignal::from_c_int(signal as c_int).ok()); + } } } } @@ -255,8 +267,8 @@ impl<'a> JobControl for Shell<'a> { /// before the shell terminates itself. #[cfg(all(unix, not(target_os = "redox")))] fn handle_signal(&self, signal: i32) { - if signal == libc::SIGTERM { - self.background_send(libc::SIGTERM); + if signal == libc::SIGTERM || signal == libc::SIGHUP { + self.background_send(signal); process::exit(TERMINATED); } } diff --git a/src/shell/pipe.rs b/src/shell/pipe.rs index b741f6a38607f5c55ed85b9a66ce347c734e5304..7dee66a671df9a121be9878d6db55cfa4b253d34 100644 --- a/src/shell/pipe.rs +++ b/src/shell/pipe.rs @@ -44,14 +44,17 @@ mod crossplat { } } - pub fn unmask_sigtstp() { + pub fn unblock_signals() { unsafe { - use libc::{sigset_t, SIG_UNBLOCK, SIGTSTP, sigemptyset, sigaddset, sigprocmask}; + use libc::*; use std::mem; use std::ptr; 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); } } @@ -127,7 +130,7 @@ mod crossplat { } } - pub fn unmask_sigtstp() { + pub fn unblock_signals() { // TODO } @@ -293,7 +296,7 @@ fn fork_pipe(shell: &mut Shell, commands: Vec<(Command, JobKind)>) -> i32 { SUCCESS }, Ok(Fork::Child) => { - unmask_sigtstp(); + unblock_signals(); create_process_group(); exit(pipe(shell, commands, false)); }, @@ -332,7 +335,6 @@ fn pipe ( match kind { JobKind::Pipe(mut mode) => { - // We need to remember the commands as they own the file descriptors that are // created by crossplat::create_pipe. We purposfully drop the pipes that are // owned by a given command in `wait` in order to close those pipes, sending @@ -343,7 +345,7 @@ fn pipe ( macro_rules! spawn_proc { ($cmd:expr) => {{ let child = $cmd.before_exec(move || { - unmask_sigtstp(); + unblock_signals(); create_process_group(); Ok(()) }).spawn(); @@ -412,12 +414,12 @@ fn terminate_fg(shell: &mut Shell) { #[cfg(target_os = "redox")] fn terminate_fg(shell: &mut Shell) { - shell.foreground_send(syscall::SIGTERM as i32); + shell.foreground_send(syscall::SIGTERM as i32); } fn execute_command(shell: &mut Shell, command: &mut Command, foreground: bool) -> i32 { match command.before_exec(move || { - unmask_sigtstp(); + unblock_signals(); create_process_group(); Ok(()) }).spawn() {