about summary refs log tree commit diff
path: root/src/libcore/run.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/libcore/run.rs')
-rw-r--r--src/libcore/run.rs413
1 files changed, 346 insertions, 67 deletions
diff --git a/src/libcore/run.rs b/src/libcore/run.rs
index 37401788ca2..7e73b3a3f80 100644
--- a/src/libcore/run.rs
+++ b/src/libcore/run.rs
@@ -22,31 +22,6 @@ use str;
 use task;
 use vec;
 
-pub mod rustrt {
-    use libc::{c_int, c_void};
-    use libc;
-    use run;
-
-    #[abi = "cdecl"]
-    pub extern {
-        unsafe fn rust_run_program(argv: **libc::c_char,
-                                   envp: *c_void,
-                                   dir: *libc::c_char,
-                                   in_fd: c_int,
-                                   out_fd: c_int,
-                                   err_fd: c_int) -> run::RunProgramResult;
-        unsafe fn rust_process_wait(pid: c_int) -> c_int;
-    }
-}
-
-pub struct RunProgramResult {
-    // the process id of the program, or -1 if in case of errors
-    pid: pid_t,
-    // a handle to the process - on unix this will always be NULL, but on windows it will be a
-    // HANDLE to the process, which will prevent the pid being re-used until the handle is closed.
-    handle: *(),
-}
-
 /// A value representing a child process
 pub struct Program {
     priv pid: pid_t,
@@ -191,21 +166,262 @@ pub fn spawn_process(prog: &str, args: &[~str],
     return res.pid;
 }
 
+struct RunProgramResult {
+    // the process id of the program (this should never be negative)
+    pid: pid_t,
+    // a handle to the process - on unix this will always be NULL, but on windows it will be a
+    // HANDLE to the process, which will prevent the pid being re-used until the handle is closed.
+    handle: *(),
+}
+
+#[cfg(windows)]
 fn spawn_process_internal(prog: &str, args: &[~str],
                           env: &Option<~[(~str,~str)]>,
                           dir: &Option<~str>,
                           in_fd: c_int, out_fd: c_int, err_fd: c_int) -> RunProgramResult {
+
+    use libc::types::os::arch::extra::{DWORD, HANDLE, STARTUPINFO};
+    use libc::consts::os::extra::{
+        TRUE, FALSE,
+        STARTF_USESTDHANDLES,
+        INVALID_HANDLE_VALUE,
+        DUPLICATE_SAME_ACCESS
+    };
+    use libc::funcs::extra::kernel32::{
+        GetCurrentProcess,
+        DuplicateHandle,
+        CloseHandle,
+        CreateProcessA
+    };
+    use libc::funcs::extra::msvcrt::get_osfhandle;
+
     unsafe {
-        do with_argv(prog, args) |argv| {
-            do with_envp(env) |envp| {
-                do with_dirp(dir) |dirp| {
-                    rustrt::rust_run_program(argv, envp, dirp, in_fd, out_fd, err_fd)
+
+        let mut si = zeroed_startupinfo();
+        si.cb = sys::size_of::<STARTUPINFO>() as DWORD;
+        si.dwFlags = STARTF_USESTDHANDLES;
+
+        let cur_proc = GetCurrentProcess();
+
+        let orig_std_in = get_osfhandle(if in_fd > 0 { in_fd } else { 0 }) as HANDLE;
+        if orig_std_in == INVALID_HANDLE_VALUE as HANDLE {
+            fail!(fmt!("failure in get_osfhandle: %s", os::last_os_error()));
+        }
+        if DuplicateHandle(cur_proc, orig_std_in, cur_proc, &mut si.hStdInput,
+                           0, TRUE, DUPLICATE_SAME_ACCESS) == FALSE {
+            fail!(fmt!("failure in DuplicateHandle: %s", os::last_os_error()));
+        }
+
+        let orig_std_out = get_osfhandle(if out_fd > 0 { out_fd } else { 1 }) as HANDLE;
+        if orig_std_out == INVALID_HANDLE_VALUE as HANDLE {
+            fail!(fmt!("failure in get_osfhandle: %s", os::last_os_error()));
+        }
+        if DuplicateHandle(cur_proc, orig_std_out, cur_proc, &mut si.hStdOutput,
+                           0, TRUE, DUPLICATE_SAME_ACCESS) == FALSE {
+            fail!(fmt!("failure in DuplicateHandle: %s", os::last_os_error()));
+        }
+
+        let orig_std_err = get_osfhandle(if err_fd > 0 { err_fd } else { 2 }) as HANDLE;
+        if orig_std_err as HANDLE == INVALID_HANDLE_VALUE as HANDLE {
+            fail!(fmt!("failure in get_osfhandle: %s", os::last_os_error()));
+        }
+        if DuplicateHandle(cur_proc, orig_std_err, cur_proc, &mut si.hStdError,
+                           0, TRUE, DUPLICATE_SAME_ACCESS) == FALSE {
+            fail!(fmt!("failure in DuplicateHandle: %s", os::last_os_error()));
+        }
+
+        let cmd = make_command_line(prog, args);
+        let mut pi = zeroed_process_information();
+        let mut create_err = None;
+
+        do with_envp(env) |envp| {
+            do with_dirp(dir) |dirp| {
+                do str::as_c_str(cmd) |cmdp| {
+                    let created = CreateProcessA(ptr::null(), cast::transmute(cmdp),
+                                                 ptr::mut_null(), ptr::mut_null(), TRUE,
+                                                 0, envp, dirp, &mut si, &mut pi);
+                    if created == FALSE {
+                        create_err = Some(os::last_os_error());
+                    }
                 }
             }
         }
+
+        CloseHandle(si.hStdInput);
+        CloseHandle(si.hStdOutput);
+        CloseHandle(si.hStdError);
+
+        for create_err.each |msg| {
+            fail!(fmt!("failure in CreateProcess: %s", *msg));
+        }
+
+        // We close the thread handle because we don't care about keeping the thread id valid,
+        // and we aren't keeping the thread handle around to be able to close it later. We don't
+        // close the process handle however because we want the process id to stay valid at least
+        // until the calling code closes the process handle.
+        CloseHandle(pi.hThread);
+
+        RunProgramResult {
+            pid: pi.dwProcessId as pid_t,
+            handle: pi.hProcess as *()
+        }
+    }
+}
+
+#[cfg(windows)]
+fn zeroed_startupinfo() -> libc::types::os::arch::extra::STARTUPINFO {
+    libc::types::os::arch::extra::STARTUPINFO {
+        cb: 0,
+        lpReserved: ptr::mut_null(),
+        lpDesktop: ptr::mut_null(),
+        lpTitle: ptr::mut_null(),
+        dwX: 0,
+        dwY: 0,
+        dwXSize: 0,
+        dwYSize: 0,
+        dwXCountChars: 0,
+        dwYCountCharts: 0,
+        dwFillAttribute: 0,
+        dwFlags: 0,
+        wShowWindow: 0,
+        cbReserved2: 0,
+        lpReserved2: ptr::mut_null(),
+        hStdInput: ptr::mut_null(),
+        hStdOutput: ptr::mut_null(),
+        hStdError: ptr::mut_null()
     }
 }
 
+#[cfg(windows)]
+fn zeroed_process_information() -> libc::types::os::arch::extra::PROCESS_INFORMATION {
+    libc::types::os::arch::extra::PROCESS_INFORMATION {
+        hProcess: ptr::mut_null(),
+        hThread: ptr::mut_null(),
+        dwProcessId: 0,
+        dwThreadId: 0
+    }
+}
+
+// FIXME: this is only pub so it can be tested (see issue #4536)
+#[cfg(windows)]
+pub fn make_command_line(prog: &str, args: &[~str]) -> ~str {
+
+    let mut cmd = ~"";
+    append_arg(&mut cmd, prog);
+    for args.each |arg| {
+        cmd.push_char(' ');
+        append_arg(&mut cmd, *arg);
+    }
+    return cmd;
+
+    fn append_arg(cmd: &mut ~str, arg: &str) {
+        let quote = arg.any(|c| c == ' ' || c == '\t');
+        if quote {
+            cmd.push_char('"');
+        }
+        for uint::range(0, arg.len()) |i| {
+            append_char_at(cmd, arg, i);
+        }
+        if quote {
+            cmd.push_char('"');
+        }
+    }
+
+    fn append_char_at(cmd: &mut ~str, arg: &str, i: uint) {
+        match arg[i] as char {
+            '"' => {
+                // Escape quotes.
+                cmd.push_str("\\\"");
+            }
+            '\\' => {
+                if backslash_run_ends_in_quote(arg, i) {
+                    // Double all backslashes that are in runs before quotes.
+                    cmd.push_str("\\\\");
+                } else {
+                    // Pass other backslashes through unescaped.
+                    cmd.push_char('\\');
+                }
+            }
+            c => {
+                cmd.push_char(c);
+            }
+        }
+    }
+
+    fn backslash_run_ends_in_quote(s: &str, mut i: uint) -> bool {
+        while i < s.len() && s[i] as char == '\\' {
+            i += 1;
+        }
+        return i < s.len() && s[i] as char == '"';
+    }
+}
+
+#[cfg(unix)]
+fn spawn_process_internal(prog: &str, args: &[~str],
+                          env: &Option<~[(~str,~str)]>,
+                          dir: &Option<~str>,
+                          in_fd: c_int, out_fd: c_int, err_fd: c_int) -> RunProgramResult {
+
+    use libc::funcs::posix88::unistd::{fork, dup2, close, chdir, execvp};
+    use libc::funcs::bsd44::getdtablesize;
+
+    mod rustrt {
+        use libc::c_void;
+
+        #[abi = "cdecl"]
+        pub extern {
+            unsafe fn rust_unset_sigprocmask();
+            unsafe fn rust_set_environ(envp: *c_void);
+        }
+    }
+
+    unsafe {
+
+        let pid = fork();
+        if pid < 0 {
+            fail!(fmt!("failure in fork: %s", os::last_os_error()));
+        } else if pid > 0 {
+            return RunProgramResult {pid: pid, handle: ptr::null()};
+        }
+
+        rustrt::rust_unset_sigprocmask();
+
+        if in_fd > 0 && dup2(in_fd, 0) == -1 {
+            fail!(fmt!("failure in dup2(in_fd, 0): %s", os::last_os_error()));
+        }
+        if out_fd > 0 && dup2(out_fd, 1) == -1 {
+            fail!(fmt!("failure in dup2(out_fd, 1): %s", os::last_os_error()));
+        }
+        if err_fd > 0 && dup2(err_fd, 2) == -1 {
+            fail!(fmt!("failure in dup3(err_fd, 2): %s", os::last_os_error()));
+        }
+        // close all other fds
+        for int::range_rev(getdtablesize() as int - 1, 2) |fd| {
+            close(fd as c_int);
+        }
+
+        for dir.each |dir| {
+            do str::as_c_str(*dir) |dirp| {
+                if chdir(dirp) == -1 {
+                    fail!(fmt!("failure in chdir: %s", os::last_os_error()));
+                }
+            }
+        }
+
+        do with_envp(env) |envp| {
+            if !envp.is_null() {
+                rustrt::rust_set_environ(envp);
+            }
+            do with_argv(prog, args) |argv| {
+                execvp(*argv, argv);
+                // execvp only returns if an error occurred
+                fail!(fmt!("failure in execvp: %s", os::last_os_error()));
+            }
+        }
+    }
+}
+
+#[cfg(unix)]
 fn with_argv<T>(prog: &str, args: &[~str],
                 cb: &fn(**libc::c_char) -> T) -> T {
     let mut argptrs = str::as_c_str(prog, |b| ~[b]);
@@ -246,7 +462,7 @@ fn with_envp<T>(env: &Option<~[(~str,~str)]>,
 
 #[cfg(windows)]
 fn with_envp<T>(env: &Option<~[(~str,~str)]>,
-                cb: &fn(*c_void) -> T) -> T {
+                cb: &fn(*mut c_void) -> T) -> T {
     // On win32 we pass an "environment block" which is not a char**, but
     // rather a concatenation of null-terminated k=v\0 sequences, with a final
     // \0 to terminate.
@@ -264,11 +480,12 @@ fn with_envp<T>(env: &Option<~[(~str,~str)]>,
             blk += ~[0_u8];
             vec::as_imm_buf(blk, |p, _len| cb(::cast::transmute(p)))
           }
-          _ => cb(ptr::null())
+          _ => cb(ptr::mut_null())
         }
     }
 }
 
+#[cfg(windows)]
 fn with_dirp<T>(d: &Option<~str>,
                 cb: &fn(*libc::c_char) -> T) -> T {
     match *d {
@@ -312,8 +529,6 @@ priv fn free_handle(_handle: *()) {
 pub fn run_program(prog: &str, args: &[~str]) -> int {
     let res = spawn_process_internal(prog, args, &None, &None,
                                      0i32, 0i32, 0i32);
-    if res.pid == -1 as pid_t { fail!(); }
-
     let code = waitpid(res.pid);
     free_handle(res.handle);
     return code;
@@ -345,7 +560,6 @@ pub fn start_program(prog: &str, args: &[~str]) -> Program {
                                pipe_err.out);
 
     unsafe {
-        if res.pid == -1 as pid_t { fail!(); }
         libc::close(pipe_input.in);
         libc::close(pipe_output.out);
         libc::close(pipe_err.out);
@@ -398,13 +612,6 @@ pub fn program_output(prog: &str, args: &[~str]) -> ProgramOutput {
     os::close(pipe_in.in);
     os::close(pipe_out.out);
     os::close(pipe_err.out);
-    if res.pid == -1i32 {
-        os::close(pipe_in.out);
-        os::close(pipe_out.in);
-        os::close(pipe_err.in);
-        fail!();
-    }
-
     os::close(pipe_in.out);
 
     // Spawn two entire schedulers to read both stdout and sterr
@@ -485,11 +692,46 @@ pub fn waitpid(pid: pid_t) -> int {
 
     #[cfg(windows)]
     fn waitpid_os(pid: pid_t) -> int {
-        let status = unsafe { rustrt::rust_process_wait(pid) };
-        if status < 0 {
-            fail!(fmt!("failure in rust_process_wait: %s", os::last_os_error()));
+
+        use libc::types::os::arch::extra::DWORD;
+        use libc::consts::os::extra::{
+            SYNCHRONIZE,
+            PROCESS_QUERY_INFORMATION,
+            FALSE,
+            STILL_ACTIVE,
+            INFINITE,
+            WAIT_FAILED
+        };
+        use libc::funcs::extra::kernel32::{
+            OpenProcess,
+            GetExitCodeProcess,
+            CloseHandle,
+            WaitForSingleObject
+        };
+
+        unsafe {
+
+            let proc = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION, FALSE, pid as DWORD);
+            if proc.is_null() {
+                fail!(fmt!("failure in OpenProcess: %s", os::last_os_error()));
+            }
+
+            loop {
+                let mut status = 0;
+                if GetExitCodeProcess(proc, &mut status) == FALSE {
+                    CloseHandle(proc);
+                    fail!(fmt!("failure in GetExitCodeProcess: %s", os::last_os_error()));
+                }
+                if status != STILL_ACTIVE {
+                    CloseHandle(proc);
+                    return status as int;
+                }
+                if WaitForSingleObject(proc, INFINITE) == WAIT_FAILED {
+                    CloseHandle(proc);
+                    fail!(fmt!("failure in WaitForSingleObject: %s", os::last_os_error()));
+                }
+            }
         }
-        return status as int;
     }
 
     #[cfg(unix)]
@@ -539,10 +781,30 @@ mod tests {
     use libc;
     use option::None;
     use os;
-    use path::Path;
     use run::{readclose, writeclose};
     use run;
 
+    #[test]
+    #[cfg(windows)]
+    fn test_make_command_line() {
+        assert_eq!(
+            run::make_command_line("prog", [~"aaa", ~"bbb", ~"ccc"]),
+            ~"prog aaa bbb ccc"
+        );
+        assert_eq!(
+            run::make_command_line("C:\\Program Files\\blah\\blah.exe", [~"aaa"]),
+            ~"\"C:\\Program Files\\blah\\blah.exe\" aaa"
+        );
+        assert_eq!(
+            run::make_command_line("C:\\Program Files\\test", [~"aa\"bb"]),
+            ~"\"C:\\Program Files\\test\" aa\\\"bb"
+        );
+        assert_eq!(
+            run::make_command_line("echo", [~"a b c"]),
+            ~"echo \"a b c\""
+        );
+    }
+
     // Regression test for memory leaks
     #[test]
     fn test_leaks() {
@@ -607,43 +869,60 @@ mod tests {
         p.destroy(); // ...and nor should this (and nor should the destructor)
     }
 
-    #[cfg(unix)] // there is no way to sleep on windows from inside libcore...
     fn test_destroy_actually_kills(force: bool) {
-        let path = Path(fmt!("test/core-run-test-destroy-actually-kills-%?.tmp", force));
 
-        os::remove_file(&path);
+        #[cfg(unix)]
+        static BLOCK_COMMAND: &'static str = "cat";
 
-        let cmd = fmt!("sleep 5 && echo MurderDeathKill > %s", path.to_str());
-        let mut p = run::start_program("sh", [~"-c", cmd]);
+        #[cfg(windows)]
+        static BLOCK_COMMAND: &'static str = "cmd";
 
-        p.destroy(); // destroy the program before it has a chance to echo its message
+        #[cfg(unix)]
+        fn process_exists(pid: libc::pid_t) -> bool {
+            run::program_output("ps", [~"-p", pid.to_str()]).out.contains(pid.to_str())
+        }
 
-        unsafe {
-            // wait to ensure the program is really destroyed and not just waiting itself
-            libc::sleep(10);
+        #[cfg(windows)]
+        fn process_exists(pid: libc::pid_t) -> bool {
+
+            use libc::types::os::arch::extra::DWORD;
+            use libc::funcs::extra::kernel32::{CloseHandle, GetExitCodeProcess, OpenProcess};
+            use libc::consts::os::extra::{FALSE, PROCESS_QUERY_INFORMATION, STILL_ACTIVE };
+
+            unsafe {
+                let proc = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid as DWORD);
+                if proc.is_null() {
+                    return false;
+                }
+                // proc will be non-null if the process is alive, or if it died recently
+                let mut status = 0;
+                GetExitCodeProcess(proc, &mut status);
+                CloseHandle(proc);
+                return status == STILL_ACTIVE;
+            }
         }
 
-        // the program should not have had chance to echo its message
-        assert!(!path.exists());
+        // this program will stay alive indefinitely trying to read from stdin
+        let mut p = run::start_program(BLOCK_COMMAND, []);
+
+        assert!(process_exists(p.get_id()));
+
+        if force {
+            p.force_destroy();
+        } else {
+            p.destroy();
+        }
+
+        assert!(!process_exists(p.get_id()));
     }
 
     #[test]
-    #[cfg(unix)]
     fn test_unforced_destroy_actually_kills() {
         test_destroy_actually_kills(false);
     }
 
     #[test]
-    #[cfg(unix)]
     fn test_forced_destroy_actually_kills() {
         test_destroy_actually_kills(true);
     }
 }
-
-// Local Variables:
-// mode: rust
-// fill-column: 78;
-// indent-tabs-mode: nil
-// c-basic-offset: 4
-// buffer-file-coding-system: utf-8-unix
-// End: