Commit caf8b2f7 authored by Michael Aaron Murphy's avatar Michael Aaron Murphy

Resolve SIGPIPE Issues (#385)

parent a2e5875d
[root]
name = "ion-shell"
version = "1.0.4"
version = "1.0.5"
dependencies = [
"ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"app_dirs 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
......
......@@ -2,11 +2,12 @@
name = "ion-shell"
description = "The Ion Shell"
repository = "https://github.com/redox-os/ion"
version = "1.0.4"
version = "1.0.5"
license-file = "LICENSE"
readme = "README.md"
authors = [
"Michael Aaron Murphy <mmstickman@gmail.com>",
"Hunter Goldstein <hunter.d.goldstein@gmail.com>",
"Skyler Berg <skylertheberg@gmail.com>",
"Jeremy Soller <jackpot51@gmail.com>",
"Michael Gattozzi <mgattozzi@gmail.com>",
......@@ -28,7 +29,7 @@ permutate = "0.3"
unicode-segmentation = "1.2"
smallvec = "0.4"
smallstring = "0.1"
calculate = "0.1.*"
calculate = "0.1"
[target.'cfg(target_os = "redox")'.dependencies]
redox_syscall = "0.1"
......
......@@ -24,7 +24,7 @@ mod redox {
}
/// Ensures that the forked child is given a unique process ID.
pub fn create_process_group() {
pub fn create_process_group(pgid: u32) {
}
}
......@@ -45,8 +45,8 @@ mod unix {
}
/// Ensures that the forked child is given a unique process ID.
pub fn create_process_group() {
let _ = setpgid(0, 0);
pub fn create_process_group(pgid: u32) {
let _ = setpgid(0, pgid as i32);
}
}
......@@ -75,7 +75,7 @@ pub fn fork_pipe (
// The child fork should not have any signals blocked, so the shell can control it.
signals::unblock();
// This ensures that the child fork has a unique PGID.
create_process_group();
create_process_group(0);
// After execution of it's commands, exit with the last command's status.
exit(pipe(shell, commands, false));
},
......
......@@ -34,7 +34,14 @@ pub trait JobControl {
fn handle_signal(&self, signal: i32) -> bool;
fn foreground_send(&self, signal: i32);
fn background_send(&self, signal: i32);
fn watch_foreground<F: Fn() -> String>(&mut self, pid: u32, get_command: F) -> i32;
fn watch_foreground <F, D> (
&mut self,
pid: u32,
last_pid: u32,
get_command: F,
drop_command: D
) -> i32 where F: FnOnce() -> String,
D: FnMut(i32);
fn send_to_background(&mut self, child: u32, state: ProcessState, command: String);
}
......@@ -217,16 +224,27 @@ impl<'a> JobControl for Shell<'a> {
}
#[cfg(all(unix, not(target_os = "redox")))]
fn watch_foreground <F: Fn() -> String> (
fn watch_foreground <F: FnOnce() -> String, D: FnMut(i32)> (
&mut self,
pid: u32,
get_command: F
last_pid: u32,
get_command: F,
mut drop_command: D,
) -> i32 {
use nix::sys::wait::{waitpid, WaitStatus, WUNTRACED};
use nix::sys::signal::Signal;
use nix::{Error, Errno};
let mut exit_status = 0;
loop {
match waitpid(-(pid as pid_t), Some(WUNTRACED)) {
Ok(WaitStatus::Exited(_, status)) => break status as i32,
Ok(WaitStatus::Exited(pid, status)) => {
if pid == (last_pid as i32) {
break status as i32
} else {
drop_command(pid);
exit_status = status;
}
}
Ok(WaitStatus::Signaled(_, signal, _)) => {
eprintln!("ion: process ended by signal");
if signal == Signal::SIGTERM {
......@@ -247,6 +265,8 @@ impl<'a> JobControl for Shell<'a> {
break TERMINATED
},
Ok(_) => (),
// ECHILD signifies that all children have exited
Err(Error::Sys(Errno::ECHILD)) => break exit_status as i32,
Err(why) => {
eprintln!("ion: process doesn't exist: {}", why);
break FAILURE
......@@ -256,10 +276,12 @@ impl<'a> JobControl for Shell<'a> {
}
#[cfg(target_os = "redox")]
fn watch_foreground <F: Fn() -> String> (
fn watch_foreground <F: FnOnce() -> String, D: FnMut(i32)> (
&mut self,
pid: u32,
_get_command: F
_last_pid: u32,
_get_command: F,
mut drop_command: D,
) -> i32 {
use std::io::{self, Write};
use std::os::unix::process::ExitStatusExt;
......
......@@ -289,16 +289,22 @@ pub fn pipe (
let mut remember = Vec::new();
// A list of the PIDs in the piped command
let mut children: Vec<u32> = Vec::new();
// The process group by which all of the PIDs belong to.
let mut pgid = 0; // 0 means the PGID is not set yet.
macro_rules! spawn_proc {
($cmd:expr) => {{
let child = $cmd.before_exec(move || {
signals::unblock();
create_process_group();
create_process_group(pgid);
Ok(())
}).spawn();
match child {
Ok(child) => {
if pgid == 0 {
pgid = child.id();
if foreground { set_foreground(pgid); }
}
shell.foreground.push(child.id());
children.push(child.id());
},
......@@ -334,7 +340,7 @@ pub fn pipe (
}
previous_kind = kind;
previous_status = wait(shell, &mut children, remember, foreground);
previous_status = wait(shell, children, remember);
if previous_status == TERMINATED {
terminate_fg(shell);
return previous_status;
......@@ -365,12 +371,12 @@ fn terminate_fg(shell: &mut Shell) {
fn execute_command(shell: &mut Shell, command: &mut Command, foreground: bool) -> i32 {
match command.before_exec(move || {
signals::unblock();
create_process_group();
create_process_group(0);
Ok(())
}).spawn() {
Ok(child) => {
if foreground { set_foreground(child.id()); }
shell.watch_foreground(child.id(), || get_full_command(command))
shell.watch_foreground(child.id(), child.id(), || get_full_command(command), |_| ())
},
Err(_) => {
let stderr = io::stderr();
......@@ -382,31 +388,28 @@ fn execute_command(shell: &mut Shell, command: &mut Command, foreground: bool) -
}
/// Waits for all of the children within a pipe to finish exuecting, returning the
/// exit status of the last process in the queue. TODO: we need a way of
/// enabling the last command in the pipe to close it's FDs so that the SIGPIPE
/// signal is propagated back to the first command. Otherwise, there's an issue
/// where a command like `yes | head` will wait forever, until Ctrl+C'd.
/// exit status of the last process in the queue.
fn wait (
shell: &mut Shell,
children: &mut Vec<u32>,
mut commands: Vec<Command>,
foreground: bool
mut children: Vec<u32>,
mut commands: Vec<Command>
) -> i32 {
let end = children.len() - 1;
for (child, cmd) in children.drain(..end).zip(commands.drain(..end)) {
// It is important that `cmd` gets dropped at the end of this
// block in order to write EOF to the pipes that it owns.
if foreground { set_foreground(child); }
let status = shell.watch_foreground(child, || get_full_command(&cmd));
if status == TERMINATED {
return status
}
}
// 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 child = children.pop().unwrap();
let cmd = commands.pop().unwrap();
if foreground { set_foreground(child); }
shell.watch_foreground(child, || get_full_command(&cmd))
// Each process in the pipe has the same PGID, which is the first process's PID.
let pgid = children[0];
// If the last process exits, we know that all processes should exit.
let last_pid = children[children.len()-1];
// Watch the foreground group, dropping all commands that exit as they exit.
shell.watch_foreground(pgid, last_pid, move || as_string, move |pid| {
if let Some(id) = children.iter().position(|&x| x as i32 == pid) {
commands.remove(id);
children.remove(id);
}
})
}
fn get_command_name(command: &Command) -> String {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment