diff options
Diffstat (limited to 'src/libcore/run.rs')
| -rw-r--r-- | src/libcore/run.rs | 413 |
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: |
