diff options
| author | Alex Crichton <alex@alexcrichton.com> | 2013-12-12 17:54:53 -0800 |
|---|---|---|
| committer | Alex Crichton <alex@alexcrichton.com> | 2013-12-24 14:42:00 -0800 |
| commit | 6aadc9d18856f8e7ea8038e2c4b2ba0f9507e26a (patch) | |
| tree | 563ed3086b2bb3ddd683d0cf5d654763895b1201 /src/libnative | |
| parent | 49e5493587162d53a693ba7e7833edd8f1718e94 (diff) | |
| download | rust-6aadc9d18856f8e7ea8038e2c4b2ba0f9507e26a.tar.gz rust-6aadc9d18856f8e7ea8038e2c4b2ba0f9507e26a.zip | |
native: Introduce libnative
This commit introduces a new crate called "native" which will be the crate that implements the 1:1 runtime of rust. This currently entails having an implementation of std::rt::Runtime inside of libnative as well as moving all of the native I/O implementations to libnative. The current snag is that the start lang item must currently be defined in libnative in order to start running, but this will change in the future. Cool fact about this crate, there are no extra features that are enabled. Note that this commit does not include any makefile support necessary for building libnative, that's all coming in a later commit.
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() } + } +} |
