about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2013-11-11 18:11:17 -0800
committerbors <bors@rust-lang.org>2013-11-11 18:11:17 -0800
commit86787f8befe0f8971f27bd15be1e16ec7aa99e7e (patch)
tree481ca094a98776594c0810dfc5b4767d8e9fae9a /src
parentc0b7972f7d63e4b55098797cb48949b00d3ffee7 (diff)
parentf698decf3701cc51a61bbc3e36971898339ba91e (diff)
downloadrust-86787f8befe0f8971f27bd15be1e16ec7aa99e7e.tar.gz
rust-86787f8befe0f8971f27bd15be1e16ec7aa99e7e.zip
auto merge of #10109 : pcmattman/rust/pass-nonzero-exit-status-on-termination-by-signal, r=alexcrichton
The UvProcess exit callback is called with a zero exit status and non-zero termination signal when a child is terminated by a signal.

If a parent checks only the exit status (for example, only checks the return value from `wait()`), it may believe the process completed successfully when it actually failed.

Helpers for common use-cases are in `std::rt::io::process`.

Should resolve https://github.com/mozilla/rust/issues/10062.
Diffstat (limited to 'src')
-rw-r--r--src/compiletest/procsrv.rs3
-rw-r--r--src/compiletest/runtest.rs58
-rw-r--r--src/librustc/back/link.rs13
-rw-r--r--src/librustpkg/api.rs9
-rw-r--r--src/librustpkg/conditions.rs3
-rw-r--r--src/librustpkg/lib.rs9
-rw-r--r--src/librustpkg/source_control.rs10
-rw-r--r--src/librustpkg/tests.rs49
-rw-r--r--src/librustpkg/version.rs4
-rw-r--r--src/librustuv/process.rs14
-rw-r--r--src/libstd/rt/io/process.rs38
-rw-r--r--src/libstd/rt/rtio.rs4
-rw-r--r--src/libstd/run.rs30
-rw-r--r--src/test/run-pass/rtio-processes.rs29
-rw-r--r--src/test/run-pass/signal-exit-status.rs29
15 files changed, 202 insertions, 100 deletions
diff --git a/src/compiletest/procsrv.rs b/src/compiletest/procsrv.rs
index 6b15e21b70a..32ee19badd2 100644
--- a/src/compiletest/procsrv.rs
+++ b/src/compiletest/procsrv.rs
@@ -11,6 +11,7 @@
 use std::os;
 use std::run;
 use std::str;
+use std::rt::io::process::ProcessExit;
 
 #[cfg(target_os = "win32")]
 fn target_env(lib_path: &str, prog: &str) -> ~[(~str,~str)] {
@@ -39,7 +40,7 @@ fn target_env(_lib_path: &str, _prog: &str) -> ~[(~str,~str)] {
     os::env()
 }
 
-pub struct Result {status: int, out: ~str, err: ~str}
+pub struct Result {status: ProcessExit, out: ~str, err: ~str}
 
 pub fn run(lib_path: &str,
            prog: &str,
diff --git a/src/compiletest/runtest.rs b/src/compiletest/runtest.rs
index 1701961b293..ffd5e721d67 100644
--- a/src/compiletest/runtest.rs
+++ b/src/compiletest/runtest.rs
@@ -23,6 +23,8 @@ use util::logv;
 use std::rt::io;
 use std::rt::io::fs;
 use std::rt::io::File;
+use std::rt::io::process;
+use std::rt::io::process::ProcessExit;
 use std::os;
 use std::str;
 use std::vec;
@@ -60,7 +62,7 @@ pub fn run_metrics(config: config, testfile: ~str, mm: &mut MetricMap) {
 fn run_cfail_test(config: &config, props: &TestProps, testfile: &Path) {
     let ProcRes = compile_test(config, props, testfile);
 
-    if ProcRes.status == 0 {
+    if ProcRes.status.success() {
         fatal_ProcRes(~"compile-fail test compiled successfully!", &ProcRes);
     }
 
@@ -81,7 +83,7 @@ fn run_rfail_test(config: &config, props: &TestProps, testfile: &Path) {
     let ProcRes = if !config.jit {
         let ProcRes = compile_test(config, props, testfile);
 
-        if ProcRes.status != 0 {
+        if !ProcRes.status.success() {
             fatal_ProcRes(~"compilation failed!", &ProcRes);
         }
 
@@ -92,7 +94,7 @@ fn run_rfail_test(config: &config, props: &TestProps, testfile: &Path) {
 
     // The value our Makefile configures valgrind to return on failure
     static VALGRIND_ERR: int = 100;
-    if ProcRes.status == VALGRIND_ERR {
+    if ProcRes.status.matches_exit_status(VALGRIND_ERR) {
         fatal_ProcRes(~"run-fail test isn't valgrind-clean!", &ProcRes);
     }
 
@@ -115,10 +117,9 @@ fn run_rfail_test(config: &config, props: &TestProps, testfile: &Path) {
 fn check_correct_failure_status(ProcRes: &ProcRes) {
     // The value the rust runtime returns on failure
     static RUST_ERR: int = 101;
-    if ProcRes.status != RUST_ERR {
+    if !ProcRes.status.matches_exit_status(RUST_ERR) {
         fatal_ProcRes(
-            format!("failure produced the wrong error code: {}",
-                    ProcRes.status),
+            format!("failure produced the wrong error: {}", ProcRes.status),
             ProcRes);
     }
 }
@@ -127,19 +128,19 @@ fn run_rpass_test(config: &config, props: &TestProps, testfile: &Path) {
     if !config.jit {
         let mut ProcRes = compile_test(config, props, testfile);
 
-        if ProcRes.status != 0 {
+        if !ProcRes.status.success() {
             fatal_ProcRes(~"compilation failed!", &ProcRes);
         }
 
         ProcRes = exec_compiled_test(config, props, testfile);
 
-        if ProcRes.status != 0 {
+        if !ProcRes.status.success() {
             fatal_ProcRes(~"test run failed!", &ProcRes);
         }
     } else {
         let ProcRes = jit_test(config, props, testfile);
 
-        if ProcRes.status != 0 { fatal_ProcRes(~"jit failed!", &ProcRes); }
+        if !ProcRes.status.success() { fatal_ProcRes(~"jit failed!", &ProcRes); }
     }
 }
 
@@ -160,7 +161,7 @@ fn run_pretty_test(config: &config, props: &TestProps, testfile: &Path) {
         logv(config, format!("pretty-printing round {}", round));
         let ProcRes = print_source(config, testfile, srcs[round].clone());
 
-        if ProcRes.status != 0 {
+        if !ProcRes.status.success() {
             fatal_ProcRes(format!("pretty-printing failed in round {}", round),
                           &ProcRes);
         }
@@ -192,7 +193,7 @@ fn run_pretty_test(config: &config, props: &TestProps, testfile: &Path) {
     // Finally, let's make sure it actually appears to remain valid code
     let ProcRes = typecheck_source(config, props, testfile, actual);
 
-    if ProcRes.status != 0 {
+    if !ProcRes.status.success() {
         fatal_ProcRes(~"pretty-printed source does not typecheck", &ProcRes);
     }
 
@@ -264,7 +265,7 @@ fn run_debuginfo_test(config: &config, props: &TestProps, testfile: &Path) {
 
     // compile test file (it shoud have 'compile-flags:-g' in the header)
     let mut ProcRes = compile_test(config, props, testfile);
-    if ProcRes.status != 0 {
+    if !ProcRes.status.success() {
         fatal_ProcRes(~"compilation failed!", &ProcRes);
     }
 
@@ -375,7 +376,7 @@ fn run_debuginfo_test(config: &config, props: &TestProps, testfile: &Path) {
         }
     }
 
-    if ProcRes.status != 0 {
+    if !ProcRes.status.success() {
         fatal(~"gdb failed to execute");
     }
     let num_check_lines = check_lines.len();
@@ -431,7 +432,7 @@ fn check_error_patterns(props: &TestProps,
         }
     }
 
-    if ProcRes.status == 0 {
+    if ProcRes.status.success() {
         fatal(~"process did not return an error status");
     }
 
@@ -473,7 +474,7 @@ fn check_expected_errors(expected_errors: ~[errors::ExpectedError],
     let mut found_flags = vec::from_elem(
         expected_errors.len(), false);
 
-    if ProcRes.status == 0 {
+    if ProcRes.status.success() {
         fatal(~"process did not return an error status");
     }
 
@@ -625,7 +626,7 @@ fn scan_string(haystack: &str, needle: &str, idx: &mut uint) -> bool {
 
 struct ProcArgs {prog: ~str, args: ~[~str]}
 
-struct ProcRes {status: int, stdout: ~str, stderr: ~str, cmdline: ~str}
+struct ProcRes {status: ProcessExit, stdout: ~str, stderr: ~str, cmdline: ~str}
 
 fn compile_test(config: &config, props: &TestProps,
                 testfile: &Path) -> ProcRes {
@@ -692,7 +693,7 @@ fn compose_and_run_compiler(
                               |a,b| make_lib_name(a, b, testfile), &abs_ab);
         let auxres = compose_and_run(config, &abs_ab, aux_args, ~[],
                                      config.compile_lib_path, None);
-        if auxres.status != 0 {
+        if !auxres.status.success() {
             fatal_ProcRes(
                 format!("auxiliary build of {} failed to compile: ",
                      abs_ab.display()),
@@ -966,7 +967,12 @@ fn _arm_exec_compiled_test(config: &config, props: &TestProps,
 
     dump_output(config, testfile, stdout_out, stderr_out);
 
-    ProcRes {status: exitcode, stdout: stdout_out, stderr: stderr_out, cmdline: cmdline }
+    ProcRes {
+        status: process::ExitStatus(exitcode),
+        stdout: stdout_out,
+        stderr: stderr_out,
+        cmdline: cmdline
+    }
 }
 
 fn _dummy_exec_compiled_test(config: &config, props: &TestProps,
@@ -976,9 +982,9 @@ fn _dummy_exec_compiled_test(config: &config, props: &TestProps,
     let cmdline = make_cmdline("", args.prog, args.args);
 
     match config.mode {
-        mode_run_fail => ProcRes {status: 101, stdout: ~"",
+        mode_run_fail => ProcRes {status: process::ExitStatus(101), stdout: ~"",
                                  stderr: ~"", cmdline: cmdline},
-        _             => ProcRes {status: 0, stdout: ~"",
+        _             => ProcRes {status: process::ExitStatus(0), stdout: ~"",
                                  stderr: ~"", cmdline: cmdline}
     }
 }
@@ -1099,33 +1105,33 @@ fn run_codegen_test(config: &config, props: &TestProps,
     }
 
     let mut ProcRes = compile_test_and_save_bitcode(config, props, testfile);
-    if ProcRes.status != 0 {
+    if !ProcRes.status.success() {
         fatal_ProcRes(~"compilation failed!", &ProcRes);
     }
 
     ProcRes = extract_function_from_bitcode(config, props, "test", testfile, "");
-    if ProcRes.status != 0 {
+    if !ProcRes.status.success() {
         fatal_ProcRes(~"extracting 'test' function failed", &ProcRes);
     }
 
     ProcRes = disassemble_extract(config, props, testfile, "");
-    if ProcRes.status != 0 {
+    if !ProcRes.status.success() {
         fatal_ProcRes(~"disassembling extract failed", &ProcRes);
     }
 
 
     let mut ProcRes = compile_cc_with_clang_and_save_bitcode(config, props, testfile);
-    if ProcRes.status != 0 {
+    if !ProcRes.status.success() {
         fatal_ProcRes(~"compilation failed!", &ProcRes);
     }
 
     ProcRes = extract_function_from_bitcode(config, props, "test", testfile, "clang");
-    if ProcRes.status != 0 {
+    if !ProcRes.status.success() {
         fatal_ProcRes(~"extracting 'test' function failed", &ProcRes);
     }
 
     ProcRes = disassemble_extract(config, props, testfile, "clang");
-    if ProcRes.status != 0 {
+    if !ProcRes.status.success() {
         fatal_ProcRes(~"disassembling extract failed", &ProcRes);
     }
 
diff --git a/src/librustc/back/link.rs b/src/librustc/back/link.rs
index 21268c132ba..9b0a2c5fde8 100644
--- a/src/librustc/back/link.rs
+++ b/src/librustc/back/link.rs
@@ -378,9 +378,8 @@ pub mod write {
 
         let prog = run::process_output(cc_prog, cc_args);
 
-        if prog.status != 0 {
-            sess.err(format!("building with `{}` failed with code {}",
-                        cc_prog, prog.status));
+        if !prog.status.success() {
+            sess.err(format!("linking with `{}` failed: {}", cc_prog, prog.status));
             sess.note(format!("{} arguments: {}",
                         cc_prog, cc_args.connect(" ")));
             sess.note(str::from_utf8(prog.error + prog.output));
@@ -947,11 +946,11 @@ pub fn link_binary(sess: Session,
 
     // We run 'cc' here
     let prog = run::process_output(cc_prog, cc_args);
-    if 0 != prog.status {
-        sess.err(format!("linking with `{}` failed with code {}",
-                      cc_prog, prog.status));
+
+    if !prog.status.success() {
+        sess.err(format!("linking with `{}` failed: {}", cc_prog, prog.status));
         sess.note(format!("{} arguments: {}",
-                       cc_prog, cc_args.connect(" ")));
+                    cc_prog, cc_args.connect(" ")));
         sess.note(str::from_utf8(prog.error + prog.output));
         sess.abort_if_errors();
     }
diff --git a/src/librustpkg/api.rs b/src/librustpkg/api.rs
index c0ffd66d22e..bb73882bde8 100644
--- a/src/librustpkg/api.rs
+++ b/src/librustpkg/api.rs
@@ -159,17 +159,16 @@ pub fn build_library_in_workspace(exec: &mut workcache::Exec,
 
     let all_args = flags + absolute_paths + cc_args +
          ~[~"-o", out_name.as_str().unwrap().to_owned()];
-    let exit_code = run::process_status(tool, all_args);
-    if exit_code != 0 {
-        command_failed.raise((tool.to_owned(), all_args, exit_code))
-    }
-    else {
+    let exit_process = run::process_status(tool, all_args);
+    if exit_process.success() {
         let out_name_str = out_name.as_str().unwrap().to_owned();
         exec.discover_output("binary",
                              out_name_str,
                              digest_only_date(&out_name));
         context.add_library_path(out_name.dir_path());
         out_name_str
+    } else {
+        command_failed.raise((tool.to_owned(), all_args, exit_process))
     }
 }
 
diff --git a/src/librustpkg/conditions.rs b/src/librustpkg/conditions.rs
index 91edc275826..6831b6ec312 100644
--- a/src/librustpkg/conditions.rs
+++ b/src/librustpkg/conditions.rs
@@ -13,6 +13,7 @@
 pub use std::path::Path;
 pub use package_id::PkgId;
 pub use std::rt::io::FileStat;
+pub use std::rt::io::process::ProcessExit;
 
 condition! {
     pub bad_path: (Path, ~str) -> Path;
@@ -57,5 +58,5 @@ condition! {
 condition! {
     // str is output of applying the command (first component)
     // to the args (second component)
-    pub command_failed: (~str, ~[~str], int) -> ~str;
+    pub command_failed: (~str, ~[~str], ProcessExit) -> ~str;
 }
diff --git a/src/librustpkg/lib.rs b/src/librustpkg/lib.rs
index f7d10d6b5ef..94978b4a7e1 100644
--- a/src/librustpkg/lib.rs
+++ b/src/librustpkg/lib.rs
@@ -26,6 +26,7 @@ extern mod rustc;
 extern mod syntax;
 
 use std::{os, result, run, str, task};
+use std::rt::io::process;
 use std::hashmap::HashSet;
 use std::rt::io;
 use std::rt::io::fs;
@@ -164,14 +165,14 @@ impl<'self> PkgScript<'self> {
     /// is the command to pass to it (e.g., "build", "clean", "install")
     /// Returns a pair of an exit code and list of configs (obtained by
     /// calling the package script's configs() function if it exists
-    fn run_custom(exe: &Path, sysroot: &Path) -> (~[~str], int) {
+    fn run_custom(exe: &Path, sysroot: &Path) -> (~[~str], process::ProcessExit) {
         debug!("Running program: {} {} {}", exe.as_str().unwrap().to_owned(),
                sysroot.display(), "install");
         // FIXME #7401 should support commands besides `install`
         // FIXME (#9639): This needs to handle non-utf8 paths
         let status = run::process_status(exe.as_str().unwrap(),
                                          [sysroot.as_str().unwrap().to_owned(), ~"install"]);
-        if status != 0 {
+        if !status.success() {
             debug!("run_custom: first pkg command failed with {:?}", status);
             (~[], status)
         }
@@ -486,7 +487,7 @@ impl CtxMethods for BuildContext {
                 // We always *run* the package script
                 let (cfgs, hook_result) = PkgScript::run_custom(&Path::new(pkg_exe), &sysroot);
                 debug!("Command return code = {:?}", hook_result);
-                if hook_result != 0 {
+                if !hook_result.success() {
                     fail!("Error running custom build command")
                 }
                 custom = true;
@@ -697,7 +698,7 @@ impl CtxMethods for BuildContext {
                 debug!("test: test_exec = {}", test_exec.display());
                 // FIXME (#9639): This needs to handle non-utf8 paths
                 let status = run::process_status(test_exec.as_str().unwrap(), [~"--test"]);
-                if status != 0 {
+                if !status.success() {
                     fail!("Some tests failed");
                 }
             }
diff --git a/src/librustpkg/source_control.rs b/src/librustpkg/source_control.rs
index bcda3168bd8..6f484a7be5d 100644
--- a/src/librustpkg/source_control.rs
+++ b/src/librustpkg/source_control.rs
@@ -36,7 +36,7 @@ pub fn safe_git_clone(source: &Path, v: &Version, target: &Path) -> CloneResult
             let outp = run::process_output("git", [~"clone",
                                                    source.as_str().unwrap().to_owned(),
                                                    target.as_str().unwrap().to_owned()]);
-            if outp.status != 0 {
+            if !outp.status.success() {
                 println(str::from_utf8_owned(outp.output.clone()));
                 println(str::from_utf8_owned(outp.error));
                 return DirToUse(target.clone());
@@ -52,7 +52,7 @@ pub fn safe_git_clone(source: &Path, v: &Version, target: &Path) -> CloneResult
                             [format!("--work-tree={}", target.as_str().unwrap().to_owned()),
                              format!("--git-dir={}", git_dir.as_str().unwrap().to_owned()),
                              ~"checkout", format!("{}", *s)]);
-                        if outp.status != 0 {
+                        if !outp.status.success() {
                             println(str::from_utf8_owned(outp.output.clone()));
                             println(str::from_utf8_owned(outp.error));
                             return DirToUse(target.clone());
@@ -73,7 +73,7 @@ pub fn safe_git_clone(source: &Path, v: &Version, target: &Path) -> CloneResult
                         format!("--git-dir={}", git_dir.as_str().unwrap().to_owned()),
                         ~"pull", ~"--no-edit", source.as_str().unwrap().to_owned()];
             let outp = run::process_output("git", args);
-            assert!(outp.status == 0);
+            assert!(outp.status.success());
         }
         CheckedOutSources
     } else {
@@ -110,7 +110,7 @@ pub fn git_clone_url(source: &str, target: &Path, v: &Version) {
     // FIXME (#9639): This needs to handle non-utf8 paths
     let outp = run::process_output("git", [~"clone", source.to_owned(),
                                            target.as_str().unwrap().to_owned()]);
-    if outp.status != 0 {
+    if !outp.status.success() {
          debug!("{}", str::from_utf8_owned(outp.output.clone()));
          debug!("{}", str::from_utf8_owned(outp.error));
          cond.raise((source.to_owned(), target.clone()))
@@ -120,7 +120,7 @@ pub fn git_clone_url(source: &str, target: &Path, v: &Version) {
             &ExactRevision(ref s) | &Tagged(ref s) => {
                     let outp = process_output_in_cwd("git", [~"checkout", s.to_owned()],
                                                          target);
-                    if outp.status != 0 {
+                    if !outp.status.success() {
                         debug!("{}", str::from_utf8_owned(outp.output.clone()));
                         debug!("{}", str::from_utf8_owned(outp.error));
                         cond.raise((source.to_owned(), target.clone()))
diff --git a/src/librustpkg/tests.rs b/src/librustpkg/tests.rs
index b65e1b7ba02..99910242582 100644
--- a/src/librustpkg/tests.rs
+++ b/src/librustpkg/tests.rs
@@ -15,6 +15,8 @@ use std::{os, run, str, task};
 use std::rt::io;
 use std::rt::io::fs;
 use std::rt::io::File;
+use std::rt::io::process;
+use std::rt::io::process::ProcessExit;
 use extra::arc::Arc;
 use extra::arc::RWArc;
 use extra::tempfile::TempDir;
@@ -149,7 +151,7 @@ fn run_git(args: &[~str], env: Option<~[(~str, ~str)]>, cwd: &Path, err_msg: &st
         err_fd: None
     });
     let rslt = prog.finish_with_output();
-    if rslt.status != 0 {
+    if !rslt.status.success() {
         fail!("{} [git returned {:?}, output = {}, error = {}]", err_msg,
            rslt.status, str::from_utf8(rslt.output), str::from_utf8(rslt.error));
     }
@@ -251,7 +253,7 @@ fn command_line_test_expect_fail(args: &[~str],
                                  expected_exitcode: int) {
     match command_line_test_with_env(args, cwd, env) {
         Success(_) => fail!("Should have failed with {}, but it succeeded", expected_exitcode),
-        Fail(error) if error == expected_exitcode => (), // ok
+        Fail(error) if error.matches_exit_status(expected_exitcode) => (), // ok
         Fail(other) => fail!("Expected to fail with {}, but failed with {} instead",
                               expected_exitcode, other)
     }
@@ -259,7 +261,7 @@ fn command_line_test_expect_fail(args: &[~str],
 
 enum ProcessResult {
     Success(ProcessOutput),
-    Fail(int) // exit code
+    Fail(ProcessExit)
 }
 
 /// Runs `rustpkg` (based on the directory that this executable was
@@ -289,7 +291,7 @@ fn command_line_test_with_env(args: &[~str], cwd: &Path, env: Option<~[(~str, ~s
                     cmd, args, str::from_utf8(output.output),
                    str::from_utf8(output.error),
                    output.status);
-    if output.status != 0 {
+    if !output.status.success() {
         debug!("Command {} {:?} failed with exit code {:?}; its output was --- {} ---",
               cmd, args, output.status,
               str::from_utf8(output.output) + str::from_utf8(output.error));
@@ -501,9 +503,9 @@ fn touch_source_file(workspace: &Path, pkgid: &PkgId) {
             // should be able to do this w/o a process
             // FIXME (#9639): This needs to handle non-utf8 paths
             // n.b. Bumps time up by 2 seconds to get around granularity issues
-            if run::process_output("touch", [~"--date",
+            if !run::process_output("touch", [~"--date",
                                              ~"+2 seconds",
-                                             p.as_str().unwrap().to_owned()]).status != 0 {
+                                             p.as_str().unwrap().to_owned()]).status.success() {
                 let _ = cond.raise((pkg_src_dir.clone(), ~"Bad path"));
             }
         }
@@ -520,8 +522,8 @@ fn touch_source_file(workspace: &Path, pkgid: &PkgId) {
             // should be able to do this w/o a process
             // FIXME (#9639): This needs to handle non-utf8 paths
             // n.b. Bumps time up by 2 seconds to get around granularity issues
-            if run::process_output("touch", [~"-A02",
-                                             p.as_str().unwrap().to_owned()]).status != 0 {
+            if !run::process_output("touch", [~"-A02",
+                                             p.as_str().unwrap().to_owned()]).status.success() {
                 let _ = cond.raise((pkg_src_dir.clone(), ~"Bad path"));
             }
         }
@@ -1091,7 +1093,8 @@ fn no_rebuilding() {
 
     match command_line_test_partial([~"build", ~"foo"], workspace) {
         Success(*) => (), // ok
-        Fail(status) if status == 65 => fail!("no_rebuilding failed: it tried to rebuild bar"),
+        Fail(status) if status.matches_exit_status(65) =>
+            fail!("no_rebuilding failed: it tried to rebuild bar"),
         Fail(_) => fail!("no_rebuilding failed for some other reason")
     }
 }
@@ -1109,7 +1112,7 @@ fn no_recopying() {
 
     match command_line_test_partial([~"install", ~"foo"], workspace) {
         Success(*) => (), // ok
-        Fail(65) => fail!("no_recopying failed: it tried to re-copy foo"),
+        Fail(process::ExitStatus(65)) => fail!("no_recopying failed: it tried to re-copy foo"),
         Fail(_) => fail!("no_copying failed for some other reason")
     }
 }
@@ -1127,7 +1130,8 @@ fn no_rebuilding_dep() {
     assert!(chmod_read_only(&bar_lib));
     match command_line_test_partial([~"build", ~"foo"], workspace) {
         Success(*) => (), // ok
-        Fail(status) if status == 65 => fail!("no_rebuilding_dep failed: it tried to rebuild bar"),
+        Fail(status) if status.matches_exit_status(65) =>
+            fail!("no_rebuilding_dep failed: it tried to rebuild bar"),
         Fail(_) => fail!("no_rebuilding_dep failed for some other reason")
     }
 }
@@ -1147,7 +1151,7 @@ fn do_rebuild_dep_dates_change() {
 
     match command_line_test_partial([~"build", ~"foo"], workspace) {
         Success(*) => fail!("do_rebuild_dep_dates_change failed: it didn't rebuild bar"),
-        Fail(status) if status == 65 => (), // ok
+        Fail(status) if status.matches_exit_status(65) => (), // ok
         Fail(_) => fail!("do_rebuild_dep_dates_change failed for some other reason")
     }
 }
@@ -1168,7 +1172,7 @@ fn do_rebuild_dep_only_contents_change() {
     // should adjust the datestamp
     match command_line_test_partial([~"build", ~"foo"], workspace) {
         Success(*) => fail!("do_rebuild_dep_only_contents_change failed: it didn't rebuild bar"),
-        Fail(status) if status == 65 => (), // ok
+        Fail(status) if status.matches_exit_status(65) => (), // ok
         Fail(_) => fail!("do_rebuild_dep_only_contents_change failed for some other reason")
     }
 }
@@ -1274,7 +1278,7 @@ fn test_extern_mod() {
         err_fd: None
     });
     let outp = prog.finish_with_output();
-    if outp.status != 0 {
+    if !outp.status.success() {
         fail!("output was {}, error was {}",
               str::from_utf8(outp.output),
               str::from_utf8(outp.error));
@@ -1329,7 +1333,7 @@ fn test_extern_mod_simpler() {
         err_fd: None
     });
     let outp = prog.finish_with_output();
-    if outp.status != 0 {
+    if !outp.status.success() {
         fail!("output was {}, error was {}",
               str::from_utf8(outp.output),
               str::from_utf8(outp.error));
@@ -2144,7 +2148,7 @@ fn test_rebuild_when_needed() {
     chmod_read_only(&test_executable);
     match command_line_test_partial([~"test", ~"foo"], foo_workspace) {
         Success(*) => fail!("test_rebuild_when_needed didn't rebuild"),
-        Fail(status) if status == 65 => (), // ok
+        Fail(status) if status.matches_exit_status(65) => (), // ok
         Fail(_) => fail!("test_rebuild_when_needed failed for some other reason")
     }
 }
@@ -2164,7 +2168,8 @@ fn test_no_rebuilding() {
     chmod_read_only(&test_executable);
     match command_line_test_partial([~"test", ~"foo"], foo_workspace) {
         Success(*) => (), // ok
-        Fail(status) if status == 65 => fail!("test_no_rebuilding failed: it rebuilt the tests"),
+        Fail(status) if status.matches_exit_status(65) =>
+            fail!("test_no_rebuilding failed: it rebuilt the tests"),
         Fail(_) => fail!("test_no_rebuilding failed for some other reason")
     }
 }
@@ -2359,9 +2364,11 @@ fn test_c_dependency_no_rebuilding() {
 
     match command_line_test_partial([~"build", ~"cdep"], dir) {
         Success(*) => (), // ok
-        Fail(status) if status == 65 => fail!("test_c_dependency_no_rebuilding failed: \
-                                              it tried to rebuild foo.c"),
-        Fail(_) => fail!("test_c_dependency_no_rebuilding failed for some other reason")
+        Fail(status) if status.matches_exit_status(65) =>
+            fail!("test_c_dependency_no_rebuilding failed: \
+                    it tried to rebuild foo.c"),
+        Fail(_) =>
+            fail!("test_c_dependency_no_rebuilding failed for some other reason")
     }
 }
 
@@ -2396,7 +2403,7 @@ fn test_c_dependency_yes_rebuilding() {
     match command_line_test_partial([~"build", ~"cdep"], dir) {
         Success(*) => fail!("test_c_dependency_yes_rebuilding failed: \
                             it didn't rebuild and should have"),
-        Fail(status) if status == 65 => (),
+        Fail(status) if status.matches_exit_status(65) => (),
         Fail(_) => fail!("test_c_dependency_yes_rebuilding failed for some other reason")
     }
 }
diff --git a/src/librustpkg/version.rs b/src/librustpkg/version.rs
index eff16cb9996..d40a104ccda 100644
--- a/src/librustpkg/version.rs
+++ b/src/librustpkg/version.rs
@@ -109,7 +109,7 @@ pub fn try_getting_local_version(local_path: &Path) -> Option<Version> {
 
         debug!("git --git-dir={} tag -l ~~~> {:?}", git_dir.display(), outp.status);
 
-        if outp.status != 0 {
+        if !outp.status.success() {
             continue;
         }
 
@@ -143,7 +143,7 @@ pub fn try_getting_version(remote_path: &Path) -> Option<Version> {
         let outp  = run::process_output("git", [~"clone", format!("https://{}",
                                                                   remote_path.as_str().unwrap()),
                                                 tmp_dir.as_str().unwrap().to_owned()]);
-        if outp.status == 0 {
+        if outp.status.success() {
             debug!("Cloned it... ( {}, {} )",
                    str::from_utf8(outp.output),
                    str::from_utf8(outp.error));
diff --git a/src/librustuv/process.rs b/src/librustuv/process.rs
index 7e75515972c..e49930c1fc9 100644
--- a/src/librustuv/process.rs
+++ b/src/librustuv/process.rs
@@ -33,8 +33,7 @@ pub struct Process {
     to_wake: Option<BlockedTask>,
 
     /// Collected from the exit_cb
-    exit_status: Option<int>,
-    term_signal: Option<int>,
+    exit_status: Option<ProcessExit>,
 }
 
 impl Process {
@@ -82,7 +81,6 @@ impl Process {
                     home: get_handle_to_current_scheduler!(),
                     to_wake: None,
                     exit_status: None,
-                    term_signal: None,
                 };
                 match unsafe {
                     uvll::uv_spawn(loop_.handle, handle, &options)
@@ -106,9 +104,10 @@ extern fn on_exit(handle: *uvll::uv_process_t,
     let p: &mut Process = unsafe { UvHandle::from_uv_handle(&handle) };
 
     assert!(p.exit_status.is_none());
-    assert!(p.term_signal.is_none());
-    p.exit_status = Some(exit_status as int);
-    p.term_signal = Some(term_signal as int);
+    p.exit_status = Some(match term_signal {
+        0 => ExitStatus(exit_status as int),
+        n => ExitSignal(n as int),
+    });
 
     match p.to_wake.take() {
         Some(task) => {
@@ -209,7 +208,7 @@ impl RtioProcess for Process {
         }
     }
 
-    fn wait(&mut self) -> int {
+    fn wait(&mut self) -> ProcessExit {
         // Make sure (on the home scheduler) that we have an exit status listed
         let _m = self.fire_homing_missile();
         match self.exit_status {
@@ -223,7 +222,6 @@ impl RtioProcess for Process {
             }
         }
 
-        // FIXME(#10109): this is wrong
         self.exit_status.unwrap()
     }
 }
diff --git a/src/libstd/rt/io/process.rs b/src/libstd/rt/io/process.rs
index ae087099d1f..6b21cde2488 100644
--- a/src/libstd/rt/io/process.rs
+++ b/src/libstd/rt/io/process.rs
@@ -18,6 +18,8 @@ use rt::io;
 use rt::io::io_error;
 use rt::rtio::{RtioProcess, IoFactory, with_local_io};
 
+use fmt;
+
 // windows values don't matter as long as they're at least one of unix's
 // TERM/KILL/INT signals
 #[cfg(windows)] pub static PleaseExitSignal: int = 15;
@@ -79,6 +81,40 @@ pub enum StdioContainer {
     CreatePipe(bool /* readable */, bool /* writable */),
 }
 
+/// Describes the result of a process after it has terminated.
+#[deriving(Eq)]
+pub enum ProcessExit {
+    /// Normal termination with an exit status.
+    ExitStatus(int),
+
+    /// Termination by signal, with the signal number.
+    ExitSignal(int),
+}
+
+impl fmt::Default for ProcessExit {
+    /// Format a ProcessExit enum, to nicely present the information.
+    fn fmt(obj: &ProcessExit, f: &mut fmt::Formatter) {
+        match *obj {
+            ExitStatus(code) =>  write!(f.buf, "exit code: {}", code),
+            ExitSignal(code) =>  write!(f.buf, "signal: {}", code),
+        }
+    }
+}
+
+impl ProcessExit {
+    /// Was termination successful? Signal termination not considered a success,
+    /// and success is defined as a zero exit status.
+    pub fn success(&self) -> bool {
+        return self.matches_exit_status(0);
+    }
+
+    /// Checks whether this ProcessExit matches the given exit status.
+    /// Termination by signal will never match an exit code.
+    pub fn matches_exit_status(&self, wanted: int) -> bool {
+        *self == ExitStatus(wanted)
+    }
+}
+
 impl Process {
     /// Creates a new pipe initialized, but not bound to any particular
     /// source/destination
@@ -122,7 +158,7 @@ impl Process {
     /// Wait for the child to exit completely, returning the status that it
     /// exited with. This function will continue to have the same return value
     /// after it has been called at least once.
-    pub fn wait(&mut self) -> int { self.handle.wait() }
+    pub fn wait(&mut self) -> ProcessExit { self.handle.wait() }
 }
 
 impl Drop for Process {
diff --git a/src/libstd/rt/rtio.rs b/src/libstd/rt/rtio.rs
index d623914cdad..9b1103b8a74 100644
--- a/src/libstd/rt/rtio.rs
+++ b/src/libstd/rt/rtio.rs
@@ -18,7 +18,7 @@ use c_str::CString;
 use ai = rt::io::net::addrinfo;
 use rt::io::IoError;
 use rt::io::signal::Signum;
-use super::io::process::ProcessConfig;
+use super::io::process::{ProcessConfig, ProcessExit};
 use super::io::net::ip::{IpAddr, SocketAddr};
 use path::Path;
 use super::io::{SeekStyle};
@@ -201,7 +201,7 @@ pub trait RtioFileStream {
 pub trait RtioProcess {
     fn id(&self) -> libc::pid_t;
     fn kill(&mut self, signal: int) -> Result<(), IoError>;
-    fn wait(&mut self) -> int;
+    fn wait(&mut self) -> ProcessExit;
 }
 
 pub trait RtioPipe {
diff --git a/src/libstd/run.rs b/src/libstd/run.rs
index 844f61dda0b..c9b33a14c52 100644
--- a/src/libstd/run.rs
+++ b/src/libstd/run.rs
@@ -18,6 +18,7 @@ use libc::{pid_t, c_int};
 use libc;
 use prelude::*;
 use rt::io::process;
+use rt::io::process::ProcessExit;
 use rt::io;
 use rt::io::Reader;
 use task;
@@ -100,7 +101,7 @@ impl <'self> ProcessOptions<'self> {
 /// The output of a finished process.
 pub struct ProcessOutput {
     /// The status (exit code) of the process.
-    status: int,
+    status: ProcessExit,
 
     /// The data that the process wrote to stdout.
     output: ~[u8],
@@ -194,7 +195,7 @@ impl Process {
      *
      * If the child has already been finished then the exit code is returned.
      */
-    pub fn finish(&mut self) -> int { self.inner.wait() }
+    pub fn finish(&mut self) -> ProcessExit { self.inner.wait() }
 
     /**
      * Closes the handle to stdin, waits for the child process to terminate, and
@@ -296,7 +297,7 @@ impl Process {
  *
  * The process's exit code
  */
-pub fn process_status(prog: &str, args: &[~str]) -> int {
+pub fn process_status(prog: &str, args: &[~str]) -> ProcessExit {
     let mut prog = Process::new(prog, args, ProcessOptions {
         env: None,
         dir: None,
@@ -340,8 +341,11 @@ mod tests {
     #[test]
     #[cfg(not(target_os="android"))] // FIXME(#10380)
     fn test_process_status() {
-        assert_eq!(run::process_status("false", []), 1);
-        assert_eq!(run::process_status("true", []), 0);
+        let mut status = run::process_status("false", []);
+        assert!(status.matches_exit_status(1));
+
+        status = run::process_status("true", []);
+        assert!(status.success());
     }
 
     #[test]
@@ -352,7 +356,7 @@ mod tests {
              = run::process_output("echo", [~"hello"]);
         let output_str = str::from_utf8(output);
 
-        assert_eq!(status, 0);
+        assert!(status.success());
         assert_eq!(output_str.trim().to_owned(), ~"hello");
         // FIXME #7224
         if !running_on_valgrind() {
@@ -367,7 +371,7 @@ mod tests {
         let run::ProcessOutput {status, output, error}
              = run::process_output("mkdir", [~"."]);
 
-        assert_eq!(status, 1);
+        assert!(status.matches_exit_status(1));
         assert_eq!(output, ~[]);
         assert!(!error.is_empty());
     }
@@ -424,15 +428,15 @@ mod tests {
     #[cfg(not(target_os="android"))] // FIXME(#10380)
     fn test_finish_once() {
         let mut prog = run::Process::new("false", [], run::ProcessOptions::new());
-        assert_eq!(prog.finish(), 1);
+        assert!(prog.finish().matches_exit_status(1));
     }
 
     #[test]
     #[cfg(not(target_os="android"))] // FIXME(#10380)
     fn test_finish_twice() {
         let mut prog = run::Process::new("false", [], run::ProcessOptions::new());
-        assert_eq!(prog.finish(), 1);
-        assert_eq!(prog.finish(), 1);
+        assert!(prog.finish().matches_exit_status(1));
+        assert!(prog.finish().matches_exit_status(1));
     }
 
     #[test]
@@ -444,7 +448,7 @@ mod tests {
             = prog.finish_with_output();
         let output_str = str::from_utf8(output);
 
-        assert_eq!(status, 0);
+        assert!(status.success());
         assert_eq!(output_str.trim().to_owned(), ~"hello");
         // FIXME #7224
         if !running_on_valgrind() {
@@ -462,7 +466,7 @@ mod tests {
 
         let output_str = str::from_utf8(output);
 
-        assert_eq!(status, 0);
+        assert!(status.success());
         assert_eq!(output_str.trim().to_owned(), ~"hello");
         // FIXME #7224
         if !running_on_valgrind() {
@@ -472,7 +476,7 @@ mod tests {
         let run::ProcessOutput {status, output, error}
             = prog.finish_with_output();
 
-        assert_eq!(status, 0);
+        assert!(status.success());
         assert_eq!(output, ~[]);
         // FIXME #7224
         if !running_on_valgrind() {
diff --git a/src/test/run-pass/rtio-processes.rs b/src/test/run-pass/rtio-processes.rs
index 65cf8722eed..2228f284fc3 100644
--- a/src/test/run-pass/rtio-processes.rs
+++ b/src/test/run-pass/rtio-processes.rs
@@ -24,6 +24,7 @@
 // See #9341
 
 use std::rt::io;
+use std::rt::io::process;
 use std::rt::io::process::{Process, ProcessConfig, CreatePipe, Ignored};
 use std::str;
 
@@ -42,7 +43,7 @@ fn smoke() {
     let p = Process::new(args);
     assert!(p.is_some());
     let mut p = p.unwrap();
-    assert_eq!(p.wait(), 0);
+    assert!(p.wait().success());
 }
 
 #[test]
@@ -78,7 +79,27 @@ fn exit_reported_right() {
     let p = Process::new(args);
     assert!(p.is_some());
     let mut p = p.unwrap();
-    assert_eq!(p.wait(), 1);
+    assert!(p.wait().matches_exit_status(1));
+}
+
+#[test]
+#[cfg(unix, not(target_os="android"))]
+fn signal_reported_right() {
+    let io = ~[];
+    let args = ProcessConfig {
+        program: "/bin/sh",
+        args: [~"-c", ~"kill -1 $$"],
+        env: None,
+        cwd: None,
+        io: io,
+    };
+    let p = Process::new(args);
+    assert!(p.is_some());
+    let mut p = p.unwrap();
+    match p.wait() {
+        process::ExitSignal(1) => {},
+        result => fail!("not terminated by signal 1 (instead, {})", result),
+    }
 }
 
 fn read_all(input: &mut Reader) -> ~str {
@@ -100,7 +121,7 @@ fn run_output(args: ProcessConfig) -> ~str {
     assert!(p.io[0].is_none());
     assert!(p.io[1].is_some());
     let ret = read_all(p.io[1].get_mut_ref() as &mut Reader);
-    assert_eq!(p.wait(), 0);
+    assert!(p.wait().success());
     return ret;
 }
 
@@ -152,6 +173,6 @@ fn stdin_works() {
     p.io[0].get_mut_ref().write("foobar".as_bytes());
     p.io[0] = None; // close stdin;
     let out = read_all(p.io[1].get_mut_ref() as &mut Reader);
-    assert_eq!(p.wait(), 0);
+    assert!(p.wait().success());
     assert_eq!(out, ~"foobar\n");
 }
diff --git a/src/test/run-pass/signal-exit-status.rs b/src/test/run-pass/signal-exit-status.rs
new file mode 100644
index 00000000000..e3dc4dcaadb
--- /dev/null
+++ b/src/test/run-pass/signal-exit-status.rs
@@ -0,0 +1,29 @@
+// copyright 2013 the rust project developers. see the copyright
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/copyright.
+//
+// licensed under the apache license, version 2.0 <license-apache or
+// http://www.apache.org/licenses/license-2.0> or the mit license
+// <license-mit or http://opensource.org/licenses/mit>, at your
+// option. this file may not be copied, modified, or distributed
+// except according to those terms.
+
+// xfail-fast
+
+use std::{os, run};
+use std::rt::io::process;
+
+fn main() {
+    let args = os::args();
+    if args.len() >= 2 && args[1] == ~"signal" {
+        // Raise a segfault.
+        unsafe { *(0 as *mut int) = 0; }
+    } else {
+        let status = run::process_status(args[0], [~"signal"]);
+        match status {
+            process::ExitSignal(_) => {},
+            _ => fail!("invalid termination (was not signalled): {:?}", status)
+        }
+    }
+}
+