diff options
Diffstat (limited to 'src/libnative')
| -rw-r--r-- | src/libnative/io/file.rs | 958 | ||||
| -rw-r--r-- | src/libnative/io/mod.rs | 225 | ||||
| -rw-r--r-- | src/libnative/io/process.rs | 652 | ||||
| -rw-r--r-- | src/libnative/lib.rs | 61 | ||||
| -rw-r--r-- | src/libnative/task.rs | 257 |
5 files changed, 2153 insertions, 0 deletions
diff --git a/src/libnative/io/file.rs b/src/libnative/io/file.rs new file mode 100644 index 00000000000..eaa4403f7bf --- /dev/null +++ b/src/libnative/io/file.rs @@ -0,0 +1,958 @@ +// 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. + +//! Blocking posix-based file I/O + +use std::c_str::CString; +use std::io::IoError; +use std::io; +use std::libc::c_int; +use std::libc; +use std::os; +use std::rt::rtio; +use std::unstable::intrinsics; +use std::vec; + +use super::IoResult; + +#[cfg(windows)] use std::os::win32::{as_utf16_p, fill_utf16_buf_and_decode}; +#[cfg(windows)] use std::ptr; +#[cfg(windows)] use std::str; + +fn keep_going(data: &[u8], f: |*u8, uint| -> i64) -> i64 { + #[cfg(windows)] static eintr: int = 0; // doesn't matter + #[cfg(not(windows))] static eintr: int = libc::EINTR as int; + + let origamt = data.len(); + let mut data = data.as_ptr(); + let mut amt = origamt; + while amt > 0 { + let mut ret; + loop { + ret = f(data, amt); + if cfg!(not(windows)) { break } // windows has no eintr + // if we get an eintr, then try again + if ret != -1 || os::errno() as int != eintr { break } + } + if ret == 0 { + break + } else if ret != -1 { + amt -= ret as uint; + data = unsafe { data.offset(ret as int) }; + } else { + return ret; + } + } + return (origamt - amt) as i64; +} + +pub type fd_t = libc::c_int; + +pub struct FileDesc { + priv fd: fd_t, + priv close_on_drop: bool, +} + +impl FileDesc { + /// Create a `FileDesc` from an open C file descriptor. + /// + /// The `FileDesc` will take ownership of the specified file descriptor and + /// close it upon destruction if the `close_on_drop` flag is true, otherwise + /// it will not close the file descriptor when this `FileDesc` is dropped. + /// + /// Note that all I/O operations done on this object will be *blocking*, but + /// they do not require the runtime to be active. + pub fn new(fd: fd_t, close_on_drop: bool) -> FileDesc { + FileDesc { fd: fd, close_on_drop: close_on_drop } + } + + fn inner_read(&mut self, buf: &mut [u8]) -> Result<uint, IoError> { + #[cfg(windows)] type rlen = libc::c_uint; + #[cfg(not(windows))] type rlen = libc::size_t; + let ret = keep_going(buf, |buf, len| { + unsafe { + libc::read(self.fd, buf as *mut libc::c_void, len as rlen) as i64 + } + }); + if ret == 0 { + Err(io::standard_error(io::EndOfFile)) + } else if ret < 0 { + Err(super::last_error()) + } else { + Ok(ret as uint) + } + } + fn inner_write(&mut self, buf: &[u8]) -> Result<(), IoError> { + #[cfg(windows)] type wlen = libc::c_uint; + #[cfg(not(windows))] type wlen = libc::size_t; + let ret = keep_going(buf, |buf, len| { + unsafe { + libc::write(self.fd, buf as *libc::c_void, len as wlen) as i64 + } + }); + if ret < 0 { + Err(super::last_error()) + } else { + Ok(()) + } + } +} + +impl io::Reader for FileDesc { + fn read(&mut self, buf: &mut [u8]) -> Option<uint> { + match self.inner_read(buf) { Ok(n) => Some(n), Err(..) => None } + } + fn eof(&mut self) -> bool { false } +} + +impl io::Writer for FileDesc { + fn write(&mut self, buf: &[u8]) { + self.inner_write(buf); + } +} + +impl rtio::RtioFileStream for FileDesc { + fn read(&mut self, buf: &mut [u8]) -> Result<int, IoError> { + self.inner_read(buf).map(|i| i as int) + } + fn write(&mut self, buf: &[u8]) -> Result<(), IoError> { + self.inner_write(buf) + } + fn pread(&mut self, buf: &mut [u8], offset: u64) -> Result<int, IoError> { + return os_pread(self.fd, buf.as_ptr(), buf.len(), offset); + + #[cfg(windows)] + fn os_pread(fd: c_int, buf: *u8, amt: uint, offset: u64) -> IoResult<int> { + unsafe { + let mut overlap: libc::OVERLAPPED = intrinsics::init(); + let handle = libc::get_osfhandle(fd) as libc::HANDLE; + let mut bytes_read = 0; + overlap.Offset = offset as libc::DWORD; + overlap.OffsetHigh = (offset >> 32) as libc::DWORD; + + match libc::ReadFile(handle, buf as libc::LPVOID, + amt as libc::DWORD, + &mut bytes_read, &mut overlap) { + 0 => Err(super::last_error()), + _ => Ok(bytes_read as int) + } + } + } + + #[cfg(unix)] + fn os_pread(fd: c_int, buf: *u8, amt: uint, offset: u64) -> IoResult<int> { + match unsafe { + libc::pread(fd, buf as *libc::c_void, amt as libc::size_t, + offset as libc::off_t) + } { + -1 => Err(super::last_error()), + n => Ok(n as int) + } + } + } + fn pwrite(&mut self, buf: &[u8], offset: u64) -> Result<(), IoError> { + return os_pwrite(self.fd, buf.as_ptr(), buf.len(), offset); + + #[cfg(windows)] + fn os_pwrite(fd: c_int, buf: *u8, amt: uint, offset: u64) -> IoResult<()> { + unsafe { + let mut overlap: libc::OVERLAPPED = intrinsics::init(); + let handle = libc::get_osfhandle(fd) as libc::HANDLE; + overlap.Offset = offset as libc::DWORD; + overlap.OffsetHigh = (offset >> 32) as libc::DWORD; + + match libc::WriteFile(handle, buf as libc::LPVOID, + amt as libc::DWORD, + ptr::mut_null(), &mut overlap) { + 0 => Err(super::last_error()), + _ => Ok(()), + } + } + } + + #[cfg(unix)] + fn os_pwrite(fd: c_int, buf: *u8, amt: uint, offset: u64) -> IoResult<()> { + super::mkerr_libc(unsafe { + libc::pwrite(fd, buf as *libc::c_void, amt as libc::size_t, + offset as libc::off_t) + } as c_int) + } + } + #[cfg(windows)] + fn seek(&mut self, pos: i64, style: io::SeekStyle) -> Result<u64, IoError> { + let whence = match style { + io::SeekSet => libc::FILE_BEGIN, + io::SeekEnd => libc::FILE_END, + io::SeekCur => libc::FILE_CURRENT, + }; + unsafe { + let handle = libc::get_osfhandle(self.fd) as libc::HANDLE; + let mut newpos = 0; + match libc::SetFilePointerEx(handle, pos, &mut newpos, whence) { + 0 => Err(super::last_error()), + _ => Ok(newpos as u64), + } + } + } + #[cfg(unix)] + fn seek(&mut self, pos: i64, whence: io::SeekStyle) -> Result<u64, IoError> { + let whence = match whence { + io::SeekSet => libc::SEEK_SET, + io::SeekEnd => libc::SEEK_END, + io::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) + } + } + fn tell(&self) -> Result<u64, IoError> { + let n = unsafe { libc::lseek(self.fd, 0, libc::SEEK_CUR) }; + if n < 0 { + Err(super::last_error()) + } else { + Ok(n as u64) + } + } + fn fsync(&mut self) -> Result<(), IoError> { + return os_fsync(self.fd); + + #[cfg(windows)] + fn os_fsync(fd: c_int) -> IoResult<()> { + super::mkerr_winbool(unsafe { + let handle = libc::get_osfhandle(fd); + libc::FlushFileBuffers(handle as libc::HANDLE) + }) + } + #[cfg(unix)] + fn os_fsync(fd: c_int) -> IoResult<()> { + super::mkerr_libc(unsafe { libc::fsync(fd) }) + } + } + #[cfg(windows)] + fn datasync(&mut self) -> Result<(), IoError> { return self.fsync(); } + + #[cfg(not(windows))] + fn datasync(&mut self) -> Result<(), IoError> { + return super::mkerr_libc(os_datasync(self.fd)); + + #[cfg(target_os = "macos")] + 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 { unsafe { libc::fdatasync(fd) } } + #[cfg(not(target_os = "macos"), not(target_os = "linux"))] + fn os_datasync(fd: c_int) -> c_int { unsafe { libc::fsync(fd) } } + } + + #[cfg(windows)] + fn truncate(&mut self, offset: i64) -> Result<(), IoError> { + let orig_pos = match self.tell() { Ok(i) => i, Err(e) => return Err(e) }; + match self.seek(offset, io::SeekSet) { + Ok(_) => {}, Err(e) => return Err(e), + }; + let ret = unsafe { + let handle = libc::get_osfhandle(self.fd) as libc::HANDLE; + match libc::SetEndOfFile(handle) { + 0 => Err(super::last_error()), + _ => Ok(()) + } + }; + self.seek(orig_pos as i64, io::SeekSet); + return ret; + } + #[cfg(unix)] + fn truncate(&mut self, offset: i64) -> Result<(), IoError> { + super::mkerr_libc(unsafe { + libc::ftruncate(self.fd, offset as libc::off_t) + }) + } +} + +impl rtio::RtioPipe for FileDesc { + fn read(&mut self, buf: &mut [u8]) -> Result<uint, IoError> { + self.inner_read(buf) + } + fn write(&mut self, buf: &[u8]) -> Result<(), IoError> { + self.inner_write(buf) + } +} + +impl rtio::RtioTTY for FileDesc { + fn read(&mut self, buf: &mut [u8]) -> Result<uint, IoError> { + self.inner_read(buf) + } + fn write(&mut self, buf: &[u8]) -> Result<(), IoError> { + self.inner_write(buf) + } + fn set_raw(&mut self, _raw: bool) -> Result<(), IoError> { + Err(super::unimpl()) + } + fn get_winsize(&mut self) -> Result<(int, int), IoError> { + 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 + if self.close_on_drop && self.fd > libc::STDERR_FILENO { + unsafe { libc::close(self.fd); } + } + } +} + +pub struct CFile { + priv file: *libc::FILE, + priv fd: FileDesc, +} + +impl CFile { + /// Create a `CFile` from an open `FILE` pointer. + /// + /// The `CFile` takes ownership of the `FILE` pointer and will close it upon + /// destruction. + pub fn new(file: *libc::FILE) -> CFile { + CFile { + file: file, + fd: FileDesc::new(unsafe { libc::fileno(file) }, false) + } + } + + pub fn flush(&mut self) -> Result<(), IoError> { + super::mkerr_libc(unsafe { libc::fflush(self.file) }) + } +} + +impl rtio::RtioFileStream for CFile { + fn read(&mut self, buf: &mut [u8]) -> Result<int, IoError> { + let ret = keep_going(buf, |buf, len| { + unsafe { + libc::fread(buf as *mut libc::c_void, 1, len as libc::size_t, + self.file) as i64 + } + }); + if ret == 0 { + Err(io::standard_error(io::EndOfFile)) + } else if ret < 0 { + Err(super::last_error()) + } else { + Ok(ret as int) + } + } + + fn write(&mut self, buf: &[u8]) -> Result<(), IoError> { + let ret = keep_going(buf, |buf, len| { + unsafe { + libc::fwrite(buf as *libc::c_void, 1, len as libc::size_t, + self.file) as i64 + } + }); + if ret < 0 { + Err(super::last_error()) + } else { + Ok(()) + } + } + + fn pread(&mut self, buf: &mut [u8], offset: u64) -> Result<int, IoError> { + self.flush(); + self.fd.pread(buf, offset) + } + fn pwrite(&mut self, buf: &[u8], offset: u64) -> Result<(), IoError> { + self.flush(); + self.fd.pwrite(buf, offset) + } + fn seek(&mut self, pos: i64, style: io::SeekStyle) -> Result<u64, IoError> { + let whence = match style { + io::SeekSet => libc::SEEK_SET, + io::SeekEnd => libc::SEEK_END, + io::SeekCur => libc::SEEK_CUR, + }; + let n = unsafe { libc::fseek(self.file, pos as libc::c_long, whence) }; + if n < 0 { + Err(super::last_error()) + } else { + Ok(n as u64) + } + } + fn tell(&self) -> Result<u64, IoError> { + let ret = unsafe { libc::ftell(self.file) }; + if ret < 0 { + Err(super::last_error()) + } else { + Ok(ret as u64) + } + } + fn fsync(&mut self) -> Result<(), IoError> { + self.flush(); + self.fd.fsync() + } + fn datasync(&mut self) -> Result<(), IoError> { + self.flush(); + self.fd.fsync() + } + fn truncate(&mut self, offset: i64) -> Result<(), IoError> { + self.flush(); + self.fd.truncate(offset) + } +} + +impl Drop for CFile { + fn drop(&mut self) { + unsafe { libc::fclose(self.file); } + } +} + +pub fn open(path: &CString, fm: io::FileMode, fa: io::FileAccess) + -> IoResult<FileDesc> { + let flags = match fm { + io::Open => 0, + io::Append => libc::O_APPEND, + io::Truncate => libc::O_TRUNC, + }; + // Opening with a write permission must silently create the file. + let (flags, mode) = match fa { + io::Read => (flags | libc::O_RDONLY, 0), + io::Write => (flags | libc::O_WRONLY | libc::O_CREAT, + libc::S_IRUSR | libc::S_IWUSR), + io::ReadWrite => (flags | libc::O_RDWR | libc::O_CREAT, + libc::S_IRUSR | libc::S_IWUSR), + }; + + return match os_open(path, flags, mode) { + -1 => Err(super::last_error()), + fd => Ok(FileDesc::new(fd, true)), + }; + + #[cfg(windows)] + fn os_open(path: &CString, flags: c_int, mode: c_int) -> c_int { + as_utf16_p(path.as_str().unwrap(), |path| { + unsafe { libc::wopen(path, flags, mode) } + }) + } + + #[cfg(unix)] + fn os_open(path: &CString, flags: c_int, mode: c_int) -> c_int { + unsafe { libc::open(path.with_ref(|p| p), flags, mode) } + } +} + +pub fn mkdir(p: &CString, mode: io::FilePermission) -> IoResult<()> { + return os_mkdir(p, mode as c_int); + + #[cfg(windows)] + fn os_mkdir(p: &CString, _mode: c_int) -> IoResult<()> { + super::mkerr_winbool(unsafe { + // FIXME: turn mode into something useful? #2623 + as_utf16_p(p.as_str().unwrap(), |buf| { + libc::CreateDirectoryW(buf, ptr::mut_null()) + }) + }) + } + + #[cfg(unix)] + fn os_mkdir(p: &CString, mode: c_int) -> IoResult<()> { + super::mkerr_libc(unsafe { + libc::mkdir(p.with_ref(|p| p), mode as libc::mode_t) + }) + } +} + +pub fn readdir(p: &CString) -> IoResult<~[Path]> { + fn prune(root: &CString, dirs: ~[Path]) -> ~[Path] { + let root = unsafe { CString::new(root.with_ref(|p| p), false) }; + let root = Path::new(root); + + dirs.move_iter().filter(|path| { + path.as_vec() != bytes!(".") && path.as_vec() != bytes!("..") + }).map(|path| root.join(path)).collect() + } + + unsafe { + #[cfg(not(windows))] + unsafe fn get_list(p: &CString) -> IoResult<~[Path]> { + use std::libc::{dirent_t}; + use std::libc::{opendir, readdir, closedir}; + extern { + fn rust_list_dir_val(ptr: *dirent_t) -> *libc::c_char; + } + debug!("os::list_dir -- BEFORE OPENDIR"); + + let dir_ptr = p.with_ref(|buf| opendir(buf)); + + if (dir_ptr as uint != 0) { + let mut paths = ~[]; + debug!("os::list_dir -- opendir() SUCCESS"); + let mut entry_ptr = readdir(dir_ptr); + while (entry_ptr as uint != 0) { + let cstr = CString::new(rust_list_dir_val(entry_ptr), false); + paths.push(Path::new(cstr)); + entry_ptr = readdir(dir_ptr); + } + closedir(dir_ptr); + Ok(paths) + } else { + Err(super::last_error()) + } + } + + #[cfg(windows)] + unsafe fn get_list(p: &CString) -> IoResult<~[Path]> { + use std::libc::consts::os::extra::INVALID_HANDLE_VALUE; + use std::libc::{wcslen, free}; + use std::libc::funcs::extra::kernel32::{ + FindFirstFileW, + FindNextFileW, + FindClose, + }; + use std::libc::types::os::arch::extra::HANDLE; + use os::win32::{ + as_utf16_p + }; + use rt::global_heap::malloc_raw; + + #[nolink] + extern { + fn rust_list_dir_wfd_size() -> libc::size_t; + fn rust_list_dir_wfd_fp_buf(wfd: *libc::c_void) -> *u16; + } + let p = CString::new(p.with_ref(|p| p), false); + let p = Path::new(p); + let star = p.join("*"); + as_utf16_p(star.as_str().unwrap(), |path_ptr| { + let wfd_ptr = malloc_raw(rust_list_dir_wfd_size() as uint); + let find_handle = FindFirstFileW(path_ptr, wfd_ptr as HANDLE); + if find_handle as libc::c_int != INVALID_HANDLE_VALUE { + let mut paths = ~[]; + let mut more_files = 1 as libc::c_int; + while more_files != 0 { + let fp_buf = rust_list_dir_wfd_fp_buf(wfd_ptr); + if fp_buf as uint == 0 { + fail!("os::list_dir() failure: got null ptr from wfd"); + } + else { + let fp_vec = vec::from_buf( + fp_buf, wcslen(fp_buf) as uint); + let fp_str = str::from_utf16(fp_vec); + paths.push(Path::new(fp_str)); + } + more_files = FindNextFileW(find_handle, wfd_ptr as HANDLE); + } + FindClose(find_handle); + free(wfd_ptr); + Ok(paths) + } else { + Err(super::last_error()) + } + }) + } + + get_list(p).map(|paths| prune(p, paths)) + } +} + +pub fn unlink(p: &CString) -> IoResult<()> { + return os_unlink(p); + + #[cfg(windows)] + fn os_unlink(p: &CString) -> IoResult<()> { + super::mkerr_winbool(unsafe { + as_utf16_p(p.as_str().unwrap(), |buf| { + libc::DeleteFileW(buf) + }) + }) + } + + #[cfg(unix)] + fn os_unlink(p: &CString) -> IoResult<()> { + super::mkerr_libc(unsafe { libc::unlink(p.with_ref(|p| p)) }) + } +} + +pub fn rename(old: &CString, new: &CString) -> IoResult<()> { + return os_rename(old, new); + + #[cfg(windows)] + fn os_rename(old: &CString, new: &CString) -> IoResult<()> { + super::mkerr_winbool(unsafe { + as_utf16_p(old.as_str().unwrap(), |old| { + as_utf16_p(new.as_str().unwrap(), |new| { + libc::MoveFileExW(old, new, libc::MOVEFILE_REPLACE_EXISTING) + }) + }) + }) + } + + #[cfg(unix)] + fn os_rename(old: &CString, new: &CString) -> IoResult<()> { + super::mkerr_libc(unsafe { + libc::rename(old.with_ref(|p| p), new.with_ref(|p| p)) + }) + } +} + +pub fn chmod(p: &CString, mode: io::FilePermission) -> IoResult<()> { + return super::mkerr_libc(os_chmod(p, mode as c_int)); + + #[cfg(windows)] + fn os_chmod(p: &CString, mode: c_int) -> c_int { + unsafe { + as_utf16_p(p.as_str().unwrap(), |p| libc::wchmod(p, mode)) + } + } + + #[cfg(unix)] + fn os_chmod(p: &CString, mode: c_int) -> c_int { + unsafe { libc::chmod(p.with_ref(|p| p), mode as libc::mode_t) } + } +} + +pub fn rmdir(p: &CString) -> IoResult<()> { + return super::mkerr_libc(os_rmdir(p)); + + #[cfg(windows)] + fn os_rmdir(p: &CString) -> c_int { + unsafe { + as_utf16_p(p.as_str().unwrap(), |p| libc::wrmdir(p)) + } + } + + #[cfg(unix)] + fn os_rmdir(p: &CString) -> c_int { + unsafe { libc::rmdir(p.with_ref(|p| p)) } + } +} + +pub fn chown(p: &CString, uid: int, gid: int) -> IoResult<()> { + return super::mkerr_libc(os_chown(p, uid, gid)); + + // libuv has this as a no-op, so seems like this should as well? + #[cfg(windows)] + fn os_chown(_p: &CString, _uid: int, _gid: int) -> c_int { 0 } + + #[cfg(unix)] + fn os_chown(p: &CString, uid: int, gid: int) -> c_int { + unsafe { + libc::chown(p.with_ref(|p| p), uid as libc::uid_t, + gid as libc::gid_t) + } + } +} + +pub fn readlink(p: &CString) -> IoResult<Path> { + return os_readlink(p); + + // XXX: I have a feeling that this reads intermediate symlinks as well. + #[cfg(windows)] + fn os_readlink(p: &CString) -> IoResult<Path> { + let handle = unsafe { + as_utf16_p(p.as_str().unwrap(), |p| { + libc::CreateFileW(p, + libc::GENERIC_READ, + libc::FILE_SHARE_READ, + ptr::mut_null(), + libc::OPEN_EXISTING, + libc::FILE_ATTRIBUTE_NORMAL, + ptr::mut_null()) + }) + }; + if handle == ptr::mut_null() { return Err(super::last_error()) } + let ret = fill_utf16_buf_and_decode(|buf, sz| { + unsafe { + libc::GetFinalPathNameByHandleW(handle, buf as *u16, sz, + libc::VOLUME_NAME_NT) + } + }); + let ret = match ret { + Some(s) => Ok(Path::new(s)), + None => Err(super::last_error()), + }; + unsafe { libc::CloseHandle(handle) }; + return ret; + + } + + #[cfg(unix)] + fn os_readlink(p: &CString) -> IoResult<Path> { + let p = p.with_ref(|p| p); + let mut len = unsafe { libc::pathconf(p, libc::_PC_NAME_MAX) }; + if len == -1 { + len = 1024; // XXX: read PATH_MAX from C ffi? + } + let mut buf = vec::with_capacity::<u8>(len as uint); + match unsafe { + libc::readlink(p, buf.as_ptr() as *mut libc::c_char, + len as libc::size_t) + } { + -1 => Err(super::last_error()), + n => { + assert!(n > 0); + unsafe { buf.set_len(n as uint); } + Ok(Path::new(buf)) + } + } + } +} + +pub fn symlink(src: &CString, dst: &CString) -> IoResult<()> { + return os_symlink(src, dst); + + #[cfg(windows)] + fn os_symlink(src: &CString, dst: &CString) -> IoResult<()> { + super::mkerr_winbool(as_utf16_p(src.as_str().unwrap(), |src| { + as_utf16_p(dst.as_str().unwrap(), |dst| { + unsafe { libc::CreateSymbolicLinkW(dst, src, 0) } + }) + })) + } + + #[cfg(unix)] + fn os_symlink(src: &CString, dst: &CString) -> IoResult<()> { + super::mkerr_libc(unsafe { + libc::symlink(src.with_ref(|p| p), dst.with_ref(|p| p)) + }) + } +} + +pub fn link(src: &CString, dst: &CString) -> IoResult<()> { + return os_link(src, dst); + + #[cfg(windows)] + fn os_link(src: &CString, dst: &CString) -> IoResult<()> { + super::mkerr_winbool(as_utf16_p(src.as_str().unwrap(), |src| { + as_utf16_p(dst.as_str().unwrap(), |dst| { + unsafe { libc::CreateHardLinkW(dst, src, ptr::mut_null()) } + }) + })) + } + + #[cfg(unix)] + fn os_link(src: &CString, dst: &CString) -> IoResult<()> { + super::mkerr_libc(unsafe { + libc::link(src.with_ref(|p| p), dst.with_ref(|p| p)) + }) + } +} + +#[cfg(windows)] +fn mkstat(stat: &libc::stat, path: &CString) -> io::FileStat { + let path = unsafe { CString::new(path.with_ref(|p| p), false) }; + let kind = match (stat.st_mode as 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, + }; + + io::FileStat { + path: Path::new(path), + size: stat.st_size as u64, + kind: kind, + perm: (stat.st_mode) as io::FilePermission & io::AllPermissions, + created: stat.st_ctime as u64, + modified: stat.st_mtime as u64, + accessed: stat.st_atime as u64, + unstable: io::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, + } + } +} + +#[cfg(unix)] +fn mkstat(stat: &libc::stat, path: &CString) -> io::FileStat { + let path = unsafe { CString::new(path.with_ref(|p| p), false) }; + + // FileStat times are in milliseconds + fn mktime(secs: u64, nsecs: u64) -> u64 { secs * 1000 + nsecs / 1000000 } + + let kind = match (stat.st_mode as 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, + }; + + #[cfg(not(target_os = "linux"), not(target_os = "android"))] + fn flags(stat: &libc::stat) -> u64 { stat.st_flags as u64 } + #[cfg(target_os = "linux")] #[cfg(target_os = "android")] + fn flags(_stat: &libc::stat) -> u64 { 0 } + + #[cfg(not(target_os = "linux"), not(target_os = "android"))] + fn gen(stat: &libc::stat) -> u64 { stat.st_gen as u64 } + #[cfg(target_os = "linux")] #[cfg(target_os = "android")] + fn gen(_stat: &libc::stat) -> u64 { 0 } + + io::FileStat { + path: Path::new(path), + size: stat.st_size as u64, + kind: kind, + perm: (stat.st_mode) as io::FilePermission & io::AllPermissions, + 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: io::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: &CString) -> IoResult<io::FileStat> { + return os_stat(p); + + #[cfg(windows)] + fn os_stat(p: &CString) -> IoResult<io::FileStat> { + let mut stat: libc::stat = unsafe { intrinsics::uninit() }; + as_utf16_p(p.as_str().unwrap(), |up| { + match unsafe { libc::wstat(up, &mut stat) } { + 0 => Ok(mkstat(&stat, p)), + _ => Err(super::last_error()), + } + }) + } + + #[cfg(unix)] + fn os_stat(p: &CString) -> IoResult<io::FileStat> { + let mut stat: libc::stat = unsafe { intrinsics::uninit() }; + match unsafe { libc::stat(p.with_ref(|p| p), &mut stat) } { + 0 => Ok(mkstat(&stat, p)), + _ => Err(super::last_error()), + } + } +} + +pub fn lstat(p: &CString) -> IoResult<io::FileStat> { + return os_lstat(p); + + // XXX: windows implementation is missing + #[cfg(windows)] + fn os_lstat(_p: &CString) -> IoResult<io::FileStat> { + Err(super::unimpl()) + } + + #[cfg(unix)] + fn os_lstat(p: &CString) -> IoResult<io::FileStat> { + let mut stat: libc::stat = unsafe { intrinsics::uninit() }; + match unsafe { libc::lstat(p.with_ref(|p| p), &mut stat) } { + 0 => Ok(mkstat(&stat, p)), + _ => Err(super::last_error()), + } + } +} + +pub fn utime(p: &CString, atime: u64, mtime: u64) -> IoResult<()> { + return super::mkerr_libc(os_utime(p, atime, mtime)); + + #[cfg(windows)] + fn os_utime(p: &CString, atime: u64, mtime: u64) -> c_int { + let buf = libc::utimbuf { + actime: (atime / 1000) as libc::time64_t, + modtime: (mtime / 1000) as libc::time64_t, + }; + unsafe { + as_utf16_p(p.as_str().unwrap(), |p| libc::wutime(p, &buf)) + } + } + + #[cfg(unix)] + fn os_utime(p: &CString, atime: u64, mtime: u64) -> c_int { + let buf = libc::utimbuf { + actime: (atime / 1000) as libc::time_t, + modtime: (mtime / 1000) as libc::time_t, + }; + unsafe { libc::utime(p.with_ref(|p| p), &buf) } + } +} + +#[cfg(test)] +mod tests { + use std::io::native::file::{CFile, FileDesc}; + use std::io::fs; + use std::io; + use std::libc; + use std::os; + use std::rt::rtio::RtioFileStream; + + #[ignore(cfg(target_os = "freebsd"))] // 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. + unsafe { + let os::Pipe { input, out } = os::pipe(); + let mut reader = FileDesc::new(input, true); + let mut writer = FileDesc::new(out, true); + + writer.inner_write(bytes!("test")); + let mut buf = [0u8, ..4]; + match reader.inner_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 => fail!("invalid read: {:?}", r) + } + + assert!(writer.inner_read(buf).is_err()); + assert!(reader.inner_write(buf).is_err()); + } + } + + #[ignore(cfg(windows))] // apparently windows doesn't like tmpfile + #[test] + fn test_cfile() { + unsafe { + let f = libc::tmpfile(); + assert!(!f.is_null()); + let mut file = CFile::new(f); + + file.write(bytes!("test")); + let mut buf = [0u8, ..4]; + file.seek(0, io::SeekSet); + match file.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 => fail!("invalid read: {:?}", r) + } + } + } +} diff --git a/src/libnative/io/mod.rs b/src/libnative/io/mod.rs new file mode 100644 index 00000000000..36e3f8af190 --- /dev/null +++ b/src/libnative/io/mod.rs @@ -0,0 +1,225 @@ +// 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. + +//! Native thread-blocking I/O implementation +//! +//! This module contains the implementation of native thread-blocking +//! implementations of I/O on all platforms. This module is not intended to be +//! used directly, but rather the rust runtime will fall back to using it if +//! necessary. +//! +//! Rust code normally runs inside of green tasks with a local scheduler using +//! asynchronous I/O to cooperate among tasks. This model is not always +//! available, however, and that's where these native implementations come into +//! play. The only dependencies of these modules are the normal system libraries +//! that you would find on the respective platform. + +use std::c_str::CString; +use std::comm::SharedChan; +use std::libc::c_int; +use std::libc; +use std::os; +use std::rt::rtio; +use std::rt::rtio::{RtioTcpStream, RtioTcpListener, RtioUdpSocket, + RtioUnixListener, RtioPipe, RtioFileStream, RtioProcess, + RtioSignal, RtioTTY, CloseBehavior, RtioTimer}; +use std::io; +use std::io::IoError; +use std::io::net::ip::SocketAddr; +use std::io::process::ProcessConfig; +use std::io::signal::Signum; +use ai = std::io::net::addrinfo; + +// Local re-exports +pub use self::file::FileDesc; +pub use self::process::Process; + +// Native I/O implementations +pub mod file; +pub mod process; + +type IoResult<T> = Result<T, IoError>; + +fn unimpl() -> IoError { + IoError { + kind: io::IoUnavailable, + desc: "unimplemented I/O interface", + detail: None, + } +} + +fn last_error() -> IoError { + #[cfg(windows)] + fn get_err(errno: i32) -> (io::IoErrorKind, &'static str) { + match errno { + libc::EOF => (io::EndOfFile, "end of file"), + _ => (io::OtherIoError, "unknown error"), + } + } + + #[cfg(not(windows))] + fn get_err(errno: i32) -> (io::IoErrorKind, &'static str) { + // XXX: this should probably be a bit more descriptive... + match errno { + libc::EOF => (io::EndOfFile, "end of file"), + + // These two constants can have the same value on some systems, but + // different values on others, so we can't use a match clause + x if x == libc::EAGAIN || x == libc::EWOULDBLOCK => + (io::ResourceUnavailable, "resource temporarily unavailable"), + + _ => (io::OtherIoError, "unknown error"), + } + } + + let (kind, desc) = get_err(os::errno() as i32); + IoError { + kind: kind, + desc: desc, + detail: Some(os::last_os_error()) + } +} + +// unix has nonzero values as errors +fn mkerr_libc(ret: libc::c_int) -> IoResult<()> { + if ret != 0 { + Err(last_error()) + } else { + Ok(()) + } +} + +// windows has zero values as errors +#[cfg(windows)] +fn mkerr_winbool(ret: libc::c_int) -> IoResult<()> { + if ret == 0 { + Err(last_error()) + } else { + Ok(()) + } +} + +/// Implementation of rt::rtio's IoFactory trait to generate handles to the +/// native I/O functionality. +pub struct IoFactory; + +impl rtio::IoFactory for IoFactory { + // all native io factories are the same + fn id(&self) -> uint { 0 } + + // networking + fn tcp_connect(&mut self, _addr: SocketAddr) -> IoResult<~RtioTcpStream> { + Err(unimpl()) + } + fn tcp_bind(&mut self, _addr: SocketAddr) -> IoResult<~RtioTcpListener> { + Err(unimpl()) + } + fn udp_bind(&mut self, _addr: SocketAddr) -> IoResult<~RtioUdpSocket> { + Err(unimpl()) + } + fn unix_bind(&mut self, _path: &CString) -> IoResult<~RtioUnixListener> { + Err(unimpl()) + } + fn unix_connect(&mut self, _path: &CString) -> IoResult<~RtioPipe> { + Err(unimpl()) + } + fn get_host_addresses(&mut self, _host: Option<&str>, _servname: Option<&str>, + _hint: Option<ai::Hint>) -> IoResult<~[ai::Info]> { + Err(unimpl()) + } + + // filesystem operations + fn fs_from_raw_fd(&mut self, fd: c_int, + close: CloseBehavior) -> ~RtioFileStream { + let close = match close { + rtio::CloseSynchronously | rtio::CloseAsynchronously => true, + rtio::DontClose => false + }; + ~file::FileDesc::new(fd, close) as ~RtioFileStream + } + fn fs_open(&mut self, path: &CString, fm: io::FileMode, fa: io::FileAccess) + -> IoResult<~RtioFileStream> { + file::open(path, fm, fa).map(|fd| ~fd as ~RtioFileStream) + } + fn fs_unlink(&mut self, path: &CString) -> IoResult<()> { + file::unlink(path) + } + fn fs_stat(&mut self, path: &CString) -> IoResult<io::FileStat> { + file::stat(path) + } + fn fs_mkdir(&mut self, path: &CString, + mode: io::FilePermission) -> IoResult<()> { + file::mkdir(path, mode) + } + fn fs_chmod(&mut self, path: &CString, + mode: io::FilePermission) -> IoResult<()> { + file::chmod(path, mode) + } + fn fs_rmdir(&mut self, path: &CString) -> IoResult<()> { + file::rmdir(path) + } + fn fs_rename(&mut self, path: &CString, to: &CString) -> IoResult<()> { + file::rename(path, to) + } + fn fs_readdir(&mut self, path: &CString, _flags: c_int) -> IoResult<~[Path]> { + file::readdir(path) + } + fn fs_lstat(&mut self, path: &CString) -> IoResult<io::FileStat> { + file::lstat(path) + } + fn fs_chown(&mut self, path: &CString, uid: int, gid: int) -> IoResult<()> { + file::chown(path, uid, gid) + } + fn fs_readlink(&mut self, path: &CString) -> IoResult<Path> { + file::readlink(path) + } + fn fs_symlink(&mut self, src: &CString, dst: &CString) -> IoResult<()> { + file::symlink(src, dst) + } + fn fs_link(&mut self, src: &CString, dst: &CString) -> IoResult<()> { + file::link(src, dst) + } + fn fs_utime(&mut self, src: &CString, atime: u64, + mtime: u64) -> IoResult<()> { + file::utime(src, atime, mtime) + } + + // misc + fn timer_init(&mut self) -> IoResult<~RtioTimer> { + Err(unimpl()) + } + fn spawn(&mut self, config: ProcessConfig) + -> IoResult<(~RtioProcess, ~[Option<~RtioPipe>])> { + process::Process::spawn(config).map(|(p, io)| { + (~p as ~RtioProcess, + io.move_iter().map(|p| p.map(|p| ~p as ~RtioPipe)).collect()) + }) + } + fn pipe_open(&mut self, fd: c_int) -> IoResult<~RtioPipe> { + Ok(~file::FileDesc::new(fd, true) as ~RtioPipe) + } + fn tty_open(&mut self, fd: c_int, _readable: bool) -> IoResult<~RtioTTY> { + if unsafe { libc::isatty(fd) } != 0 { + // Don't ever close the stdio file descriptors, nothing good really + // comes of that. + Ok(~file::FileDesc::new(fd, fd > libc::STDERR_FILENO) as ~RtioTTY) + } else { + Err(IoError { + kind: io::MismatchedFileTypeForOperation, + desc: "file descriptor is not a TTY", + detail: None, + }) + } + } + fn signal(&mut self, _signal: Signum, _channel: SharedChan<Signum>) + -> IoResult<~RtioSignal> { + Err(unimpl()) + } +} diff --git a/src/libnative/io/process.rs b/src/libnative/io/process.rs new file mode 100644 index 00000000000..2277d408ee4 --- /dev/null +++ b/src/libnative/io/process.rs @@ -0,0 +1,652 @@ +// Copyright 2012-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. + +use std::cast; +use std::io; +use std::libc::{pid_t, c_void, c_int}; +use std::libc; +use std::os; +use std::ptr; +use std::rt::rtio; +use p = std::io::process; + +use super::file; + +/** + * 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). + priv 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. + priv handle: *(), + + /// None until finish() is called. + priv exit_code: Option<int>, +} + +impl Process { + /// Creates a new process using native process-spawning abilities provided + /// by the OS. Operations on this process will be blocking instead of using + /// the runtime for sleeping just this current task. + /// + /// # Arguments + /// + /// * prog - the program to run + /// * args - the arguments to pass to the program, not including the program + /// itself + /// * env - an optional environment to specify for the child process. If + /// this value is `None`, then the child will inherit the parent's + /// environment + /// * cwd - an optionally specified current working directory of the child, + /// defaulting to the parent's current working directory + /// * stdin, stdout, stderr - These optionally specified file descriptors + /// dictate where the stdin/out/err of the child process will go. If + /// these are `None`, then this module will bind the input/output to an + /// os pipe instead. This process takes ownership of these file + /// descriptors, closing them upon destruction of the process. + pub fn spawn(config: p::ProcessConfig) + -> Result<(Process, ~[Option<file::FileDesc>]), io::IoError> + { + // right now we only handle stdin/stdout/stderr. + if config.io.len() > 3 { + return Err(super::unimpl()); + } + + fn get_io(io: &[p::StdioContainer], + ret: &mut ~[Option<file::FileDesc>], + idx: uint) -> (Option<os::Pipe>, c_int) { + if idx >= io.len() { return (None, -1); } + ret.push(None); + match io[idx] { + p::Ignored => (None, -1), + p::InheritFd(fd) => (None, fd), + p::CreatePipe(readable, _writable) => { + let pipe = os::pipe(); + let (theirs, ours) = if readable { + (pipe.input, pipe.out) + } else { + (pipe.out, pipe.input) + }; + ret[idx] = Some(file::FileDesc::new(ours, true)); + (Some(pipe), theirs) + } + } + } + + let mut ret_io = ~[]; + let (in_pipe, in_fd) = get_io(config.io, &mut ret_io, 0); + let (out_pipe, out_fd) = get_io(config.io, &mut ret_io, 1); + let (err_pipe, err_fd) = get_io(config.io, &mut ret_io, 2); + + let env = config.env.map(|a| a.to_owned()); + let cwd = config.cwd.map(|a| Path::new(a)); + let res = spawn_process_os(config.program, config.args, env, + cwd.as_ref(), in_fd, out_fd, err_fd); + + unsafe { + for pipe in in_pipe.iter() { libc::close(pipe.input); } + for pipe in out_pipe.iter() { libc::close(pipe.out); } + for pipe in err_pipe.iter() { libc::close(pipe.out); } + } + + Ok((Process { pid: res.pid, handle: res.handle, exit_code: None }, ret_io)) + } +} + +impl rtio::RtioProcess for Process { + fn id(&self) -> pid_t { self.pid } + + fn wait(&mut self) -> p::ProcessExit { + let code = match self.exit_code { + Some(code) => code, + None => { + let code = waitpid(self.pid); + self.exit_code = Some(code); + code + } + }; + return p::ExitStatus(code); // XXX: this is wrong + } + + fn kill(&mut self, signum: int) -> Result<(), io::IoError> { + // if the process has finished, and therefore had waitpid called, + // and we kill it, then on unix we might ending up killing a + // newer process that happens to have the same (re-used) id + match self.exit_code { + Some(..) => return Err(io::IoError { + kind: io::OtherIoError, + desc: "can't kill an exited process", + detail: None, + }), + None => {} + } + return unsafe { killpid(self.pid, signum) }; + + #[cfg(windows)] + unsafe fn killpid(pid: pid_t, signal: int) -> Result<(), io::IoError> { + match signal { + io::process::PleaseExitSignal | io::process::MustDieSignal => { + libc::funcs::extra::kernel32::TerminateProcess( + cast::transmute(pid), 1); + Ok(()) + } + _ => Err(io::IoError { + kind: io::OtherIoError, + desc: "unsupported signal on windows", + detail: None, + }) + } + } + + #[cfg(not(windows))] + unsafe fn killpid(pid: pid_t, signal: int) -> Result<(), io::IoError> { + libc::funcs::posix88::signal::kill(pid, signal as c_int); + Ok(()) + } + } +} + +impl Drop for Process { + fn drop(&mut self) { + free_handle(self.handle); + } +} + +struct SpawnProcessResult { + pid: pid_t, + handle: *(), +} + +#[cfg(windows)] +fn spawn_process_os(prog: &str, args: &[~str], + env: Option<~[(~str, ~str)]>, + dir: Option<&Path>, + in_fd: c_int, out_fd: c_int, err_fd: c_int) -> SpawnProcessResult { + use std::libc::types::os::arch::extra::{DWORD, HANDLE, STARTUPINFO}; + use std::libc::consts::os::extra::{ + TRUE, FALSE, + STARTF_USESTDHANDLES, + INVALID_HANDLE_VALUE, + DUPLICATE_SAME_ACCESS + }; + use std::libc::funcs::extra::kernel32::{ + GetCurrentProcess, + DuplicateHandle, + CloseHandle, + CreateProcessA + }; + use std::libc::funcs::extra::msvcrt::get_osfhandle; + + use std::mem; + + unsafe { + + let mut si = zeroed_startupinfo(); + si.cb = mem::size_of::<STARTUPINFO>() as DWORD; + si.dwFlags = STARTF_USESTDHANDLES; + + let cur_proc = GetCurrentProcess(); + + let orig_std_in = get_osfhandle(in_fd) as HANDLE; + if orig_std_in == INVALID_HANDLE_VALUE as HANDLE { + fail!("failure in get_osfhandle: {}", os::last_os_error()); + } + if DuplicateHandle(cur_proc, orig_std_in, cur_proc, &mut si.hStdInput, + 0, TRUE, DUPLICATE_SAME_ACCESS) == FALSE { + fail!("failure in DuplicateHandle: {}", os::last_os_error()); + } + + let orig_std_out = get_osfhandle(out_fd) as HANDLE; + if orig_std_out == INVALID_HANDLE_VALUE as HANDLE { + fail!("failure in get_osfhandle: {}", os::last_os_error()); + } + if DuplicateHandle(cur_proc, orig_std_out, cur_proc, &mut si.hStdOutput, + 0, TRUE, DUPLICATE_SAME_ACCESS) == FALSE { + fail!("failure in DuplicateHandle: {}", os::last_os_error()); + } + + let orig_std_err = get_osfhandle(err_fd) as HANDLE; + if orig_std_err == INVALID_HANDLE_VALUE as HANDLE { + fail!("failure in get_osfhandle: {}", os::last_os_error()); + } + if DuplicateHandle(cur_proc, orig_std_err, cur_proc, &mut si.hStdError, + 0, TRUE, DUPLICATE_SAME_ACCESS) == FALSE { + fail!("failure in DuplicateHandle: {}", os::last_os_error()); + } + + let cmd = make_command_line(prog, args); + let mut pi = zeroed_process_information(); + let mut create_err = None; + + with_envp(env, |envp| { + with_dirp(dir, |dirp| { + cmd.with_c_str(|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 msg in create_err.iter() { + fail!("failure in CreateProcess: {}", *msg); + } + + // We close the thread handle because std::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. + CloseHandle(pi.hThread); + + SpawnProcessResult { + 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 arg in args.iter() { + cmd.push_char(' '); + append_arg(&mut cmd, *arg); + } + return cmd; + + fn append_arg(cmd: &mut ~str, arg: &str) { + let quote = arg.chars().any(|c| c == ' ' || c == '\t'); + if quote { + cmd.push_char('"'); + } + for i in range(0u, arg.len()) { + 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_os(prog: &str, args: &[~str], + env: Option<~[(~str, ~str)]>, + dir: Option<&Path>, + in_fd: c_int, out_fd: c_int, err_fd: c_int) -> SpawnProcessResult { + use std::libc::funcs::posix88::unistd::{fork, dup2, close, chdir, execvp}; + use std::libc::funcs::bsd44::getdtablesize; + + mod rustrt { + extern { + pub fn rust_unset_sigprocmask(); + } + } + + #[cfg(windows)] + unsafe fn set_environ(_envp: *c_void) {} + #[cfg(target_os = "macos")] + unsafe fn set_environ(envp: *c_void) { + extern { fn _NSGetEnviron() -> *mut *c_void; } + + *_NSGetEnviron() = envp; + } + #[cfg(not(target_os = "macos"), not(windows))] + unsafe fn set_environ(envp: *c_void) { + extern { + static mut environ: *c_void; + } + environ = envp; + } + + unsafe { + + let pid = fork(); + if pid < 0 { + fail!("failure in fork: {}", os::last_os_error()); + } else if pid > 0 { + return SpawnProcessResult {pid: pid, handle: ptr::null()}; + } + + rustrt::rust_unset_sigprocmask(); + + if dup2(in_fd, 0) == -1 { + fail!("failure in dup2(in_fd, 0): {}", os::last_os_error()); + } + if dup2(out_fd, 1) == -1 { + fail!("failure in dup2(out_fd, 1): {}", os::last_os_error()); + } + if dup2(err_fd, 2) == -1 { + fail!("failure in dup3(err_fd, 2): {}", os::last_os_error()); + } + // close all other fds + for fd in range(3, getdtablesize()).invert() { + close(fd as c_int); + } + + with_dirp(dir, |dirp| { + if !dirp.is_null() && chdir(dirp) == -1 { + fail!("failure in chdir: {}", os::last_os_error()); + } + }); + + with_envp(env, |envp| { + if !envp.is_null() { + set_environ(envp); + } + with_argv(prog, args, |argv| { + execvp(*argv, argv); + // execvp only returns if an error occurred + fail!("failure in execvp: {}", os::last_os_error()); + }) + }) + } +} + +#[cfg(unix)] +fn with_argv<T>(prog: &str, args: &[~str], cb: |**libc::c_char| -> T) -> T { + use std::vec; + + // We can't directly convert `str`s into `*char`s, as someone needs to hold + // a reference to the intermediary byte buffers. So first build an array to + // hold all the ~[u8] byte strings. + let mut tmps = vec::with_capacity(args.len() + 1); + + tmps.push(prog.to_c_str()); + + for arg in args.iter() { + tmps.push(arg.to_c_str()); + } + + // Next, convert each of the byte strings into a pointer. This is + // technically unsafe as the caller could leak these pointers out of our + // scope. + let mut ptrs = tmps.map(|tmp| tmp.with_ref(|buf| buf)); + + // Finally, make sure we add a null pointer. + ptrs.push(ptr::null()); + + cb(ptrs.as_ptr()) +} + +#[cfg(unix)] +fn with_envp<T>(env: Option<~[(~str, ~str)]>, cb: |*c_void| -> T) -> T { + use std::vec; + + // On posixy systems we can pass a char** for envp, which is a + // null-terminated array of "k=v\n" strings. Like `with_argv`, we have to + // have a temporary buffer to hold the intermediary `~[u8]` byte strings. + match env { + Some(env) => { + let mut tmps = vec::with_capacity(env.len()); + + for pair in env.iter() { + let kv = format!("{}={}", pair.first(), pair.second()); + tmps.push(kv.to_c_str()); + } + + // Once again, this is unsafe. + let mut ptrs = tmps.map(|tmp| tmp.with_ref(|buf| buf)); + ptrs.push(ptr::null()); + + cb(ptrs.as_ptr() as *c_void) + } + _ => cb(ptr::null()) + } +} + +#[cfg(windows)] +fn with_envp<T>(env: Option<~[(~str, ~str)]>, cb: |*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. + match env { + Some(env) => { + let mut blk = ~[]; + + for pair in env.iter() { + let kv = format!("{}={}", pair.first(), pair.second()); + blk.push_all(kv.as_bytes()); + blk.push(0); + } + + blk.push(0); + + cb(blk.as_mut_ptr() as *mut c_void) + } + _ => cb(ptr::mut_null()) + } +} + +fn with_dirp<T>(d: Option<&Path>, cb: |*libc::c_char| -> T) -> T { + match d { + Some(dir) => dir.with_c_str(|buf| cb(buf)), + None => cb(ptr::null()) + } +} + +#[cfg(windows)] +fn free_handle(handle: *()) { + unsafe { + libc::funcs::extra::kernel32::CloseHandle(cast::transmute(handle)); + } +} + +#[cfg(unix)] +fn free_handle(_handle: *()) { + // unix has no process handle object, just a pid +} + +/** + * 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. + */ +fn waitpid(pid: pid_t) -> int { + return waitpid_os(pid); + + #[cfg(windows)] + fn waitpid_os(pid: pid_t) -> int { + use std::libc::types::os::arch::extra::DWORD; + use std::libc::consts::os::extra::{ + SYNCHRONIZE, + PROCESS_QUERY_INFORMATION, + FALSE, + STILL_ACTIVE, + INFINITE, + WAIT_FAILED + }; + use std::libc::funcs::extra::kernel32::{ + OpenProcess, + GetExitCodeProcess, + CloseHandle, + WaitForSingleObject + }; + + unsafe { + + let process = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION, + FALSE, + pid as DWORD); + if process.is_null() { + fail!("failure in OpenProcess: {}", os::last_os_error()); + } + + loop { + let mut status = 0; + if GetExitCodeProcess(process, &mut status) == FALSE { + CloseHandle(process); + fail!("failure in GetExitCodeProcess: {}", os::last_os_error()); + } + if status != STILL_ACTIVE { + CloseHandle(process); + return status as int; + } + if WaitForSingleObject(process, INFINITE) == WAIT_FAILED { + CloseHandle(process); + fail!("failure in WaitForSingleObject: {}", os::last_os_error()); + } + } + } + } + + #[cfg(unix)] + fn waitpid_os(pid: pid_t) -> int { + use std::libc::funcs::posix01::wait; + + #[cfg(target_os = "linux")] + #[cfg(target_os = "android")] + fn WIFEXITED(status: i32) -> bool { + (status & 0xffi32) == 0i32 + } + + #[cfg(target_os = "macos")] + #[cfg(target_os = "freebsd")] + fn WIFEXITED(status: i32) -> bool { + (status & 0x7fi32) == 0i32 + } + + #[cfg(target_os = "linux")] + #[cfg(target_os = "android")] + fn WEXITSTATUS(status: i32) -> i32 { + (status >> 8i32) & 0xffi32 + } + + #[cfg(target_os = "macos")] + #[cfg(target_os = "freebsd")] + fn WEXITSTATUS(status: i32) -> i32 { + status >> 8i32 + } + + let mut status = 0 as c_int; + if unsafe { wait::waitpid(pid, &mut status, 0) } == -1 { + fail!("failure in waitpid: {}", os::last_os_error()); + } + + return if WIFEXITED(status) { + WEXITSTATUS(status) as int + } else { + 1 + }; + } +} + +#[cfg(test)] +mod tests { + + #[test] #[cfg(windows)] + fn test_make_command_line() { + use super::make_command_line; + assert_eq!( + make_command_line("prog", [~"aaa", ~"bbb", ~"ccc"]), + ~"prog aaa bbb ccc" + ); + assert_eq!( + make_command_line("C:\\Program Files\\blah\\blah.exe", [~"aaa"]), + ~"\"C:\\Program Files\\blah\\blah.exe\" aaa" + ); + assert_eq!( + make_command_line("C:\\Program Files\\test", [~"aa\"bb"]), + ~"\"C:\\Program Files\\test\" aa\\\"bb" + ); + assert_eq!( + make_command_line("echo", [~"a b c"]), + ~"echo \"a b c\"" + ); + } + + // Currently most of the tests of this functionality live inside std::run, + // but they may move here eventually as a non-blocking backend is added to + // std::run +} diff --git a/src/libnative/lib.rs b/src/libnative/lib.rs new file mode 100644 index 00000000000..4b32511dc42 --- /dev/null +++ b/src/libnative/lib.rs @@ -0,0 +1,61 @@ +// 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. + +//! The native runtime crate +//! +//! This crate contains an implementation of 1:1 scheduling for a "native" +//! runtime. In addition, all I/O provided by this crate is the thread blocking +//! version of I/O. + +#[link(name = "native", + package_id = "native", + vers = "0.9-pre", + uuid = "535344a7-890f-5a23-e1f3-e0d118805141", + url = "https://github.com/mozilla/rust/tree/master/src/native")]; + +#[license = "MIT/ASL2"]; +#[crate_type = "rlib"]; +#[crate_type = "dylib"]; + +// NB this crate explicitly does *not* allow glob imports, please seriously +// consider whether they're needed before adding that feature here. + +use std::cast; +use std::os; +use std::rt; +use std::task::try; + +pub mod io; +pub mod task; + +// XXX: this should not exist here +#[cfg(stage0)] +#[lang = "start"] +pub fn start(main: *u8, argc: int, argv: **u8) -> int { + rt::init(argc, argv); + + // Bootstrap ourselves by installing a local Task and then immediately + // spawning a thread to run 'main'. Always spawn a new thread for main so + // the stack size of 'main' is known (and the bounds can be set + // appropriately). + // + // Once the main task has completed, then we wait for everyone else to exit. + task::run(task::new(), proc() { + let main: extern "Rust" fn() = unsafe { cast::transmute(main) }; + match do try { main() } { + Ok(()) => { os::set_exit_status(0); } + Err(..) => { os::set_exit_status(rt::DEFAULT_ERROR_CODE); } + } + }); + task::wait_for_completion(); + + unsafe { rt::cleanup(); } + os::get_exit_status() +} diff --git a/src/libnative/task.rs b/src/libnative/task.rs new file mode 100644 index 00000000000..fa7500ca85e --- /dev/null +++ b/src/libnative/task.rs @@ -0,0 +1,257 @@ +// 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. + +//! Tasks implemented on top of OS threads +//! +//! This module contains the implementation of the 1:1 threading module required +//! by rust tasks. This implements the necessary API traits laid out by std::rt +//! in order to spawn new tasks and deschedule the current task. + +use std::cast; +use std::rt::env; +use std::rt::local::Local; +use std::rt::rtio; +use std::rt::task::{Task, BlockedTask}; +use std::rt::thread::Thread; +use std::rt; +use std::sync::atomics::{AtomicUint, SeqCst, INIT_ATOMIC_UINT}; +use std::task::TaskOpts; +use std::unstable::mutex::{Mutex, MUTEX_INIT}; +use std::unstable::stack; + +use io; +use task; + +static mut THREAD_CNT: AtomicUint = INIT_ATOMIC_UINT; +static mut LOCK: Mutex = MUTEX_INIT; + +/// Waits for all spawned threads to finish completion. This should only be used +/// by the main task in order to wait for all other tasks to terminate. +/// +/// This mirrors the same semantics as the green scheduling model. +pub fn wait_for_completion() { + static mut someone_waited: bool = false; + + unsafe { + LOCK.lock(); + assert!(!someone_waited); + someone_waited = true; + while THREAD_CNT.load(SeqCst) > 0 { + LOCK.wait(); + } + LOCK.unlock(); + LOCK.destroy(); + } + +} + +// Signal that a thread has finished execution, possibly waking up a blocker +// waiting for all threads to have finished. +fn signal_done() { + unsafe { + LOCK.lock(); + if THREAD_CNT.fetch_sub(1, SeqCst) == 1 { + LOCK.signal(); + } + LOCK.unlock(); + } +} + +/// Creates a new Task which is ready to execute as a 1:1 task. +pub fn new() -> ~Task { + let mut task = ~Task::new(); + task.put_runtime(~Ops { + lock: unsafe { Mutex::new() }, + } as ~rt::Runtime); + return task; +} + +/// Spawns a new task given the configuration options and a procedure to run +/// inside the task. +pub fn spawn(opts: TaskOpts, f: proc()) { + // must happen before the spawn, no need to synchronize with a lock. + unsafe { THREAD_CNT.fetch_add(1, SeqCst); } + + let TaskOpts { + watched: _watched, + notify_chan, name, stack_size + } = opts; + + let mut task = new(); + task.name = name; + match notify_chan { + Some(chan) => { + let on_exit = proc(task_result) { chan.send(task_result) }; + task.death.on_exit = Some(on_exit); + } + None => {} + } + + let stack = stack_size.unwrap_or(env::min_stack()); + let task = task; + + // Spawning a new OS thread guarantees that __morestack will never get + // triggered, but we must manually set up the actual stack bounds once this + // function starts executing. This raises the lower limit by a bit because + // by the time that this function is executing we've already consumed at + // least a little bit of stack (we don't know the exact byte address at + // which our stack started). + Thread::spawn_stack(stack, proc() { + let something_around_the_top_of_the_stack = 1; + let addr = &something_around_the_top_of_the_stack as *int; + unsafe { + let my_stack = addr as uint; + stack::record_stack_bounds(my_stack - stack + 1024, my_stack); + } + + run(task, f); + signal_done(); + }) +} + +/// Runs a task once, consuming the task. The given procedure is run inside of +/// the task. +pub fn run(t: ~Task, f: proc()) { + let mut f = Some(f); + t.run(|| { f.take_unwrap()(); }); +} + +// This structure is the glue between channels and the 1:1 scheduling mode. This +// structure is allocated once per task. +struct Ops { + lock: Mutex, // native synchronization +} + +impl rt::Runtime for Ops { + fn yield_now(~self, mut cur_task: ~Task) { + // put the task back in TLS and then invoke the OS thread yield + cur_task.put_runtime(self as ~rt::Runtime); + Local::put(cur_task); + Thread::yield_now(); + } + + fn maybe_yield(~self, mut cur_task: ~Task) { + // just put the task back in TLS, on OS threads we never need to + // opportunistically yield b/c the OS will do that for us (preemption) + cur_task.put_runtime(self as ~rt::Runtime); + Local::put(cur_task); + } + + fn wrap(~self) -> ~Any { + self as ~Any + } + + // This function gets a little interesting. There are a few safety and + // ownership violations going on here, but this is all done in the name of + // shared state. Additionally, all of the violations are protected with a + // mutex, so in theory there are no races. + // + // The first thing we need to do is to get a pointer to the task's internal + // mutex. This address will not be changing (because the task is allocated + // on the heap). We must have this handle separately because the task will + // have its ownership transferred to the given closure. We're guaranteed, + // however, that this memory will remain valid because *this* is the current + // task's execution thread. + // + // The next weird part is where ownership of the task actually goes. We + // relinquish it to the `f` blocking function, but upon returning this + // function needs to replace the task back in TLS. There is no communication + // from the wakeup thread back to this thread about the task pointer, and + // there's really no need to. In order to get around this, we cast the task + // to a `uint` which is then used at the end of this function to cast back + // to a `~Task` object. Naturally, this looks like it violates ownership + // semantics in that there may be two `~Task` objects. + // + // The fun part is that the wakeup half of this implementation knows to + // "forget" the task on the other end. This means that the awakening half of + // things silently relinquishes ownership back to this thread, but not in a + // way that the compiler can understand. The task's memory is always valid + // for both tasks because these operations are all done inside of a mutex. + // + // You'll also find that if blocking fails (the `f` function hands the + // BlockedTask back to us), we will `cast::forget` the handles. The + // reasoning for this is the same logic as above in that the task silently + // transfers ownership via the `uint`, not through normal compiler + // semantics. + fn deschedule(mut ~self, times: uint, mut cur_task: ~Task, + f: |BlockedTask| -> Result<(), BlockedTask>) { + let my_lock: *mut Mutex = &mut self.lock as *mut Mutex; + cur_task.put_runtime(self as ~rt::Runtime); + + unsafe { + let cur_task_dupe = *cast::transmute::<&~Task, &uint>(&cur_task); + let task = BlockedTask::block(cur_task); + + if times == 1 { + (*my_lock).lock(); + match f(task) { + Ok(()) => (*my_lock).wait(), + Err(task) => { cast::forget(task.wake()); } + } + (*my_lock).unlock(); + } else { + let mut iter = task.make_selectable(times); + (*my_lock).lock(); + let success = iter.all(|task| { + match f(task) { + Ok(()) => true, + Err(task) => { + cast::forget(task.wake()); + false + } + } + }); + if success { + (*my_lock).wait(); + } + (*my_lock).unlock(); + } + // re-acquire ownership of the task + cur_task = cast::transmute::<uint, ~Task>(cur_task_dupe); + } + + // put the task back in TLS, and everything is as it once was. + Local::put(cur_task); + } + + // See the comments on `deschedule` for why the task is forgotten here, and + // why it's valid to do so. + fn reawaken(mut ~self, mut to_wake: ~Task, _can_resched: bool) { + unsafe { + let lock: *mut Mutex = &mut self.lock as *mut Mutex; + to_wake.put_runtime(self as ~rt::Runtime); + cast::forget(to_wake); + (*lock).lock(); + (*lock).signal(); + (*lock).unlock(); + } + } + + fn spawn_sibling(~self, mut cur_task: ~Task, opts: TaskOpts, f: proc()) { + cur_task.put_runtime(self as ~rt::Runtime); + Local::put(cur_task); + + task::spawn(opts, f); + } + + fn local_io<'a>(&'a mut self) -> Option<rtio::LocalIo<'a>> { + static mut io: io::IoFactory = io::IoFactory; + // Unsafety is from accessing `io`, which is guaranteed to be safe + // because you can't do anything usable with this statically initialized + // unit struct. + Some(unsafe { rtio::LocalIo::new(&mut io as &mut rtio::IoFactory) }) + } +} + +impl Drop for Ops { + fn drop(&mut self) { + unsafe { self.lock.destroy() } + } +} |
