diff options
Diffstat (limited to 'src/libstd/sys')
| -rw-r--r-- | src/libstd/sys/common/helper_thread.rs | 11 | ||||
| -rw-r--r-- | src/libstd/sys/unix/fs.rs | 411 | ||||
| -rw-r--r-- | src/libstd/sys/unix/mod.rs | 2 | ||||
| -rw-r--r-- | src/libstd/sys/unix/process.rs | 587 | ||||
| -rw-r--r-- | src/libstd/sys/windows/fs.rs | 460 | ||||
| -rw-r--r-- | src/libstd/sys/windows/mod.rs | 1 | ||||
| -rw-r--r-- | src/libstd/sys/windows/process.rs | 511 |
7 files changed, 1971 insertions, 12 deletions
diff --git a/src/libstd/sys/common/helper_thread.rs b/src/libstd/sys/common/helper_thread.rs index 8c8ec4466a7..87907fde277 100644 --- a/src/libstd/sys/common/helper_thread.rs +++ b/src/libstd/sys/common/helper_thread.rs @@ -20,8 +20,6 @@ //! can be created in the future and there must be no active timers at that //! time. -#![macro_escape] - use mem; use rt::bookkeeping; use rt::mutex::StaticNativeMutex; @@ -57,15 +55,6 @@ pub struct Helper<M> { pub initialized: UnsafeCell<bool>, } -macro_rules! helper_init( (static $name:ident: Helper<$m:ty>) => ( - static $name: Helper<$m> = Helper { - lock: ::std::rt::mutex::NATIVE_MUTEX_INIT, - chan: ::std::cell::UnsafeCell { value: 0 as *mut Sender<$m> }, - signal: ::std::cell::UnsafeCell { value: 0 }, - initialized: ::std::cell::UnsafeCell { value: false }, - }; -) ) - impl<M: Send> Helper<M> { /// Lazily boots a helper thread, becoming a no-op if the helper has already /// been spawned. diff --git a/src/libstd/sys/unix/fs.rs b/src/libstd/sys/unix/fs.rs new file mode 100644 index 00000000000..3dcd99859e8 --- /dev/null +++ b/src/libstd/sys/unix/fs.rs @@ -0,0 +1,411 @@ +// Copyright 2013-2014 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. + +//! Blocking posix-based file I/O + +use libc::{mod, c_int, c_void}; +use c_str::CString; +use mem; +use io; + +use prelude::*; + +use io::{FilePermission, Write, UnstableFileStat, Open, FileAccess, FileMode}; +use io::{IoResult, FileStat, SeekStyle, Reader}; +use io::{Read, Truncate, SeekCur, SeekSet, ReadWrite, SeekEnd, Append}; +use result::{Ok, Err}; +use sys::retry; +use sys_common::{keep_going, eof, mkerr_libc}; + +pub use path::PosixPath as Path; + +pub type fd_t = libc::c_int; + +pub struct FileDesc { + /// The underlying C file descriptor. + fd: fd_t, + + /// Whether to close the file descriptor on drop. + close_on_drop: bool, +} + +impl FileDesc { + pub fn new(fd: fd_t, close_on_drop: bool) -> FileDesc { + FileDesc { fd: fd, close_on_drop: close_on_drop } + } + + pub fn read(&self, buf: &mut [u8]) -> IoResult<uint> { + let ret = retry(|| unsafe { + libc::read(self.fd(), + buf.as_mut_ptr() as *mut libc::c_void, + buf.len() as libc::size_t) + }); + if ret == 0 { + Err(eof()) + } else if ret < 0 { + Err(super::last_error()) + } else { + Ok(ret as uint) + } + } + pub fn write(&self, buf: &[u8]) -> IoResult<()> { + let ret = keep_going(buf, |buf, len| { + unsafe { + libc::write(self.fd(), buf as *const libc::c_void, + len as libc::size_t) as i64 + } + }); + if ret < 0 { + Err(super::last_error()) + } else { + Ok(()) + } + } + + pub fn fd(&self) -> fd_t { self.fd } + + pub fn seek(&self, pos: i64, whence: SeekStyle) -> IoResult<u64> { + let whence = match whence { + SeekSet => libc::SEEK_SET, + SeekEnd => libc::SEEK_END, + SeekCur => libc::SEEK_CUR, + }; + let n = unsafe { libc::lseek(self.fd(), pos as libc::off_t, whence) }; + if n < 0 { + Err(super::last_error()) + } else { + Ok(n as u64) + } + } + + pub fn tell(&self) -> IoResult<u64> { + let n = unsafe { libc::lseek(self.fd(), 0, libc::SEEK_CUR) }; + if n < 0 { + Err(super::last_error()) + } else { + Ok(n as u64) + } + } + + pub fn fsync(&self) -> IoResult<()> { + mkerr_libc(retry(|| unsafe { libc::fsync(self.fd()) })) + } + + pub fn datasync(&self) -> IoResult<()> { + return mkerr_libc(os_datasync(self.fd())); + + #[cfg(any(target_os = "macos", target_os = "ios"))] + fn os_datasync(fd: c_int) -> c_int { + unsafe { libc::fcntl(fd, libc::F_FULLFSYNC) } + } + #[cfg(target_os = "linux")] + fn os_datasync(fd: c_int) -> c_int { + retry(|| unsafe { libc::fdatasync(fd) }) + } + #[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "linux")))] + fn os_datasync(fd: c_int) -> c_int { + retry(|| unsafe { libc::fsync(fd) }) + } + } + + pub fn truncate(&self, offset: i64) -> IoResult<()> { + mkerr_libc(retry(|| unsafe { + libc::ftruncate(self.fd(), offset as libc::off_t) + })) + } + + pub fn fstat(&self) -> IoResult<FileStat> { + let mut stat: libc::stat = unsafe { mem::zeroed() }; + match unsafe { libc::fstat(self.fd(), &mut stat) } { + 0 => Ok(mkstat(&stat)), + _ => Err(super::last_error()), + } + } + + /// Extract the actual filedescriptor without closing it. + pub fn unwrap(self) -> fd_t { + let fd = self.fd; + unsafe { mem::forget(self) }; + fd + } +} + +/* + +impl RtioTTY for FileDesc { + fn read(&mut self, buf: &mut [u8]) -> IoResult<uint> { + self.inner_read(buf) + } + fn write(&mut self, buf: &[u8]) -> IoResult<()> { + self.inner_write(buf) + } + fn set_raw(&mut self, _raw: bool) -> IoResult<()> { + Err(super::unimpl()) + } + fn get_winsize(&mut self) -> IoResult<(int, int)> { + Err(super::unimpl()) + } + fn isatty(&self) -> bool { false } +} +*/ + +impl Drop for FileDesc { + fn drop(&mut self) { + // closing stdio file handles makes no sense, so never do it. Also, note + // that errors are ignored when closing a file descriptor. The reason + // for this is that if an error occurs we don't actually know if the + // file descriptor was closed or not, and if we retried (for something + // like EINTR), we might close another valid file descriptor (opened + // after we closed ours. + if self.close_on_drop && self.fd > libc::STDERR_FILENO { + let n = unsafe { libc::close(self.fd) }; + if n != 0 { + println!("error {} when closing file descriptor {}", n, self.fd); + } + } + } +} + +pub fn open(path: &Path, fm: FileMode, fa: FileAccess) -> IoResult<FileDesc> { + let flags = match fm { + Open => 0, + Append => libc::O_APPEND, + Truncate => libc::O_TRUNC, + }; + // Opening with a write permission must silently create the file. + let (flags, mode) = match fa { + Read => (flags | libc::O_RDONLY, 0), + Write => (flags | libc::O_WRONLY | libc::O_CREAT, + libc::S_IRUSR | libc::S_IWUSR), + ReadWrite => (flags | libc::O_RDWR | libc::O_CREAT, + libc::S_IRUSR | libc::S_IWUSR), + }; + + let path = path.to_c_str(); + match retry(|| unsafe { libc::open(path.as_ptr(), flags, mode) }) { + -1 => Err(super::last_error()), + fd => Ok(FileDesc::new(fd, true)), + } +} + +pub fn mkdir(p: &Path, mode: uint) -> IoResult<()> { + let p = p.to_c_str(); + mkerr_libc(unsafe { libc::mkdir(p.as_ptr(), mode as libc::mode_t) }) +} + +pub fn readdir(p: &Path) -> IoResult<Vec<Path>> { + use libc::{dirent_t}; + use libc::{opendir, readdir_r, closedir}; + + fn prune(root: &CString, dirs: Vec<Path>) -> Vec<Path> { + let root = unsafe { CString::new(root.as_ptr(), false) }; + let root = Path::new(root); + + dirs.into_iter().filter(|path| { + path.as_vec() != b"." && path.as_vec() != b".." + }).map(|path| root.join(path)).collect() + } + + extern { + fn rust_dirent_t_size() -> libc::c_int; + fn rust_list_dir_val(ptr: *mut dirent_t) -> *const libc::c_char; + } + + let size = unsafe { rust_dirent_t_size() }; + let mut buf = Vec::<u8>::with_capacity(size as uint); + let ptr = buf.as_mut_slice().as_mut_ptr() as *mut dirent_t; + + let p = p.to_c_str(); + let dir_ptr = unsafe {opendir(p.as_ptr())}; + + if dir_ptr as uint != 0 { + let mut paths = vec!(); + let mut entry_ptr = 0 as *mut dirent_t; + while unsafe { readdir_r(dir_ptr, ptr, &mut entry_ptr) == 0 } { + if entry_ptr.is_null() { break } + let cstr = unsafe { + CString::new(rust_list_dir_val(entry_ptr), false) + }; + paths.push(Path::new(cstr)); + } + assert_eq!(unsafe { closedir(dir_ptr) }, 0); + Ok(prune(&p, paths)) + } else { + Err(super::last_error()) + } +} + +pub fn unlink(p: &Path) -> IoResult<()> { + let p = p.to_c_str(); + mkerr_libc(unsafe { libc::unlink(p.as_ptr()) }) +} + +pub fn rename(old: &Path, new: &Path) -> IoResult<()> { + let old = old.to_c_str(); + let new = new.to_c_str(); + mkerr_libc(unsafe { + libc::rename(old.as_ptr(), new.as_ptr()) + }) +} + +pub fn chmod(p: &Path, mode: uint) -> IoResult<()> { + let p = p.to_c_str(); + mkerr_libc(retry(|| unsafe { + libc::chmod(p.as_ptr(), mode as libc::mode_t) + })) +} + +pub fn rmdir(p: &Path) -> IoResult<()> { + let p = p.to_c_str(); + mkerr_libc(unsafe { libc::rmdir(p.as_ptr()) }) +} + +pub fn chown(p: &Path, uid: int, gid: int) -> IoResult<()> { + let p = p.to_c_str(); + mkerr_libc(retry(|| unsafe { + libc::chown(p.as_ptr(), uid as libc::uid_t, gid as libc::gid_t) + })) +} + +pub fn readlink(p: &Path) -> IoResult<Path> { + let c_path = p.to_c_str(); + let p = c_path.as_ptr(); + let mut len = unsafe { libc::pathconf(p as *mut _, libc::_PC_NAME_MAX) }; + if len == -1 { + len = 1024; // FIXME: read PATH_MAX from C ffi? + } + let mut buf: Vec<u8> = Vec::with_capacity(len as uint); + match unsafe { + libc::readlink(p, buf.as_ptr() as *mut libc::c_char, + len as libc::size_t) as libc::c_int + } { + -1 => Err(super::last_error()), + n => { + assert!(n > 0); + unsafe { buf.set_len(n as uint); } + Ok(Path::new(buf)) + } + } +} + +pub fn symlink(src: &Path, dst: &Path) -> IoResult<()> { + let src = src.to_c_str(); + let dst = dst.to_c_str(); + mkerr_libc(unsafe { libc::symlink(src.as_ptr(), dst.as_ptr()) }) +} + +pub fn link(src: &Path, dst: &Path) -> IoResult<()> { + let src = src.to_c_str(); + let dst = dst.to_c_str(); + mkerr_libc(unsafe { libc::link(src.as_ptr(), dst.as_ptr()) }) +} + +fn mkstat(stat: &libc::stat) -> FileStat { + // FileStat times are in milliseconds + fn mktime(secs: u64, nsecs: u64) -> u64 { secs * 1000 + nsecs / 1000000 } + + #[cfg(not(any(target_os = "linux", target_os = "android")))] + fn flags(stat: &libc::stat) -> u64 { stat.st_flags as u64 } + #[cfg(any(target_os = "linux", target_os = "android"))] + fn flags(_stat: &libc::stat) -> u64 { 0 } + + #[cfg(not(any(target_os = "linux", target_os = "android")))] + fn gen(stat: &libc::stat) -> u64 { stat.st_gen as u64 } + #[cfg(any(target_os = "linux", target_os = "android"))] + fn gen(_stat: &libc::stat) -> u64 { 0 } + + FileStat { + size: stat.st_size as u64, + kind: match (stat.st_mode as libc::mode_t) & libc::S_IFMT { + libc::S_IFREG => io::TypeFile, + libc::S_IFDIR => io::TypeDirectory, + libc::S_IFIFO => io::TypeNamedPipe, + libc::S_IFBLK => io::TypeBlockSpecial, + libc::S_IFLNK => io::TypeSymlink, + _ => io::TypeUnknown, + }, + perm: FilePermission::from_bits_truncate(stat.st_mode as u32), + created: mktime(stat.st_ctime as u64, stat.st_ctime_nsec as u64), + modified: mktime(stat.st_mtime as u64, stat.st_mtime_nsec as u64), + accessed: mktime(stat.st_atime as u64, stat.st_atime_nsec as u64), + unstable: UnstableFileStat { + device: stat.st_dev as u64, + inode: stat.st_ino as u64, + rdev: stat.st_rdev as u64, + nlink: stat.st_nlink as u64, + uid: stat.st_uid as u64, + gid: stat.st_gid as u64, + blksize: stat.st_blksize as u64, + blocks: stat.st_blocks as u64, + flags: flags(stat), + gen: gen(stat), + }, + } +} + +pub fn stat(p: &Path) -> IoResult<FileStat> { + let p = p.to_c_str(); + let mut stat: libc::stat = unsafe { mem::zeroed() }; + match unsafe { libc::stat(p.as_ptr(), &mut stat) } { + 0 => Ok(mkstat(&stat)), + _ => Err(super::last_error()), + } +} + +pub fn lstat(p: &Path) -> IoResult<FileStat> { + let p = p.to_c_str(); + let mut stat: libc::stat = unsafe { mem::zeroed() }; + match unsafe { libc::lstat(p.as_ptr(), &mut stat) } { + 0 => Ok(mkstat(&stat)), + _ => Err(super::last_error()), + } +} + +pub fn utime(p: &Path, atime: u64, mtime: u64) -> IoResult<()> { + let p = p.to_c_str(); + let buf = libc::utimbuf { + actime: (atime / 1000) as libc::time_t, + modtime: (mtime / 1000) as libc::time_t, + }; + mkerr_libc(unsafe { libc::utime(p.as_ptr(), &buf) }) +} + +#[cfg(test)] +mod tests { + use super::FileDesc; + use libc; + use os; + use prelude::*; + + #[cfg_attr(target_os = "freebsd", ignore)] // hmm, maybe pipes have a tiny buffer + #[test] + fn test_file_desc() { + // Run this test with some pipes so we don't have to mess around with + // opening or closing files. + let os::Pipe { reader, writer } = unsafe { os::pipe().unwrap() }; + let mut reader = FileDesc::new(reader, true); + let mut writer = FileDesc::new(writer, true); + + writer.write(b"test").ok().unwrap(); + let mut buf = [0u8, ..4]; + match reader.read(buf) { + Ok(4) => { + assert_eq!(buf[0], 't' as u8); + assert_eq!(buf[1], 'e' as u8); + assert_eq!(buf[2], 's' as u8); + assert_eq!(buf[3], 't' as u8); + } + r => panic!("invalid read: {}", r), + } + + assert!(writer.read(buf).is_err()); + assert!(reader.write(buf).is_err()); + } +} diff --git a/src/libstd/sys/unix/mod.rs b/src/libstd/sys/unix/mod.rs index 6295864e0e1..b404dc7fdbd 100644 --- a/src/libstd/sys/unix/mod.rs +++ b/src/libstd/sys/unix/mod.rs @@ -17,7 +17,6 @@ use prelude::*; use io::{mod, IoResult, IoError}; use sys_common::mkerr_libc; - macro_rules! helper_init( (static $name:ident: Helper<$m:ty>) => ( static $name: Helper<$m> = Helper { lock: ::rt::mutex::NATIVE_MUTEX_INIT, @@ -34,6 +33,7 @@ pub mod tcp; pub mod udp; pub mod pipe; pub mod helper_signal; +pub mod process; pub mod addrinfo { pub use sys_common::net::get_host_addresses; diff --git a/src/libstd/sys/unix/process.rs b/src/libstd/sys/unix/process.rs new file mode 100644 index 00000000000..0965d98d9b0 --- /dev/null +++ b/src/libstd/sys/unix/process.rs @@ -0,0 +1,587 @@ +// Copyright 2014 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. + +use libc::{mod, pid_t, c_void, c_int}; +use c_str::CString; +use io::{mod, IoResult, IoError}; +use mem; +use os; +use ptr; +use prelude::*; +use io::process::{ProcessExit, ExitStatus, ExitSignal}; +use collections; +use path::BytesContainer; +use hash::Hash; + +use sys::{mod, retry, c, wouldblock, set_nonblocking, ms_to_timeval}; +use sys::fs::FileDesc; +use sys_common::helper_thread::Helper; +use sys_common::{AsFileDesc, mkerr_libc, timeout}; + +pub use sys_common::ProcessConfig; + +helper_init!(static HELPER: Helper<Req>) + +/// The unique id of the process (this should never be negative). +pub struct Process { + pub pid: pid_t +} + +enum Req { + NewChild(libc::pid_t, Sender<ProcessExit>, u64), +} + +impl Process { + pub fn id(&self) -> pid_t { + self.pid + } + + pub unsafe fn kill(&self, signal: int) -> IoResult<()> { + Process::killpid(self.pid, signal) + } + + pub unsafe fn killpid(pid: pid_t, signal: int) -> IoResult<()> { + let r = libc::funcs::posix88::signal::kill(pid, signal as c_int); + mkerr_libc(r) + } + + pub fn spawn<K, V, C, P>(cfg: &C, in_fd: Option<P>, + out_fd: Option<P>, err_fd: Option<P>) + -> IoResult<Process> + where C: ProcessConfig<K, V>, P: AsFileDesc, + K: BytesContainer + Eq + Hash, V: BytesContainer + { + use libc::funcs::posix88::unistd::{fork, dup2, close, chdir, execvp}; + use libc::funcs::bsd44::getdtablesize; + + mod rustrt { + extern { + pub fn rust_unset_sigprocmask(); + } + } + + #[cfg(target_os = "macos")] + unsafe fn set_environ(envp: *const c_void) { + extern { fn _NSGetEnviron() -> *mut *const c_void; } + + *_NSGetEnviron() = envp; + } + #[cfg(not(target_os = "macos"))] + unsafe fn set_environ(envp: *const c_void) { + extern { static mut environ: *const c_void; } + environ = envp; + } + + unsafe fn set_cloexec(fd: c_int) { + let ret = c::ioctl(fd, c::FIOCLEX); + assert_eq!(ret, 0); + } + + let dirp = cfg.cwd().map(|c| c.as_ptr()).unwrap_or(ptr::null()); + + // temporary until unboxed closures land + let cfg = unsafe { + mem::transmute::<&ProcessConfig<K,V>,&'static ProcessConfig<K,V>>(cfg) + }; + + with_envp(cfg.env(), proc(envp) { + with_argv(cfg.program(), cfg.args(), proc(argv) unsafe { + let (input, mut output) = try!(sys::os::pipe()); + + // We may use this in the child, so perform allocations before the + // fork + let devnull = "/dev/null".to_c_str(); + + set_cloexec(output.fd()); + + let pid = fork(); + if pid < 0 { + return Err(super::last_error()) + } else if pid > 0 { + drop(output); + let mut bytes = [0, ..4]; + return match input.read(bytes) { + Ok(4) => { + let errno = (bytes[0] as i32 << 24) | + (bytes[1] as i32 << 16) | + (bytes[2] as i32 << 8) | + (bytes[3] as i32 << 0); + Err(super::decode_error(errno)) + } + Err(..) => Ok(Process { pid: pid }), + Ok(..) => panic!("short read on the cloexec pipe"), + }; + } + + // And at this point we've reached a special time in the life of the + // child. The child must now be considered hamstrung and unable to + // do anything other than syscalls really. Consider the following + // scenario: + // + // 1. Thread A of process 1 grabs the malloc() mutex + // 2. Thread B of process 1 forks(), creating thread C + // 3. Thread C of process 2 then attempts to malloc() + // 4. The memory of process 2 is the same as the memory of + // process 1, so the mutex is locked. + // + // This situation looks a lot like deadlock, right? It turns out + // that this is what pthread_atfork() takes care of, which is + // presumably implemented across platforms. The first thing that + // threads to *before* forking is to do things like grab the malloc + // mutex, and then after the fork they unlock it. + // + // Despite this information, libnative's spawn has been witnessed to + // deadlock on both OSX and FreeBSD. I'm not entirely sure why, but + // all collected backtraces point at malloc/free traffic in the + // child spawned process. + // + // For this reason, the block of code below should contain 0 + // invocations of either malloc of free (or their related friends). + // + // As an example of not having malloc/free traffic, we don't close + // this file descriptor by dropping the FileDesc (which contains an + // allocation). Instead we just close it manually. This will never + // have the drop glue anyway because this code never returns (the + // child will either exec() or invoke libc::exit) + let _ = libc::close(input.fd()); + + fn fail(output: &mut FileDesc) -> ! { + let errno = sys::os::errno(); + let bytes = [ + (errno >> 24) as u8, + (errno >> 16) as u8, + (errno >> 8) as u8, + (errno >> 0) as u8, + ]; + assert!(output.write(bytes).is_ok()); + unsafe { libc::_exit(1) } + } + + rustrt::rust_unset_sigprocmask(); + + // If a stdio file descriptor is set to be ignored (via a -1 file + // descriptor), then we don't actually close it, but rather open + // up /dev/null into that file descriptor. Otherwise, the first file + // descriptor opened up in the child would be numbered as one of the + // stdio file descriptors, which is likely to wreak havoc. + let setup = |src: Option<P>, dst: c_int| { + let src = match src { + None => { + let flags = if dst == libc::STDIN_FILENO { + libc::O_RDONLY + } else { + libc::O_RDWR + }; + libc::open(devnull.as_ptr(), flags, 0) + } + Some(obj) => { + let fd = obj.as_fd().fd(); + // Leak the memory and the file descriptor. We're in the + // child now an all our resources are going to be + // cleaned up very soon + mem::forget(obj); + fd + } + }; + src != -1 && retry(|| dup2(src, dst)) != -1 + }; + + if !setup(in_fd, libc::STDIN_FILENO) { fail(&mut output) } + if !setup(out_fd, libc::STDOUT_FILENO) { fail(&mut output) } + if !setup(err_fd, libc::STDERR_FILENO) { fail(&mut output) } + + // close all other fds + for fd in range(3, getdtablesize()).rev() { + if fd != output.fd() { + let _ = close(fd as c_int); + } + } + + match cfg.gid() { + Some(u) => { + if libc::setgid(u as libc::gid_t) != 0 { + fail(&mut output); + } + } + None => {} + } + match cfg.uid() { + Some(u) => { + // When dropping privileges from root, the `setgroups` call + // will remove any extraneous groups. If we don't call this, + // then even though our uid has dropped, we may still have + // groups that enable us to do super-user things. This will + // fail if we aren't root, so don't bother checking the + // return value, this is just done as an optimistic + // privilege dropping function. + extern { + fn setgroups(ngroups: libc::c_int, + ptr: *const libc::c_void) -> libc::c_int; + } + let _ = setgroups(0, 0 as *const libc::c_void); + + if libc::setuid(u as libc::uid_t) != 0 { + fail(&mut output); + } + } + None => {} + } + if cfg.detach() { + // Don't check the error of setsid because it fails if we're the + // process leader already. We just forked so it shouldn't return + // error, but ignore it anyway. + let _ = libc::setsid(); + } + if !dirp.is_null() && chdir(dirp) == -1 { + fail(&mut output); + } + if !envp.is_null() { + set_environ(envp); + } + let _ = execvp(*argv, argv as *mut _); + fail(&mut output); + }) + }) + } + + pub fn wait(&self, deadline: u64) -> IoResult<ProcessExit> { + use std::cmp; + use std::comm; + + static mut WRITE_FD: libc::c_int = 0; + + let mut status = 0 as c_int; + if deadline == 0 { + return match retry(|| unsafe { c::waitpid(self.pid, &mut status, 0) }) { + -1 => panic!("unknown waitpid error: {}", super::last_error()), + _ => Ok(translate_status(status)), + } + } + + // On unix, wait() and its friends have no timeout parameters, so there is + // no way to time out a thread in wait(). From some googling and some + // thinking, it appears that there are a few ways to handle timeouts in + // wait(), but the only real reasonable one for a multi-threaded program is + // to listen for SIGCHLD. + // + // With this in mind, the waiting mechanism with a timeout barely uses + // waitpid() at all. There are a few times that waitpid() is invoked with + // WNOHANG, but otherwise all the necessary blocking is done by waiting for + // a SIGCHLD to arrive (and that blocking has a timeout). Note, however, + // that waitpid() is still used to actually reap the child. + // + // Signal handling is super tricky in general, and this is no exception. Due + // to the async nature of SIGCHLD, we use the self-pipe trick to transmit + // data out of the signal handler to the rest of the application. The first + // idea would be to have each thread waiting with a timeout to read this + // output file descriptor, but a write() is akin to a signal(), not a + // broadcast(), so it would only wake up one thread, and possibly the wrong + // thread. Hence a helper thread is used. + // + // The helper thread here is responsible for farming requests for a + // waitpid() with a timeout, and then processing all of the wait requests. + // By guaranteeing that only this helper thread is reading half of the + // self-pipe, we're sure that we'll never lose a SIGCHLD. This helper thread + // is also responsible for select() to wait for incoming messages or + // incoming SIGCHLD messages, along with passing an appropriate timeout to + // select() to wake things up as necessary. + // + // The ordering of the following statements is also very purposeful. First, + // we must be guaranteed that the helper thread is booted and available to + // receive SIGCHLD signals, and then we must also ensure that we do a + // nonblocking waitpid() at least once before we go ask the sigchld helper. + // This prevents the race where the child exits, we boot the helper, and + // then we ask for the child's exit status (never seeing a sigchld). + // + // The actual communication between the helper thread and this thread is + // quite simple, just a channel moving data around. + + unsafe { HELPER.boot(register_sigchld, waitpid_helper) } + + match self.try_wait() { + Some(ret) => return Ok(ret), + None => {} + } + + let (tx, rx) = channel(); + unsafe { HELPER.send(NewChild(self.pid, tx, deadline)); } + return match rx.recv_opt() { + Ok(e) => Ok(e), + Err(()) => Err(timeout("wait timed out")), + }; + + // Register a new SIGCHLD handler, returning the reading half of the + // self-pipe plus the old handler registered (return value of sigaction). + // + // Be sure to set up the self-pipe first because as soon as we register a + // handler we're going to start receiving signals. + fn register_sigchld() -> (libc::c_int, c::sigaction) { + unsafe { + let mut pipes = [0, ..2]; + assert_eq!(libc::pipe(pipes.as_mut_ptr()), 0); + set_nonblocking(pipes[0], true).ok().unwrap(); + set_nonblocking(pipes[1], true).ok().unwrap(); + WRITE_FD = pipes[1]; + + let mut old: c::sigaction = mem::zeroed(); + let mut new: c::sigaction = mem::zeroed(); + new.sa_handler = sigchld_handler; + new.sa_flags = c::SA_NOCLDSTOP; + assert_eq!(c::sigaction(c::SIGCHLD, &new, &mut old), 0); + (pipes[0], old) + } + } + + // Helper thread for processing SIGCHLD messages + fn waitpid_helper(input: libc::c_int, + messages: Receiver<Req>, + (read_fd, old): (libc::c_int, c::sigaction)) { + set_nonblocking(input, true).ok().unwrap(); + let mut set: c::fd_set = unsafe { mem::zeroed() }; + let mut tv: libc::timeval; + let mut active = Vec::<(libc::pid_t, Sender<ProcessExit>, u64)>::new(); + let max = cmp::max(input, read_fd) + 1; + + 'outer: loop { + // Figure out the timeout of our syscall-to-happen. If we're waiting + // for some processes, then they'll have a timeout, otherwise we + // wait indefinitely for a message to arrive. + // + // FIXME: sure would be nice to not have to scan the entire array + let min = active.iter().map(|a| *a.ref2()).enumerate().min_by(|p| { + p.val1() + }); + let (p, idx) = match min { + Some((idx, deadline)) => { + let now = sys::timer::now(); + let ms = if now < deadline {deadline - now} else {0}; + tv = ms_to_timeval(ms); + (&mut tv as *mut _, idx) + } + None => (ptr::null_mut(), -1), + }; + + // Wait for something to happen + c::fd_set(&mut set, input); + c::fd_set(&mut set, read_fd); + match unsafe { c::select(max, &mut set, ptr::null_mut(), + ptr::null_mut(), p) } { + // interrupted, retry + -1 if os::errno() == libc::EINTR as uint => continue, + + // We read something, break out and process + 1 | 2 => {} + + // Timeout, the pending request is removed + 0 => { + drop(active.remove(idx)); + continue + } + + n => panic!("error in select {} ({})", os::errno(), n), + } + + // Process any pending messages + if drain(input) { + loop { + match messages.try_recv() { + Ok(NewChild(pid, tx, deadline)) => { + active.push((pid, tx, deadline)); + } + Err(comm::Disconnected) => { + assert!(active.len() == 0); + break 'outer; + } + Err(comm::Empty) => break, + } + } + } + + // If a child exited (somehow received SIGCHLD), then poll all + // children to see if any of them exited. + // + // We also attempt to be responsible netizens when dealing with + // SIGCHLD by invoking any previous SIGCHLD handler instead of just + // ignoring any previous SIGCHLD handler. Note that we don't provide + // a 1:1 mapping of our handler invocations to the previous handler + // invocations because we drain the `read_fd` entirely. This is + // probably OK because the kernel is already allowed to coalesce + // simultaneous signals, we're just doing some extra coalescing. + // + // Another point of note is that this likely runs the signal handler + // on a different thread than the one that received the signal. I + // *think* this is ok at this time. + // + // The main reason for doing this is to allow stdtest to run native + // tests as well. Both libgreen and libnative are running around + // with process timeouts, but libgreen should get there first + // (currently libuv doesn't handle old signal handlers). + if drain(read_fd) { + let i: uint = unsafe { mem::transmute(old.sa_handler) }; + if i != 0 { + assert!(old.sa_flags & c::SA_SIGINFO == 0); + (old.sa_handler)(c::SIGCHLD); + } + + // FIXME: sure would be nice to not have to scan the entire + // array... + active.retain(|&(pid, ref tx, _)| { + let pr = Process { pid: pid }; + match pr.try_wait() { + Some(msg) => { tx.send(msg); false } + None => true, + } + }); + } + } + + // Once this helper thread is done, we re-register the old sigchld + // handler and close our intermediate file descriptors. + unsafe { + assert_eq!(c::sigaction(c::SIGCHLD, &old, ptr::null_mut()), 0); + let _ = libc::close(read_fd); + let _ = libc::close(WRITE_FD); + WRITE_FD = -1; + } + } + + // Drain all pending data from the file descriptor, returning if any data + // could be drained. This requires that the file descriptor is in + // nonblocking mode. + fn drain(fd: libc::c_int) -> bool { + let mut ret = false; + loop { + let mut buf = [0u8, ..1]; + match unsafe { + libc::read(fd, buf.as_mut_ptr() as *mut libc::c_void, + buf.len() as libc::size_t) + } { + n if n > 0 => { ret = true; } + 0 => return true, + -1 if wouldblock() => return ret, + n => panic!("bad read {} ({})", os::last_os_error(), n), + } + } + } + + // Signal handler for SIGCHLD signals, must be async-signal-safe! + // + // This function will write to the writing half of the "self pipe" to wake + // up the helper thread if it's waiting. Note that this write must be + // nonblocking because if it blocks and the reader is the thread we + // interrupted, then we'll deadlock. + // + // When writing, if the write returns EWOULDBLOCK then we choose to ignore + // it. At that point we're guaranteed that there's something in the pipe + // which will wake up the other end at some point, so we just allow this + // signal to be coalesced with the pending signals on the pipe. + extern fn sigchld_handler(_signum: libc::c_int) { + let msg = 1i; + match unsafe { + libc::write(WRITE_FD, &msg as *const _ as *const libc::c_void, 1) + } { + 1 => {} + -1 if wouldblock() => {} // see above comments + n => panic!("bad error on write fd: {} {}", n, os::errno()), + } + } + } + + pub fn try_wait(&self) -> Option<ProcessExit> { + let mut status = 0 as c_int; + match retry(|| unsafe { + c::waitpid(self.pid, &mut status, c::WNOHANG) + }) { + n if n == self.pid => Some(translate_status(status)), + 0 => None, + n => panic!("unknown waitpid error `{}`: {}", n, + super::last_error()), + } + } +} + +fn with_argv<T>(prog: &CString, args: &[CString], + cb: proc(*const *const libc::c_char) -> T) -> T { + let mut ptrs: Vec<*const libc::c_char> = Vec::with_capacity(args.len()+1); + + // Convert the CStrings into an array of pointers. Note: the + // lifetime of the various CStrings involved is guaranteed to be + // larger than the lifetime of our invocation of cb, but this is + // technically unsafe as the callback could leak these pointers + // out of our scope. + ptrs.push(prog.as_ptr()); + ptrs.extend(args.iter().map(|tmp| tmp.as_ptr())); + + // Add a terminating null pointer (required by libc). + ptrs.push(ptr::null()); + + cb(ptrs.as_ptr()) +} + +fn with_envp<K, V, T>(env: Option<&collections::HashMap<K, V>>, + cb: proc(*const c_void) -> T) -> T + where K: BytesContainer + Eq + Hash, V: BytesContainer +{ + // On posixy systems we can pass a char** for envp, which is a + // null-terminated array of "k=v\0" strings. Since we must create + // these strings locally, yet expose a raw pointer to them, we + // create a temporary vector to own the CStrings that outlives the + // call to cb. + match env { + Some(env) => { + let mut tmps = Vec::with_capacity(env.len()); + + for pair in env.iter() { + let mut kv = Vec::new(); + kv.push_all(pair.ref0().container_as_bytes()); + kv.push('=' as u8); + kv.push_all(pair.ref1().container_as_bytes()); + kv.push(0); // terminating null + tmps.push(kv); + } + + // As with `with_argv`, this is unsafe, since cb could leak the pointers. + let mut ptrs: Vec<*const libc::c_char> = + tmps.iter() + .map(|tmp| tmp.as_ptr() as *const libc::c_char) + .collect(); + ptrs.push(ptr::null()); + + cb(ptrs.as_ptr() as *const c_void) + } + _ => cb(ptr::null()) + } +} + +fn translate_status(status: c_int) -> ProcessExit { + #![allow(non_snake_case)] + #[cfg(any(target_os = "linux", target_os = "android"))] + mod imp { + pub fn WIFEXITED(status: i32) -> bool { (status & 0xff) == 0 } + pub fn WEXITSTATUS(status: i32) -> i32 { (status >> 8) & 0xff } + pub fn WTERMSIG(status: i32) -> i32 { status & 0x7f } + } + + #[cfg(any(target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "dragonfly"))] + mod imp { + pub fn WIFEXITED(status: i32) -> bool { (status & 0x7f) == 0 } + pub fn WEXITSTATUS(status: i32) -> i32 { status >> 8 } + pub fn WTERMSIG(status: i32) -> i32 { status & 0o177 } + } + + if imp::WIFEXITED(status) { + ExitStatus(imp::WEXITSTATUS(status) as int) + } else { + ExitSignal(imp::WTERMSIG(status) as int) + } +} diff --git a/src/libstd/sys/windows/fs.rs b/src/libstd/sys/windows/fs.rs new file mode 100644 index 00000000000..a07688b2fed --- /dev/null +++ b/src/libstd/sys/windows/fs.rs @@ -0,0 +1,460 @@ +// Copyright 2014 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. + +//! Blocking Windows-based file I/O + +use alloc::arc::Arc; +use libc::{mod, c_int}; + +use c_str::CString; +use mem; +use os::windows::fill_utf16_buf_and_decode; +use path; +use ptr; +use str; +use io; + +use prelude::*; +use sys; +use sys_common::{keep_going, eof, mkerr_libc}; + +use io::{FilePermission, Write, UnstableFileStat, Open, FileAccess, FileMode}; +use io::{IoResult, IoError, FileStat, SeekStyle, Seek, Writer, Reader}; +use io::{Read, Truncate, SeekCur, SeekSet, ReadWrite, SeekEnd, Append}; + +pub use path::WindowsPath as Path; +pub type fd_t = libc::c_int; + +pub struct FileDesc { + /// The underlying C file descriptor. + pub fd: fd_t, + + /// Whether to close the file descriptor on drop. + close_on_drop: bool, +} + +impl FileDesc { + pub fn new(fd: fd_t, close_on_drop: bool) -> FileDesc { + FileDesc { fd: fd, close_on_drop: close_on_drop } + } + + pub fn read(&self, buf: &mut [u8]) -> IoResult<uint> { + let mut read = 0; + let ret = unsafe { + libc::ReadFile(self.handle(), buf.as_ptr() as libc::LPVOID, + buf.len() as libc::DWORD, &mut read, + ptr::null_mut()) + }; + if ret != 0 { + Ok(read as uint) + } else { + Err(super::last_error()) + } + } + + pub fn write(&self, buf: &[u8]) -> IoResult<()> { + let mut cur = buf.as_ptr(); + let mut remaining = buf.len(); + while remaining > 0 { + let mut amt = 0; + let ret = unsafe { + libc::WriteFile(self.handle(), cur as libc::LPVOID, + remaining as libc::DWORD, &mut amt, + ptr::null_mut()) + }; + if ret != 0 { + remaining -= amt as uint; + cur = unsafe { cur.offset(amt as int) }; + } else { + return Err(super::last_error()) + } + } + Ok(()) + } + + pub fn fd(&self) -> fd_t { self.fd } + + pub fn handle(&self) -> libc::HANDLE { + unsafe { libc::get_osfhandle(self.fd()) as libc::HANDLE } + } + + // A version of seek that takes &self so that tell can call it + // - the private seek should of course take &mut self. + fn seek_common(&self, pos: i64, style: SeekStyle) -> IoResult<u64> { + let whence = match style { + SeekSet => libc::FILE_BEGIN, + SeekEnd => libc::FILE_END, + SeekCur => libc::FILE_CURRENT, + }; + unsafe { + let mut newpos = 0; + match libc::SetFilePointerEx(self.handle(), pos, &mut newpos, whence) { + 0 => Err(super::last_error()), + _ => Ok(newpos as u64), + } + } + } + + pub fn seek(&mut self, pos: i64, style: SeekStyle) -> IoResult<u64> { + self.seek_common(pos, style) + } + + pub fn tell(&self) -> IoResult<u64> { + self.seek_common(0, SeekCur) + } + + pub fn fsync(&mut self) -> IoResult<()> { + super::mkerr_winbool(unsafe { + libc::FlushFileBuffers(self.handle()) + }) + } + + pub fn datasync(&mut self) -> IoResult<()> { return self.fsync(); } + + pub fn truncate(&mut self, offset: i64) -> IoResult<()> { + let orig_pos = try!(self.tell()); + let _ = try!(self.seek(offset, SeekSet)); + let ret = unsafe { + match libc::SetEndOfFile(self.handle()) { + 0 => Err(super::last_error()), + _ => Ok(()) + } + }; + let _ = self.seek(orig_pos as i64, SeekSet); + return ret; + } + + pub fn fstat(&mut self) -> IoResult<io::FileStat> { + let mut stat: libc::stat = unsafe { mem::zeroed() }; + match unsafe { libc::fstat(self.fd(), &mut stat) } { + 0 => Ok(mkstat(&stat)), + _ => Err(super::last_error()), + } + } + + /// Extract the actual filedescriptor without closing it. + pub fn unwrap(self) -> fd_t { + let fd = self.fd; + unsafe { mem::forget(self) }; + fd + } +} + +impl Drop for FileDesc { + fn drop(&mut self) { + // closing stdio file handles makes no sense, so never do it. Also, note + // that errors are ignored when closing a file descriptor. The reason + // for this is that if an error occurs we don't actually know if the + // file descriptor was closed or not, and if we retried (for something + // like EINTR), we might close another valid file descriptor (opened + // after we closed ours. + if self.close_on_drop && self.fd > libc::STDERR_FILENO { + let n = unsafe { libc::close(self.fd) }; + if n != 0 { + println!("error {} when closing file descriptor {}", n, self.fd); + } + } + } +} + +pub fn to_utf16(s: &Path) -> IoResult<Vec<u16>> { + sys::to_utf16(s.as_str()) +} + +pub fn open(path: &Path, fm: FileMode, fa: FileAccess) -> IoResult<FileDesc> { + // Flags passed to open_osfhandle + let flags = match fm { + Open => 0, + Append => libc::O_APPEND, + Truncate => libc::O_TRUNC, + }; + let flags = match fa { + Read => flags | libc::O_RDONLY, + Write => flags | libc::O_WRONLY | libc::O_CREAT, + ReadWrite => flags | libc::O_RDWR | libc::O_CREAT, + }; + let mut dwDesiredAccess = match fa { + Read => libc::FILE_GENERIC_READ, + Write => libc::FILE_GENERIC_WRITE, + ReadWrite => libc::FILE_GENERIC_READ | libc::FILE_GENERIC_WRITE + }; + + // libuv has a good comment about this, but the basic idea is what we try to + // emulate unix semantics by enabling all sharing by allowing things such as + // deleting a file while it's still open. + let dwShareMode = libc::FILE_SHARE_READ | libc::FILE_SHARE_WRITE | + libc::FILE_SHARE_DELETE; + + let dwCreationDisposition = match (fm, fa) { + (Truncate, Read) => libc::TRUNCATE_EXISTING, + (Truncate, _) => libc::CREATE_ALWAYS, + (Open, Read) => libc::OPEN_EXISTING, + (Open, _) => libc::OPEN_ALWAYS, + (Append, Read) => { + dwDesiredAccess |= libc::FILE_APPEND_DATA; + libc::OPEN_EXISTING + } + (Append, _) => { + dwDesiredAccess &= !libc::FILE_WRITE_DATA; + dwDesiredAccess |= libc::FILE_APPEND_DATA; + libc::OPEN_ALWAYS + } + }; + + let mut dwFlagsAndAttributes = libc::FILE_ATTRIBUTE_NORMAL; + // Compat with unix, this allows opening directories (see libuv) + dwFlagsAndAttributes |= libc::FILE_FLAG_BACKUP_SEMANTICS; + + let path = try!(to_utf16(path)); + let handle = unsafe { + libc::CreateFileW(path.as_ptr(), + dwDesiredAccess, + dwShareMode, + ptr::null_mut(), + dwCreationDisposition, + dwFlagsAndAttributes, + ptr::null_mut()) + }; + if handle == libc::INVALID_HANDLE_VALUE { + Err(super::last_error()) + } else { + let fd = unsafe { + libc::open_osfhandle(handle as libc::intptr_t, flags) + }; + if fd < 0 { + let _ = unsafe { libc::CloseHandle(handle) }; + Err(super::last_error()) + } else { + Ok(FileDesc::new(fd, true)) + } + } +} + +pub fn mkdir(p: &Path, _mode: uint) -> IoResult<()> { + let p = try!(to_utf16(p)); + super::mkerr_winbool(unsafe { + // FIXME: turn mode into something useful? #2623 + libc::CreateDirectoryW(p.as_ptr(), ptr::null_mut()) + }) +} + +pub fn readdir(p: &Path) -> IoResult<Vec<Path>> { + fn prune(root: &Path, dirs: Vec<Path>) -> Vec<Path> { + dirs.into_iter().filter(|path| { + path.as_vec() != b"." && path.as_vec() != b".." + }).map(|path| root.join(path)).collect() + } + + let star = p.join("*"); + let path = try!(to_utf16(&star)); + + unsafe { + let mut wfd = mem::zeroed(); + let find_handle = libc::FindFirstFileW(path.as_ptr(), &mut wfd); + if find_handle != libc::INVALID_HANDLE_VALUE { + let mut paths = vec![]; + let mut more_files = 1 as libc::BOOL; + while more_files != 0 { + { + let filename = str::truncate_utf16_at_nul(wfd.cFileName); + match String::from_utf16(filename) { + Some(filename) => paths.push(Path::new(filename)), + None => { + assert!(libc::FindClose(find_handle) != 0); + return Err(IoError { + kind: io::InvalidInput, + desc: "path was not valid UTF-16", + detail: Some(format!("path was not valid UTF-16: {}", filename)), + }) + }, // FIXME #12056: Convert the UCS-2 to invalid utf-8 instead of erroring + } + } + more_files = libc::FindNextFileW(find_handle, &mut wfd); + } + assert!(libc::FindClose(find_handle) != 0); + Ok(prune(p, paths)) + } else { + Err(super::last_error()) + } + } +} + +pub fn unlink(p: &Path) -> IoResult<()> { + fn do_unlink(p_utf16: &Vec<u16>) -> IoResult<()> { + super::mkerr_winbool(unsafe { libc::DeleteFileW(p_utf16.as_ptr()) }) + } + + let p_utf16 = try!(to_utf16(p)); + let res = do_unlink(&p_utf16); + match res { + Ok(()) => Ok(()), + Err(e) => { + // FIXME: change the code below to use more direct calls + // than `stat` and `chmod`, to avoid re-conversion to + // utf16 etc. + + // On unix, a readonly file can be successfully removed. On windows, + // however, it cannot. To keep the two platforms in line with + // respect to their behavior, catch this case on windows, attempt to + // change it to read-write, and then remove the file. + if e.kind == io::PermissionDenied { + let stat = match stat(p) { + Ok(stat) => stat, + Err(..) => return Err(e), + }; + if stat.perm.intersects(io::USER_WRITE) { return Err(e) } + + match chmod(p, (stat.perm | io::USER_WRITE).bits() as uint) { + Ok(()) => do_unlink(&p_utf16), + Err(..) => { + // Try to put it back as we found it + let _ = chmod(p, stat.perm.bits() as uint); + Err(e) + } + } + } else { + Err(e) + } + } + } +} + +pub fn rename(old: &Path, new: &Path) -> IoResult<()> { + let old = try!(to_utf16(old)); + let new = try!(to_utf16(new)); + super::mkerr_winbool(unsafe { + libc::MoveFileExW(old.as_ptr(), new.as_ptr(), libc::MOVEFILE_REPLACE_EXISTING) + }) +} + +pub fn chmod(p: &Path, mode: uint) -> IoResult<()> { + let p = try!(to_utf16(p)); + mkerr_libc(unsafe { + libc::wchmod(p.as_ptr(), mode as libc::c_int) + }) +} + +pub fn rmdir(p: &Path) -> IoResult<()> { + let p = try!(to_utf16(p)); + mkerr_libc(unsafe { libc::wrmdir(p.as_ptr()) }) +} + +pub fn chown(_p: &Path, _uid: int, _gid: int) -> IoResult<()> { + // libuv has this as a no-op, so seems like this should as well? + Ok(()) +} + +pub fn readlink(p: &Path) -> IoResult<Path> { + // FIXME: I have a feeling that this reads intermediate symlinks as well. + use sys::c::compat::kernel32::GetFinalPathNameByHandleW; + let p = try!(to_utf16(p)); + let handle = unsafe { + libc::CreateFileW(p.as_ptr(), + libc::GENERIC_READ, + libc::FILE_SHARE_READ, + ptr::null_mut(), + libc::OPEN_EXISTING, + libc::FILE_ATTRIBUTE_NORMAL, + ptr::null_mut()) + }; + if handle == libc::INVALID_HANDLE_VALUE { + return Err(super::last_error()) + } + // Specify (sz - 1) because the documentation states that it's the size + // without the null pointer + let ret = fill_utf16_buf_and_decode(|buf, sz| unsafe { + GetFinalPathNameByHandleW(handle, + buf as *const u16, + sz - 1, + libc::VOLUME_NAME_DOS) + }); + let ret = match ret { + Some(ref s) if s.as_slice().starts_with(r"\\?\") => { // " + Ok(Path::new(s.as_slice().slice_from(4))) + } + Some(s) => Ok(Path::new(s)), + None => Err(super::last_error()), + }; + assert!(unsafe { libc::CloseHandle(handle) } != 0); + return ret; +} + +pub fn symlink(src: &Path, dst: &Path) -> IoResult<()> { + use sys::c::compat::kernel32::CreateSymbolicLinkW; + let src = try!(to_utf16(src)); + let dst = try!(to_utf16(dst)); + super::mkerr_winbool(unsafe { + CreateSymbolicLinkW(dst.as_ptr(), src.as_ptr(), 0) as libc::BOOL + }) +} + +pub fn link(src: &Path, dst: &Path) -> IoResult<()> { + let src = try!(to_utf16(src)); + let dst = try!(to_utf16(dst)); + super::mkerr_winbool(unsafe { + libc::CreateHardLinkW(dst.as_ptr(), src.as_ptr(), ptr::null_mut()) + }) +} + +fn mkstat(stat: &libc::stat) -> FileStat { + FileStat { + size: stat.st_size as u64, + kind: match (stat.st_mode as libc::c_int) & libc::S_IFMT { + libc::S_IFREG => io::TypeFile, + libc::S_IFDIR => io::TypeDirectory, + libc::S_IFIFO => io::TypeNamedPipe, + libc::S_IFBLK => io::TypeBlockSpecial, + libc::S_IFLNK => io::TypeSymlink, + _ => io::TypeUnknown, + }, + perm: FilePermission::from_bits_truncate(stat.st_mode as u32), + created: stat.st_ctime as u64, + modified: stat.st_mtime as u64, + accessed: stat.st_atime as u64, + unstable: UnstableFileStat { + device: stat.st_dev as u64, + inode: stat.st_ino as u64, + rdev: stat.st_rdev as u64, + nlink: stat.st_nlink as u64, + uid: stat.st_uid as u64, + gid: stat.st_gid as u64, + blksize:0, + blocks: 0, + flags: 0, + gen: 0, + }, + } +} + +pub fn stat(p: &Path) -> IoResult<FileStat> { + let mut stat: libc::stat = unsafe { mem::zeroed() }; + let p = try!(to_utf16(p)); + match unsafe { libc::wstat(p.as_ptr(), &mut stat) } { + 0 => Ok(mkstat(&stat)), + _ => Err(super::last_error()), + } +} + +// FIXME: move this to platform-specific modules (for now)? +pub fn lstat(_p: &Path) -> IoResult<FileStat> { + // FIXME: implementation is missing + Err(super::unimpl()) +} + +pub fn utime(p: &Path, atime: u64, mtime: u64) -> IoResult<()> { + let mut buf = libc::utimbuf { + actime: atime as libc::time64_t, + modtime: mtime as libc::time64_t, + }; + let p = try!(to_utf16(p)); + mkerr_libc(unsafe { + libc::wutime(p.as_ptr(), &mut buf) + }) +} diff --git a/src/libstd/sys/windows/mod.rs b/src/libstd/sys/windows/mod.rs index 6f6ca3f2e62..f50244701e4 100644 --- a/src/libstd/sys/windows/mod.rs +++ b/src/libstd/sys/windows/mod.rs @@ -40,6 +40,7 @@ pub mod tcp; pub mod udp; pub mod pipe; pub mod helper_signal; +pub mod process; pub mod addrinfo { pub use sys_common::net::get_host_addresses; diff --git a/src/libstd/sys/windows/process.rs b/src/libstd/sys/windows/process.rs new file mode 100644 index 00000000000..67e87841ed2 --- /dev/null +++ b/src/libstd/sys/windows/process.rs @@ -0,0 +1,511 @@ +// Copyright 2012-2014 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. + +use libc::{pid_t, c_void, c_int}; +use libc; +use c_str::CString; +use io; +use mem; +use os; +use ptr; +use prelude::*; +use io::process::{ProcessExit, ExitStatus, ExitSignal}; +use collections; +use path::BytesContainer; +use hash::Hash; +use io::{IoResult, IoError}; + +use sys::fs; +use sys::{mod, retry, c, wouldblock, set_nonblocking, ms_to_timeval, timer}; +use sys::fs::FileDesc; +use sys_common::helper_thread::Helper; +use sys_common::{AsFileDesc, mkerr_libc, timeout}; + +use io::fs::PathExtensions; +use string::String; + +pub use sys_common::ProcessConfig; + +/** + * A value representing a child process. + * + * The lifetime of this value is linked to the lifetime of the actual + * process - the Process destructor calls self.finish() which waits + * for the process to terminate. + */ +pub struct Process { + /// The unique id of the process (this should never be negative). + pid: pid_t, + + /// A HANDLE to the process, which will prevent the pid being + /// re-used until the handle is closed. + handle: *mut (), +} + +impl Drop for Process { + fn drop(&mut self) { + free_handle(self.handle); + } +} + +impl Process { + pub fn id(&self) -> pid_t { + self.pid + } + + pub unsafe fn kill(&self, signal: int) -> IoResult<()> { + Process::killpid(self.pid, signal) + } + + pub unsafe fn killpid(pid: pid_t, signal: int) -> IoResult<()> { + let handle = libc::OpenProcess(libc::PROCESS_TERMINATE | + libc::PROCESS_QUERY_INFORMATION, + libc::FALSE, pid as libc::DWORD); + if handle.is_null() { + return Err(super::last_error()) + } + let ret = match signal { + // test for existence on signal 0 + 0 => { + let mut status = 0; + let ret = libc::GetExitCodeProcess(handle, &mut status); + if ret == 0 { + Err(super::last_error()) + } else if status != libc::STILL_ACTIVE { + Err(IoError { + kind: io::InvalidInput, + desc: "no process to kill", + detail: None, + }) + } else { + Ok(()) + } + } + 15 | 9 => { // sigterm or sigkill + let ret = libc::TerminateProcess(handle, 1); + super::mkerr_winbool(ret) + } + _ => Err(IoError { + kind: io::IoUnavailable, + desc: "unsupported signal on windows", + detail: None, + }) + }; + let _ = libc::CloseHandle(handle); + return ret; + } + + pub fn spawn<K, V, C, P>(cfg: &C, in_fd: Option<P>, + out_fd: Option<P>, err_fd: Option<P>) + -> IoResult<Process> + where C: ProcessConfig<K, V>, P: AsFileDesc, + K: BytesContainer + Eq + Hash, V: BytesContainer + { + 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, + CreateProcessW + }; + use libc::funcs::extra::msvcrt::get_osfhandle; + + use mem; + use iter::Iterator; + use str::StrPrelude; + + if cfg.gid().is_some() || cfg.uid().is_some() { + return Err(IoError { + kind: io::IoUnavailable, + desc: "unsupported gid/uid requested on windows", + detail: None, + }) + } + + // To have the spawning semantics of unix/windows stay the same, we need to + // read the *child's* PATH if one is provided. See #15149 for more details. + let program = cfg.env().and_then(|env| { + for (key, v) in env.iter() { + if b"PATH" != key.container_as_bytes() { continue } + + // Split the value and test each path to see if the + // program exists. + for path in os::split_paths(v.container_as_bytes()).into_iter() { + let path = path.join(cfg.program().as_bytes_no_nul()) + .with_extension(os::consts::EXE_EXTENSION); + if path.exists() { + return Some(path.to_c_str()) + } + } + break + } + None + }); + + unsafe { + let mut si = zeroed_startupinfo(); + si.cb = mem::size_of::<STARTUPINFO>() as DWORD; + si.dwFlags = STARTF_USESTDHANDLES; + + let cur_proc = GetCurrentProcess(); + + // Similarly to unix, we don't actually leave holes for the stdio file + // descriptors, but rather open up /dev/null equivalents. These + // equivalents are drawn from libuv's windows process spawning. + let set_fd = |fd: &Option<P>, slot: &mut HANDLE, + is_stdin: bool| { + match *fd { + None => { + let access = if is_stdin { + libc::FILE_GENERIC_READ + } else { + libc::FILE_GENERIC_WRITE | libc::FILE_READ_ATTRIBUTES + }; + let size = mem::size_of::<libc::SECURITY_ATTRIBUTES>(); + let mut sa = libc::SECURITY_ATTRIBUTES { + nLength: size as libc::DWORD, + lpSecurityDescriptor: ptr::null_mut(), + bInheritHandle: 1, + }; + let mut filename: Vec<u16> = "NUL".utf16_units().collect(); + filename.push(0); + *slot = libc::CreateFileW(filename.as_ptr(), + access, + libc::FILE_SHARE_READ | + libc::FILE_SHARE_WRITE, + &mut sa, + libc::OPEN_EXISTING, + 0, + ptr::null_mut()); + if *slot == INVALID_HANDLE_VALUE { + return Err(super::last_error()) + } + } + Some(ref fd) => { + let orig = get_osfhandle(fd.as_fd().fd()) as HANDLE; + if orig == INVALID_HANDLE_VALUE { + return Err(super::last_error()) + } + if DuplicateHandle(cur_proc, orig, cur_proc, slot, + 0, TRUE, DUPLICATE_SAME_ACCESS) == FALSE { + return Err(super::last_error()) + } + } + } + Ok(()) + }; + + try!(set_fd(&in_fd, &mut si.hStdInput, true)); + try!(set_fd(&out_fd, &mut si.hStdOutput, false)); + try!(set_fd(&err_fd, &mut si.hStdError, false)); + + let cmd_str = make_command_line(program.as_ref().unwrap_or(cfg.program()), + cfg.args()); + let mut pi = zeroed_process_information(); + let mut create_err = None; + + // stolen from the libuv code. + let mut flags = libc::CREATE_UNICODE_ENVIRONMENT; + if cfg.detach() { + flags |= libc::DETACHED_PROCESS | libc::CREATE_NEW_PROCESS_GROUP; + } + + with_envp(cfg.env(), |envp| { + with_dirp(cfg.cwd(), |dirp| { + let mut cmd_str: Vec<u16> = cmd_str.as_slice().utf16_units().collect(); + cmd_str.push(0); + let created = CreateProcessW(ptr::null(), + cmd_str.as_mut_ptr(), + ptr::null_mut(), + ptr::null_mut(), + TRUE, + flags, envp, dirp, + &mut si, &mut pi); + if created == FALSE { + create_err = Some(super::last_error()); + } + }) + }); + + assert!(CloseHandle(si.hStdInput) != 0); + assert!(CloseHandle(si.hStdOutput) != 0); + assert!(CloseHandle(si.hStdError) != 0); + + match create_err { + Some(err) => return Err(err), + None => {} + } + + // 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 std::we want the process id to stay valid at least until the + // calling code closes the process handle. + assert!(CloseHandle(pi.hThread) != 0); + + Ok(Process { + pid: pi.dwProcessId as pid_t, + handle: pi.hProcess as *mut () + }) + } + } + + /** + * Waits for a process to exit and returns the exit code, failing + * if there is no process with the specified id. + * + * Note that this is private to avoid race conditions on unix where if + * a user calls waitpid(some_process.get_id()) then some_process.finish() + * and some_process.destroy() and some_process.finalize() will then either + * operate on a none-existent process or, even worse, on a newer process + * with the same id. + */ + pub fn wait(&self, deadline: u64) -> IoResult<ProcessExit> { + use libc::types::os::arch::extra::DWORD; + use libc::consts::os::extra::{ + SYNCHRONIZE, + PROCESS_QUERY_INFORMATION, + FALSE, + STILL_ACTIVE, + INFINITE, + WAIT_TIMEOUT, + WAIT_OBJECT_0, + }; + use libc::funcs::extra::kernel32::{ + OpenProcess, + GetExitCodeProcess, + CloseHandle, + WaitForSingleObject, + }; + + unsafe { + let process = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION, + FALSE, + self.pid as DWORD); + if process.is_null() { + return Err(super::last_error()) + } + + loop { + let mut status = 0; + if GetExitCodeProcess(process, &mut status) == FALSE { + let err = Err(super::last_error()); + assert!(CloseHandle(process) != 0); + return err; + } + if status != STILL_ACTIVE { + assert!(CloseHandle(process) != 0); + return Ok(ExitStatus(status as int)); + } + let interval = if deadline == 0 { + INFINITE + } else { + let now = timer::now(); + if deadline < now {0} else {(deadline - now) as u32} + }; + match WaitForSingleObject(process, interval) { + WAIT_OBJECT_0 => {} + WAIT_TIMEOUT => { + assert!(CloseHandle(process) != 0); + return Err(timeout("process wait timed out")) + } + _ => { + let err = Err(super::last_error()); + assert!(CloseHandle(process) != 0); + return err + } + } + } + } + } +} + +fn zeroed_startupinfo() -> libc::types::os::arch::extra::STARTUPINFO { + libc::types::os::arch::extra::STARTUPINFO { + cb: 0, + lpReserved: ptr::null_mut(), + lpDesktop: ptr::null_mut(), + lpTitle: ptr::null_mut(), + dwX: 0, + dwY: 0, + dwXSize: 0, + dwYSize: 0, + dwXCountChars: 0, + dwYCountCharts: 0, + dwFillAttribute: 0, + dwFlags: 0, + wShowWindow: 0, + cbReserved2: 0, + lpReserved2: ptr::null_mut(), + hStdInput: libc::INVALID_HANDLE_VALUE, + hStdOutput: libc::INVALID_HANDLE_VALUE, + hStdError: libc::INVALID_HANDLE_VALUE, + } +} + +fn zeroed_process_information() -> libc::types::os::arch::extra::PROCESS_INFORMATION { + libc::types::os::arch::extra::PROCESS_INFORMATION { + hProcess: ptr::null_mut(), + hThread: ptr::null_mut(), + dwProcessId: 0, + dwThreadId: 0 + } +} + +fn make_command_line(prog: &CString, args: &[CString]) -> String { + let mut cmd = String::new(); + append_arg(&mut cmd, prog.as_str() + .expect("expected program name to be utf-8 encoded")); + for arg in args.iter() { + cmd.push(' '); + append_arg(&mut cmd, arg.as_str() + .expect("expected argument to be utf-8 encoded")); + } + return cmd; + + fn append_arg(cmd: &mut String, arg: &str) { + // If an argument has 0 characters then we need to quote it to ensure + // that it actually gets passed through on the command line or otherwise + // it will be dropped entirely when parsed on the other end. + let quote = arg.chars().any(|c| c == ' ' || c == '\t') || arg.len() == 0; + if quote { + cmd.push('"'); + } + let argvec: Vec<char> = arg.chars().collect(); + for i in range(0u, argvec.len()) { + append_char_at(cmd, argvec.as_slice(), i); + } + if quote { + cmd.push('"'); + } + } + + fn append_char_at(cmd: &mut String, arg: &[char], i: uint) { + match arg[i] { + '"' => { + // 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('\\'); + } + } + c => { + cmd.push(c); + } + } + } + + fn backslash_run_ends_in_quote(s: &[char], mut i: uint) -> bool { + while i < s.len() && s[i] == '\\' { + i += 1; + } + return i < s.len() && s[i] == '"'; + } +} + +fn with_envp<K, V, T>(env: Option<&collections::HashMap<K, V>>, + cb: |*mut c_void| -> T) -> T + where K: BytesContainer + Eq + Hash, V: BytesContainer +{ + // On Windows 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. + match env { + Some(env) => { + let mut blk = Vec::new(); + + for pair in env.iter() { + let kv = format!("{}={}", + pair.ref0().container_as_str().unwrap(), + pair.ref1().container_as_str().unwrap()); + blk.extend(kv.as_slice().utf16_units()); + blk.push(0); + } + + blk.push(0); + + cb(blk.as_mut_ptr() as *mut c_void) + } + _ => cb(ptr::null_mut()) + } +} + +fn with_dirp<T>(d: Option<&CString>, cb: |*const u16| -> T) -> T { + match d { + Some(dir) => { + let dir_str = dir.as_str() + .expect("expected workingdirectory to be utf-8 encoded"); + let mut dir_str: Vec<u16> = dir_str.utf16_units().collect(); + dir_str.push(0); + cb(dir_str.as_ptr()) + }, + None => cb(ptr::null()) + } +} + +fn free_handle(handle: *mut ()) { + assert!(unsafe { + libc::CloseHandle(mem::transmute(handle)) != 0 + }) +} + +#[cfg(test)] +mod tests { + + #[test] + fn test_make_command_line() { + use prelude::*; + use str; + use c_str::CString; + use super::make_command_line; + + fn test_wrapper(prog: &str, args: &[&str]) -> String { + make_command_line(&prog.to_c_str(), + args.iter() + .map(|a| a.to_c_str()) + .collect::<Vec<CString>>() + .as_slice()) + } + + assert_eq!( + test_wrapper("prog", ["aaa", "bbb", "ccc"]), + "prog aaa bbb ccc".to_string() + ); + + assert_eq!( + test_wrapper("C:\\Program Files\\blah\\blah.exe", ["aaa"]), + "\"C:\\Program Files\\blah\\blah.exe\" aaa".to_string() + ); + assert_eq!( + test_wrapper("C:\\Program Files\\test", ["aa\"bb"]), + "\"C:\\Program Files\\test\" aa\\\"bb".to_string() + ); + assert_eq!( + test_wrapper("echo", ["a b c"]), + "echo \"a b c\"".to_string() + ); + assert_eq!( + test_wrapper("\u03c0\u042f\u97f3\u00e6\u221e", []), + "\u03c0\u042f\u97f3\u00e6\u221e".to_string() + ); + } +} |
