diff options
| author | bors <bors@rust-lang.org> | 2014-11-09 05:51:44 +0000 |
|---|---|---|
| committer | bors <bors@rust-lang.org> | 2014-11-09 05:51:44 +0000 |
| commit | 16d80de231abb2b1756f3951ffd4776d681035eb (patch) | |
| tree | 4b84ec180f4fd1debe62d440dd8be665582e55f8 /src/libstd | |
| parent | a2f303ad098844351d08800038a4f99fa2ff0817 (diff) | |
| parent | 5ea09e6a25816fb6f0aca5adb874c623981653df (diff) | |
| download | rust-16d80de231abb2b1756f3951ffd4776d681035eb.tar.gz rust-16d80de231abb2b1756f3951ffd4776d681035eb.zip | |
auto merge of #18557 : aturon/rust/io-removal, r=alexcrichton
This PR includes a sequence of commits that gradually dismantles the `librustrt` `rtio` system -- the main trait previously used to abstract over green and native io. It also largely dismantles `libnative`, moving much of its code into `libstd` and refactoring as it does so. TL;DR: * Before this PR: `rustc hello.rs && wc -c hello` produces 715,996 * After this PR: `rustc hello.rs && wc -c hello` produces 368,100 That is, this PR reduces the footprint of hello world by ~50%. This is a major step toward #17325 (i.e. toward implementing the [runtime removal RFC](https://github.com/rust-lang/rfcs/pull/230).) What remains is to pull out the scheduling, synchronization and task infrastructure, and to remove `libgreen`. These will be done soon in a follow-up PR. Part of the work here is eliminating the `rtio` abstraction, which in many cases means bringing the implementation of io closer to the actual API presented in `std::io`. Another aspect of this PR is the creation of two new, *private* modules within `std` that implement io: * The `sys` module, which represents a platform-specific implementation of a number of low-level abstractions that are used directly within `std::io` and `std::os`. These "abstractions" are left largely the same as they were in `libnative` (except for the removal of `Arc` in file descriptors), but they are expected to evolve greatly over time. Organizationally, there are `sys/unix/` and `sys/windows/` directories which both implement the entire `sys` module hierarchy; this means that nearly all of the platform-specific code is isolated and you can get a handle on each platform in isolation. * The `sys_common` module, which is rooted at `sys/common`, and provides a few pieces of private, low-level, but cross-platform functionality. In the long term, the `sys` modules will provide hooks for exposing high-level platform-specific APIs as part of `libstd`. The first such API will be access to file descriptors from `std::io` abstractions, but a bit of design work remains before that step can be taken. The `sys_common` module includes some traits (like `AsFileDesc`) which allow communication of private details between modules in disparate locations in the hierarchy; this helps overcome the relatively simple hierarchical privacy system in Rust. To emphasize: the organization in `sys` is *very preliminary* and the main goal was to migrate away from `rtio` as quickly and simply as possible. The design will certainly evolve over time, and all of the details are currently private. Along the way, this PR also entirely removes signal handling, since it was only supported on `librustuv` which was removed a while ago. Because of the removal of APIs from `libnative` and `librustrt`, and the removal of signal handling, this is a: [breaking-change] Some of these APIs will return in public from from `std` over time. r? @alexcrichton
Diffstat (limited to 'src/libstd')
38 files changed, 6796 insertions, 844 deletions
diff --git a/src/libstd/io/fs.rs b/src/libstd/io/fs.rs index e76046bac05..5c2a5c3512d 100644 --- a/src/libstd/io/fs.rs +++ b/src/libstd/io/fs.rs @@ -52,28 +52,25 @@ fs::unlink(&path); */ -use c_str::ToCStr; use clone::Clone; use io::standard_error; -use io::{FilePermission, Write, UnstableFileStat, Open, FileAccess, FileMode}; +use io::{FilePermission, Write, Open, FileAccess, FileMode}; use io::{IoResult, IoError, FileStat, SeekStyle, Seek, Writer, Reader}; -use io::{Read, Truncate, SeekCur, SeekSet, ReadWrite, SeekEnd, Append}; +use io::{Read, Truncate, ReadWrite, Append}; use io::UpdateIoError; use io; use iter::{Iterator, Extend}; -use kinds::Send; -use libc; use option::{Some, None, Option}; -use boxed::Box; use path::{Path, GenericPath}; use path; use result::{Err, Ok}; -use rt::rtio::LocalIo; -use rt::rtio; use slice::SlicePrelude; use string::String; use vec::Vec; +use sys::fs as fs_imp; +use sys_common; + /// Unconstrained file access type that exposes read and write operations /// /// Can be constructed via `File::open()`, `File::create()`, and @@ -86,11 +83,17 @@ use vec::Vec; /// configured at creation time, via the `FileAccess` parameter to /// `File::open_mode()`. pub struct File { - fd: Box<rtio::RtioFileStream + Send>, + fd: fs_imp::FileDesc, path: Path, last_nread: int, } +impl sys_common::AsFileDesc for File { + fn as_fd(&self) -> &fs_imp::FileDesc { + &self.fd + } +} + impl File { /// Open a file at `path` in the mode specified by the `mode` and `access` /// arguments @@ -133,26 +136,13 @@ impl File { pub fn open_mode(path: &Path, mode: FileMode, access: FileAccess) -> IoResult<File> { - let rtio_mode = match mode { - Open => rtio::Open, - Append => rtio::Append, - Truncate => rtio::Truncate, - }; - let rtio_access = match access { - Read => rtio::Read, - Write => rtio::Write, - ReadWrite => rtio::ReadWrite, - }; - let err = LocalIo::maybe_raise(|io| { - io.fs_open(&path.to_c_str(), rtio_mode, rtio_access).map(|fd| { - File { - path: path.clone(), - fd: fd, - last_nread: -1 - } - }) - }).map_err(IoError::from_rtio_error); - err.update_err("couldn't open file", |e| { + fs_imp::open(path, mode, access).map(|fd| { + File { + path: path.clone(), + fd: fd, + last_nread: -1 + } + }).update_err("couldn't open file", |e| { format!("{}; path={}; mode={}; access={}", e, path.display(), mode_string(mode), access_string(access)) }) @@ -194,7 +184,7 @@ impl File { /// ``` pub fn create(path: &Path) -> IoResult<File> { File::open_mode(path, Truncate, Write) - .update_desc("couldn't create file") + .update_desc("couldn't create file") } /// Returns the original path which was used to open this file. @@ -206,9 +196,9 @@ impl File { /// device. This will flush any internal buffers necessary to perform this /// operation. pub fn fsync(&mut self) -> IoResult<()> { - let err = self.fd.fsync().map_err(IoError::from_rtio_error); - err.update_err("couldn't fsync file", - |e| format!("{}; path={}", e, self.path.display())) + self.fd.fsync() + .update_err("couldn't fsync file", + |e| format!("{}; path={}", e, self.path.display())) } /// This function is similar to `fsync`, except that it may not synchronize @@ -216,9 +206,9 @@ impl File { /// must synchronize content, but don't need the metadata on disk. The goal /// of this method is to reduce disk operations. pub fn datasync(&mut self) -> IoResult<()> { - let err = self.fd.datasync().map_err(IoError::from_rtio_error); - err.update_err("couldn't datasync file", - |e| format!("{}; path={}", e, self.path.display())) + self.fd.datasync() + .update_err("couldn't datasync file", + |e| format!("{}; path={}", e, self.path.display())) } /// Either truncates or extends the underlying file, updating the size of @@ -230,10 +220,9 @@ impl File { /// will be extended to `size` and have all of the intermediate data filled /// in with 0s. pub fn truncate(&mut self, size: i64) -> IoResult<()> { - let err = self.fd.truncate(size).map_err(IoError::from_rtio_error); - err.update_err("couldn't truncate file", |e| { - format!("{}; path={}; size={}", e, self.path.display(), size) - }) + self.fd.truncate(size) + .update_err("couldn't truncate file", |e| + format!("{}; path={}; size={}", e, self.path.display(), size)) } /// Returns true if the stream has reached the end of the file. @@ -251,12 +240,9 @@ impl File { /// Queries information about the underlying file. pub fn stat(&mut self) -> IoResult<FileStat> { - let err = match self.fd.fstat() { - Ok(s) => Ok(from_rtio(s)), - Err(e) => Err(IoError::from_rtio_error(e)), - }; - err.update_err("couldn't fstat file", - |e| format!("{}; path={}", e, self.path.display())) + self.fd.fstat() + .update_err("couldn't fstat file", |e| + format!("{}; path={}", e, self.path.display())) } } @@ -282,41 +268,9 @@ impl File { /// user lacks permissions to remove the file, or if some other filesystem-level /// error occurs. pub fn unlink(path: &Path) -> IoResult<()> { - return match do_unlink(path) { - Ok(()) => Ok(()), - Err(e) => { - // On unix, a readonly file can be successfully removed. On windows, - // however, it cannot. To keep the two platforms in line with - // respect to their behavior, catch this case on windows, attempt to - // change it to read-write, and then remove the file. - if cfg!(windows) && e.kind == io::PermissionDenied { - let stat = match stat(path) { - Ok(stat) => stat, - Err(..) => return Err(e), - }; - if stat.perm.intersects(io::USER_WRITE) { return Err(e) } - - match chmod(path, stat.perm | io::USER_WRITE) { - Ok(()) => do_unlink(path), - Err(..) => { - // Try to put it back as we found it - let _ = chmod(path, stat.perm); - Err(e) - } - } - } else { - Err(e) - } - } - }; - - fn do_unlink(path: &Path) -> IoResult<()> { - let err = LocalIo::maybe_raise(|io| { - io.fs_unlink(&path.to_c_str()) - }).map_err(IoError::from_rtio_error); - err.update_err("couldn't unlink path", - |e| format!("{}; path={}", e, path.display())) - } + fs_imp::unlink(path) + .update_err("couldn't unlink path", |e| + format!("{}; path={}", e, path.display())) } /// Given a path, query the file system to get information about a file, @@ -341,12 +295,9 @@ pub fn unlink(path: &Path) -> IoResult<()> { /// to perform a `stat` call on the given `path` or if there is no entry in the /// filesystem at the provided path. pub fn stat(path: &Path) -> IoResult<FileStat> { - let err = match LocalIo::maybe_raise(|io| io.fs_stat(&path.to_c_str())) { - Ok(s) => Ok(from_rtio(s)), - Err(e) => Err(IoError::from_rtio_error(e)), - }; - err.update_err("couldn't stat path", - |e| format!("{}; path={}", e, path.display())) + fs_imp::stat(path) + .update_err("couldn't stat path", |e| + format!("{}; path={}", e, path.display())) } /// Perform the same operation as the `stat` function, except that this @@ -358,53 +309,9 @@ pub fn stat(path: &Path) -> IoResult<FileStat> { /// /// See `stat` pub fn lstat(path: &Path) -> IoResult<FileStat> { - let err = match LocalIo::maybe_raise(|io| io.fs_lstat(&path.to_c_str())) { - Ok(s) => Ok(from_rtio(s)), - Err(e) => Err(IoError::from_rtio_error(e)), - }; - err.update_err("couldn't lstat path", - |e| format!("{}; path={}", e, path.display())) -} - -fn from_rtio(s: rtio::FileStat) -> FileStat { - #[cfg(windows)] - type Mode = libc::c_int; - #[cfg(unix)] - type Mode = libc::mode_t; - - let rtio::FileStat { - size, kind, perm, created, modified, - accessed, device, inode, rdev, - nlink, uid, gid, blksize, blocks, flags, gen - } = s; - - FileStat { - size: size, - kind: match (kind as Mode) & libc::S_IFMT { - libc::S_IFREG => io::TypeFile, - libc::S_IFDIR => io::TypeDirectory, - libc::S_IFIFO => io::TypeNamedPipe, - libc::S_IFBLK => io::TypeBlockSpecial, - libc::S_IFLNK => io::TypeSymlink, - _ => io::TypeUnknown, - }, - perm: FilePermission::from_bits_truncate(perm as u32), - created: created, - modified: modified, - accessed: accessed, - unstable: UnstableFileStat { - device: device, - inode: inode, - rdev: rdev, - nlink: nlink, - uid: uid, - gid: gid, - blksize: blksize, - blocks: blocks, - flags: flags, - gen: gen, - }, - } + fs_imp::lstat(path) + .update_err("couldn't lstat path", |e| + format!("{}; path={}", e, path.display())) } /// Rename a file or directory to a new name. @@ -424,12 +331,9 @@ fn from_rtio(s: rtio::FileStat) -> FileStat { /// the process lacks permissions to view the contents, or if some other /// intermittent I/O error occurs. pub fn rename(from: &Path, to: &Path) -> IoResult<()> { - let err = LocalIo::maybe_raise(|io| { - io.fs_rename(&from.to_c_str(), &to.to_c_str()) - }).map_err(IoError::from_rtio_error); - err.update_err("couldn't rename path", |e| { - format!("{}; from={}; to={}", e, from.display(), to.display()) - }) + fs_imp::rename(from, to) + .update_err("couldn't rename path", |e| + format!("{}; from={}; to={}", e, from.display(), to.display())) } /// Copies the contents of one file to another. This function will also @@ -462,8 +366,9 @@ pub fn rename(from: &Path, to: &Path) -> IoResult<()> { /// being created and then destroyed by this operation. pub fn copy(from: &Path, to: &Path) -> IoResult<()> { fn update_err<T>(result: IoResult<T>, from: &Path, to: &Path) -> IoResult<T> { - result.update_err("couldn't copy path", - |e| format!("{}; from={}; to={}", e, from.display(), to.display())) + result.update_err("couldn't copy path", |e| { + format!("{}; from={}; to={}", e, from.display(), to.display()) + }) } if !from.is_file() { @@ -512,45 +417,33 @@ pub fn copy(from: &Path, to: &Path) -> IoResult<()> { /// the process lacks permissions to change the attributes of the file, or if /// some other I/O error is encountered. pub fn chmod(path: &Path, mode: io::FilePermission) -> IoResult<()> { - let err = LocalIo::maybe_raise(|io| { - io.fs_chmod(&path.to_c_str(), mode.bits() as uint) - }).map_err(IoError::from_rtio_error); - err.update_err("couldn't chmod path", |e| { - format!("{}; path={}; mode={}", e, path.display(), mode) - }) + fs_imp::chmod(path, mode.bits() as uint) + .update_err("couldn't chmod path", |e| + format!("{}; path={}; mode={}", e, path.display(), mode)) } /// Change the user and group owners of a file at the specified path. pub fn chown(path: &Path, uid: int, gid: int) -> IoResult<()> { - let err = LocalIo::maybe_raise(|io| { - io.fs_chown(&path.to_c_str(), uid, gid) - }).map_err(IoError::from_rtio_error); - err.update_err("couldn't chown path", |e| { - format!("{}; path={}; uid={}; gid={}", e, path.display(), uid, gid) - }) + fs_imp::chown(path, uid, gid) + .update_err("couldn't chown path", |e| + format!("{}; path={}; uid={}; gid={}", e, path.display(), uid, gid)) } /// Creates a new hard link on the filesystem. The `dst` path will be a /// link pointing to the `src` path. Note that systems often require these /// two paths to both be located on the same filesystem. pub fn link(src: &Path, dst: &Path) -> IoResult<()> { - let err = LocalIo::maybe_raise(|io| { - io.fs_link(&src.to_c_str(), &dst.to_c_str()) - }).map_err(IoError::from_rtio_error); - err.update_err("couldn't link path", |e| { - format!("{}; src={}; dest={}", e, src.display(), dst.display()) - }) + fs_imp::link(src, dst) + .update_err("couldn't link path", |e| + format!("{}; src={}; dest={}", e, src.display(), dst.display())) } /// Creates a new symbolic link on the filesystem. The `dst` path will be a /// symlink pointing to the `src` path. pub fn symlink(src: &Path, dst: &Path) -> IoResult<()> { - let err = LocalIo::maybe_raise(|io| { - io.fs_symlink(&src.to_c_str(), &dst.to_c_str()) - }).map_err(IoError::from_rtio_error); - err.update_err("couldn't symlink path", |e| { - format!("{}; src={}; dest={}", e, src.display(), dst.display()) - }) + fs_imp::symlink(src, dst) + .update_err("couldn't symlink path", |e| + format!("{}; src={}; dest={}", e, src.display(), dst.display())) } /// Reads a symlink, returning the file that the symlink points to. @@ -560,11 +453,9 @@ pub fn symlink(src: &Path, dst: &Path) -> IoResult<()> { /// This function will return an error on failure. Failure conditions include /// reading a file that does not exist or reading a file which is not a symlink. pub fn readlink(path: &Path) -> IoResult<Path> { - let err = LocalIo::maybe_raise(|io| { - Ok(Path::new(try!(io.fs_readlink(&path.to_c_str())))) - }).map_err(IoError::from_rtio_error); - err.update_err("couldn't resolve symlink for path", - |e| format!("{}; path={}", e, path.display())) + fs_imp::readlink(path) + .update_err("couldn't resolve symlink for path", |e| + format!("{}; path={}", e, path.display())) } /// Create a new, empty directory at the provided path @@ -585,12 +476,9 @@ pub fn readlink(path: &Path) -> IoResult<Path> { /// This function will return an error if the user lacks permissions to make a /// new directory at the provided `path`, or if the directory already exists. pub fn mkdir(path: &Path, mode: FilePermission) -> IoResult<()> { - let err = LocalIo::maybe_raise(|io| { - io.fs_mkdir(&path.to_c_str(), mode.bits() as uint) - }).map_err(IoError::from_rtio_error); - err.update_err("couldn't create directory", |e| { - format!("{}; path={}; mode={}", e, path.display(), mode) - }) + fs_imp::mkdir(path, mode.bits() as uint) + .update_err("couldn't create directory", |e| + format!("{}; path={}; mode={}", e, path.display(), mode)) } /// Remove an existing, empty directory @@ -610,11 +498,9 @@ pub fn mkdir(path: &Path, mode: FilePermission) -> IoResult<()> { /// This function will return an error if the user lacks permissions to remove /// the directory at the provided `path`, or if the directory isn't empty. pub fn rmdir(path: &Path) -> IoResult<()> { - let err = LocalIo::maybe_raise(|io| { - io.fs_rmdir(&path.to_c_str()) - }).map_err(IoError::from_rtio_error); - err.update_err("couldn't remove directory", - |e| format!("{}; path={}", e, path.display())) + fs_imp::rmdir(path) + .update_err("couldn't remove directory", |e| + format!("{}; path={}", e, path.display())) } /// Retrieve a vector containing all entries within a provided directory @@ -650,13 +536,9 @@ pub fn rmdir(path: &Path) -> IoResult<()> { /// the process lacks permissions to view the contents or if the `path` points /// at a non-directory file pub fn readdir(path: &Path) -> IoResult<Vec<Path>> { - let err = LocalIo::maybe_raise(|io| { - Ok(try!(io.fs_readdir(&path.to_c_str(), 0)).into_iter().map(|a| { - Path::new(a) - }).collect()) - }).map_err(IoError::from_rtio_error); - err.update_err("couldn't read directory", - |e| format!("{}; path={}", e, path.display())) + fs_imp::readdir(path) + .update_err("couldn't read directory", + |e| format!("{}; path={}", e, path.display())) } /// Returns an iterator which will recursively walk the directory structure @@ -666,8 +548,7 @@ pub fn readdir(path: &Path) -> IoResult<Vec<Path>> { pub fn walk_dir(path: &Path) -> IoResult<Directories> { Ok(Directories { stack: try!(readdir(path).update_err("couldn't walk directory", - |e| format!("{}; path={}", - e, path.display()))) + |e| format!("{}; path={}", e, path.display()))) }) } @@ -681,12 +562,7 @@ impl Iterator<Path> for Directories { match self.stack.pop() { Some(path) => { if path.is_dir() { - let result = readdir(&path) - .update_err("couldn't advance Directories iterator", - |e| format!("{}; path={}", - e, path.display())); - - match result { + match readdir(&path) { Ok(dirs) => { self.stack.extend(dirs.into_iter()); } Err(..) => {} } @@ -804,11 +680,9 @@ pub fn rmdir_recursive(path: &Path) -> IoResult<()> { /// be in milliseconds. // FIXME(#10301) these arguments should not be u64 pub fn change_file_times(path: &Path, atime: u64, mtime: u64) -> IoResult<()> { - let err = LocalIo::maybe_raise(|io| { - io.fs_utime(&path.to_c_str(), atime, mtime) - }).map_err(IoError::from_rtio_error); - err.update_err("couldn't change_file_times", - |e| format!("{}; path={}", e, path.display())) + fs_imp::utime(path, atime, mtime) + .update_err("couldn't change_file_times", |e| + format!("{}; path={}", e, path.display())) } impl Reader for File { @@ -819,12 +693,11 @@ impl Reader for File { e, file.path.display())) } - let result = update_err(self.fd.read(buf) - .map_err(IoError::from_rtio_error), self); + let result = update_err(self.fd.read(buf), self); match result { Ok(read) => { - self.last_nread = read; + self.last_nread = read as int; match read { 0 => update_err(Err(standard_error(io::EndOfFile)), self), _ => Ok(read as uint) @@ -837,32 +710,27 @@ impl Reader for File { impl Writer for File { fn write(&mut self, buf: &[u8]) -> IoResult<()> { - let err = self.fd.write(buf).map_err(IoError::from_rtio_error); - err.update_err("couldn't write to file", - |e| format!("{}; path={}", e, self.path.display())) + self.fd.write(buf) + .update_err("couldn't write to file", + |e| format!("{}; path={}", e, self.path.display())) } } impl Seek for File { fn tell(&self) -> IoResult<u64> { - let err = self.fd.tell().map_err(IoError::from_rtio_error); - err.update_err("couldn't retrieve file cursor (`tell`)", - |e| format!("{}; path={}", e, self.path.display())) + self.fd.tell() + .update_err("couldn't retrieve file cursor (`tell`)", + |e| format!("{}; path={}", e, self.path.display())) } fn seek(&mut self, pos: i64, style: SeekStyle) -> IoResult<()> { - let style = match style { - SeekSet => rtio::SeekSet, - SeekCur => rtio::SeekCur, - SeekEnd => rtio::SeekEnd, - }; let err = match self.fd.seek(pos, style) { Ok(_) => { // successful seek resets EOF indicator self.last_nread = -1; Ok(()) } - Err(e) => Err(IoError::from_rtio_error(e)), + Err(e) => Err(e), }; err.update_err("couldn't seek in file", |e| format!("{}; path={}", e, self.path.display())) @@ -942,6 +810,8 @@ fn access_string(access: FileAccess) -> &'static str { #[cfg(test)] #[allow(unused_imports)] +#[allow(unused_variables)] +#[allow(unused_mut)] mod test { use prelude::*; use io::{SeekSet, SeekCur, SeekEnd, Read, Open, ReadWrite}; diff --git a/src/libstd/io/mod.rs b/src/libstd/io/mod.rs index c404741b7c3..31eab4363d0 100644 --- a/src/libstd/io/mod.rs +++ b/src/libstd/io/mod.rs @@ -228,14 +228,13 @@ use error::{FromError, Error}; use fmt; use int; use iter::Iterator; -use libc; use mem::transmute; use ops::{BitOr, BitXor, BitAnd, Sub, Not}; use option::{Option, Some, None}; use os; use boxed::Box; use result::{Ok, Err, Result}; -use rt::rtio; +use sys; use slice::{AsSlice, SlicePrelude}; use str::{Str, StrPrelude}; use str; @@ -312,92 +311,12 @@ impl IoError { /// struct is filled with an allocated string describing the error /// in more detail, retrieved from the operating system. pub fn from_errno(errno: uint, detail: bool) -> IoError { - - #[cfg(windows)] - fn get_err(errno: i32) -> (IoErrorKind, &'static str) { - match errno { - libc::EOF => (EndOfFile, "end of file"), - libc::ERROR_NO_DATA => (BrokenPipe, "the pipe is being closed"), - libc::ERROR_FILE_NOT_FOUND => (FileNotFound, "file not found"), - libc::ERROR_INVALID_NAME => (InvalidInput, "invalid file name"), - libc::WSAECONNREFUSED => (ConnectionRefused, "connection refused"), - libc::WSAECONNRESET => (ConnectionReset, "connection reset"), - libc::ERROR_ACCESS_DENIED | libc::WSAEACCES => - (PermissionDenied, "permission denied"), - libc::WSAEWOULDBLOCK => { - (ResourceUnavailable, "resource temporarily unavailable") - } - libc::WSAENOTCONN => (NotConnected, "not connected"), - libc::WSAECONNABORTED => (ConnectionAborted, "connection aborted"), - libc::WSAEADDRNOTAVAIL => (ConnectionRefused, "address not available"), - libc::WSAEADDRINUSE => (ConnectionRefused, "address in use"), - libc::ERROR_BROKEN_PIPE => (EndOfFile, "the pipe has ended"), - libc::ERROR_OPERATION_ABORTED => - (TimedOut, "operation timed out"), - libc::WSAEINVAL => (InvalidInput, "invalid argument"), - libc::ERROR_CALL_NOT_IMPLEMENTED => - (IoUnavailable, "function not implemented"), - libc::ERROR_INVALID_HANDLE => - (MismatchedFileTypeForOperation, - "invalid handle provided to function"), - libc::ERROR_NOTHING_TO_TERMINATE => - (InvalidInput, "no process to kill"), - - // libuv maps this error code to EISDIR. we do too. if it is found - // to be incorrect, we can add in some more machinery to only - // return this message when ERROR_INVALID_FUNCTION after certain - // Windows calls. - libc::ERROR_INVALID_FUNCTION => (InvalidInput, - "illegal operation on a directory"), - - _ => (OtherIoError, "unknown error") - } - } - - #[cfg(not(windows))] - fn get_err(errno: i32) -> (IoErrorKind, &'static str) { - // FIXME: this should probably be a bit more descriptive... - match errno { - libc::EOF => (EndOfFile, "end of file"), - libc::ECONNREFUSED => (ConnectionRefused, "connection refused"), - libc::ECONNRESET => (ConnectionReset, "connection reset"), - libc::EPERM | libc::EACCES => - (PermissionDenied, "permission denied"), - libc::EPIPE => (BrokenPipe, "broken pipe"), - libc::ENOTCONN => (NotConnected, "not connected"), - libc::ECONNABORTED => (ConnectionAborted, "connection aborted"), - libc::EADDRNOTAVAIL => (ConnectionRefused, "address not available"), - libc::EADDRINUSE => (ConnectionRefused, "address in use"), - libc::ENOENT => (FileNotFound, "no such file or directory"), - libc::EISDIR => (InvalidInput, "illegal operation on a directory"), - libc::ENOSYS => (IoUnavailable, "function not implemented"), - libc::EINVAL => (InvalidInput, "invalid argument"), - libc::ENOTTY => - (MismatchedFileTypeForOperation, - "file descriptor is not a TTY"), - libc::ETIMEDOUT => (TimedOut, "operation timed out"), - libc::ECANCELED => (TimedOut, "operation aborted"), - - // 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 => - (ResourceUnavailable, "resource temporarily unavailable"), - - _ => (OtherIoError, "unknown error") - } - } - - let (kind, desc) = get_err(errno as i32); - IoError { - kind: kind, - desc: desc, - detail: if detail && kind == OtherIoError { - Some(os::error_string(errno).as_slice().chars().map(|c| c.to_lowercase()).collect()) - } else { - None - }, + let mut err = sys::decode_error(errno as i32); + if detail && err.kind == OtherIoError { + err.detail = Some(os::error_string(errno).as_slice().chars() + .map(|c| c.to_lowercase()).collect()) } + err } /// Retrieve the last error to occur as a (detailed) IoError. @@ -409,17 +328,6 @@ impl IoError { pub fn last_error() -> IoError { IoError::from_errno(os::errno() as uint, true) } - - fn from_rtio_error(err: rtio::IoError) -> IoError { - let rtio::IoError { code, extra, detail } = err; - let mut ioerr = IoError::from_errno(code, false); - ioerr.detail = detail; - ioerr.kind = match ioerr.kind { - TimedOut if extra > 0 => ShortWrite(extra), - k => k, - }; - return ioerr; - } } impl fmt::Show for IoError { diff --git a/src/libstd/io/net/addrinfo.rs b/src/libstd/io/net/addrinfo.rs index 3c72f58b10d..22775d54eff 100644 --- a/src/libstd/io/net/addrinfo.rs +++ b/src/libstd/io/net/addrinfo.rs @@ -20,12 +20,10 @@ getaddrinfo() #![allow(missing_docs)] use iter::Iterator; -use io::{IoResult, IoError}; +use io::{IoResult}; use io::net::ip::{SocketAddr, IpAddr}; use option::{Option, Some, None}; -use result::{Ok, Err}; -use rt::rtio::{IoFactory, LocalIo}; -use rt::rtio; +use sys; use vec::Vec; /// Hints to the types of sockets that are desired when looking up hosts @@ -94,31 +92,7 @@ pub fn get_host_addresses(host: &str) -> IoResult<Vec<IpAddr>> { #[allow(unused_variables)] fn lookup(hostname: Option<&str>, servname: Option<&str>, hint: Option<Hint>) -> IoResult<Vec<Info>> { - let hint = hint.map(|Hint { family, socktype, protocol, flags }| { - rtio::AddrinfoHint { - family: family, - socktype: 0, // FIXME: this should use the above variable - protocol: 0, // FIXME: this should use the above variable - flags: flags, - } - }); - match LocalIo::maybe_raise(|io| { - io.get_host_addresses(hostname, servname, hint) - }) { - Ok(v) => Ok(v.into_iter().map(|info| { - Info { - address: SocketAddr { - ip: super::from_rtio(info.address.ip), - port: info.address.port, - }, - family: info.family, - socktype: None, // FIXME: this should use the above variable - protocol: None, // FIXME: this should use the above variable - flags: info.flags, - } - }).collect()), - Err(e) => Err(IoError::from_rtio_error(e)), - } + sys::addrinfo::get_host_addresses(hostname, servname, hint) } // Ignored on android since we cannot give tcp/ip diff --git a/src/libstd/io/net/mod.rs b/src/libstd/io/net/mod.rs index b9b50a55a10..5b1747876d7 100644 --- a/src/libstd/io/net/mod.rs +++ b/src/libstd/io/net/mod.rs @@ -12,9 +12,8 @@ use io::{IoError, IoResult, InvalidInput}; use option::None; -use result::{Result, Ok, Err}; -use rt::rtio; -use self::ip::{Ipv4Addr, Ipv6Addr, IpAddr, SocketAddr, ToSocketAddr}; +use result::{Ok, Err}; +use self::ip::{SocketAddr, ToSocketAddr}; pub use self::addrinfo::get_host_addresses; @@ -24,46 +23,6 @@ pub mod udp; pub mod ip; pub mod pipe; -fn to_rtio(ip: IpAddr) -> rtio::IpAddr { - match ip { - Ipv4Addr(a, b, c, d) => rtio::Ipv4Addr(a, b, c, d), - Ipv6Addr(a, b, c, d, e, f, g, h) => { - rtio::Ipv6Addr(a, b, c, d, e, f, g, h) - } - } -} - -fn from_rtio(ip: rtio::IpAddr) -> IpAddr { - match ip { - rtio::Ipv4Addr(a, b, c, d) => Ipv4Addr(a, b, c, d), - rtio::Ipv6Addr(a, b, c, d, e, f, g, h) => { - Ipv6Addr(a, b, c, d, e, f, g, h) - } - } -} - -fn with_addresses_io<A: ToSocketAddr, T>( - addr: A, - action: |&mut rtio::IoFactory, rtio::SocketAddr| -> Result<T, rtio::IoError> -) -> Result<T, IoError> { - const DEFAULT_ERROR: IoError = IoError { - kind: InvalidInput, - desc: "no addresses found for hostname", - detail: None - }; - - let addresses = try!(addr.to_socket_addr_all()); - let mut err = DEFAULT_ERROR; - for addr in addresses.into_iter() { - let addr = rtio::SocketAddr { ip: to_rtio(addr.ip), port: addr.port }; - match rtio::LocalIo::maybe_raise(|io| action(io, addr)) { - Ok(r) => return Ok(r), - Err(e) => err = IoError::from_rtio_error(e) - } - } - Err(err) -} - fn with_addresses<A: ToSocketAddr, T>(addr: A, action: |SocketAddr| -> IoResult<T>) -> IoResult<T> { const DEFAULT_ERROR: IoError = IoError { diff --git a/src/libstd/io/net/pipe.rs b/src/libstd/io/net/pipe.rs index 8c7deadebea..111b0f2b081 100644 --- a/src/libstd/io/net/pipe.rs +++ b/src/libstd/io/net/pipe.rs @@ -26,17 +26,20 @@ instances as clients. use prelude::*; -use io::{Listener, Acceptor, IoResult, IoError, TimedOut, standard_error}; -use rt::rtio::{IoFactory, LocalIo, RtioUnixListener}; -use rt::rtio::{RtioUnixAcceptor, RtioPipe}; +use io::{Listener, Acceptor, IoResult, TimedOut, standard_error}; use time::Duration; +use sys::pipe::UnixStream as UnixStreamImp; +use sys::pipe::UnixListener as UnixListenerImp; +use sys::pipe::UnixAcceptor as UnixAcceptorImp; + /// A stream which communicates over a named pipe. pub struct UnixStream { - obj: Box<RtioPipe + Send>, + inner: UnixStreamImp, } impl UnixStream { + /// Connect to a pipe named by `path`. This will attempt to open a /// connection to the underlying socket. /// @@ -53,9 +56,8 @@ impl UnixStream { /// stream.write([1, 2, 3]); /// ``` pub fn connect<P: ToCStr>(path: &P) -> IoResult<UnixStream> { - LocalIo::maybe_raise(|io| { - io.unix_connect(&path.to_c_str(), None).map(|p| UnixStream { obj: p }) - }).map_err(IoError::from_rtio_error) + UnixStreamImp::connect(&path.to_c_str(), None) + .map(|inner| UnixStream { inner: inner }) } /// Connect to a pipe named by `path`, timing out if the specified number of @@ -73,10 +75,8 @@ impl UnixStream { return Err(standard_error(TimedOut)); } - LocalIo::maybe_raise(|io| { - let s = io.unix_connect(&path.to_c_str(), Some(timeout.num_milliseconds() as u64)); - s.map(|p| UnixStream { obj: p }) - }).map_err(IoError::from_rtio_error) + UnixStreamImp::connect(&path.to_c_str(), Some(timeout.num_milliseconds() as u64)) + .map(|inner| UnixStream { inner: inner }) } @@ -88,7 +88,7 @@ impl UnixStream { /// Note that this method affects all cloned handles associated with this /// stream, not just this one handle. pub fn close_read(&mut self) -> IoResult<()> { - self.obj.close_read().map_err(IoError::from_rtio_error) + self.inner.close_read() } /// Closes the writing half of this connection. @@ -99,7 +99,7 @@ impl UnixStream { /// Note that this method affects all cloned handles associated with this /// stream, not just this one handle. pub fn close_write(&mut self) -> IoResult<()> { - self.obj.close_write().map_err(IoError::from_rtio_error) + self.inner.close_write() } /// Sets the read/write timeout for this socket. @@ -107,7 +107,7 @@ impl UnixStream { /// For more information, see `TcpStream::set_timeout` #[experimental = "the timeout argument may change in type and value"] pub fn set_timeout(&mut self, timeout_ms: Option<u64>) { - self.obj.set_timeout(timeout_ms) + self.inner.set_timeout(timeout_ms) } /// Sets the read timeout for this socket. @@ -115,7 +115,7 @@ impl UnixStream { /// For more information, see `TcpStream::set_timeout` #[experimental = "the timeout argument may change in type and value"] pub fn set_read_timeout(&mut self, timeout_ms: Option<u64>) { - self.obj.set_read_timeout(timeout_ms) + self.inner.set_read_timeout(timeout_ms) } /// Sets the write timeout for this socket. @@ -123,36 +123,35 @@ impl UnixStream { /// For more information, see `TcpStream::set_timeout` #[experimental = "the timeout argument may change in type and value"] pub fn set_write_timeout(&mut self, timeout_ms: Option<u64>) { - self.obj.set_write_timeout(timeout_ms) + self.inner.set_write_timeout(timeout_ms) } } impl Clone for UnixStream { fn clone(&self) -> UnixStream { - UnixStream { obj: self.obj.clone() } + UnixStream { inner: self.inner.clone() } } } impl Reader for UnixStream { fn read(&mut self, buf: &mut [u8]) -> IoResult<uint> { - self.obj.read(buf).map_err(IoError::from_rtio_error) + self.inner.read(buf) } } impl Writer for UnixStream { fn write(&mut self, buf: &[u8]) -> IoResult<()> { - self.obj.write(buf).map_err(IoError::from_rtio_error) + self.inner.write(buf) } } /// A value that can listen for incoming named pipe connection requests. pub struct UnixListener { /// The internal, opaque runtime Unix listener. - obj: Box<RtioUnixListener + Send>, + inner: UnixListenerImp, } impl UnixListener { - /// Creates a new listener, ready to receive incoming connections on the /// specified socket. The server will be named by `path`. /// @@ -175,24 +174,22 @@ impl UnixListener { /// # } /// ``` pub fn bind<P: ToCStr>(path: &P) -> IoResult<UnixListener> { - LocalIo::maybe_raise(|io| { - io.unix_bind(&path.to_c_str()).map(|s| UnixListener { obj: s }) - }).map_err(IoError::from_rtio_error) + UnixListenerImp::bind(&path.to_c_str()) + .map(|inner| UnixListener { inner: inner }) } } impl Listener<UnixStream, UnixAcceptor> for UnixListener { fn listen(self) -> IoResult<UnixAcceptor> { - self.obj.listen().map(|obj| { - UnixAcceptor { obj: obj } - }).map_err(IoError::from_rtio_error) + self.inner.listen() + .map(|inner| UnixAcceptor { inner: inner }) } } /// A value that can accept named pipe connections, returned from `listen()`. pub struct UnixAcceptor { /// The internal, opaque runtime Unix acceptor. - obj: Box<RtioUnixAcceptor + Send>, + inner: UnixAcceptorImp } impl UnixAcceptor { @@ -210,7 +207,7 @@ impl UnixAcceptor { #[experimental = "the name and arguments to this function are likely \ to change"] pub fn set_timeout(&mut self, timeout_ms: Option<u64>) { - self.obj.set_timeout(timeout_ms) + self.inner.set_timeout(timeout_ms) } /// Closes the accepting capabilities of this acceptor. @@ -219,15 +216,15 @@ impl UnixAcceptor { /// more information can be found in that documentation. #[experimental] pub fn close_accept(&mut self) -> IoResult<()> { - self.obj.close_accept().map_err(IoError::from_rtio_error) + self.inner.close_accept() } } impl Acceptor<UnixStream> for UnixAcceptor { fn accept(&mut self) -> IoResult<UnixStream> { - self.obj.accept().map(|s| { - UnixStream { obj: s } - }).map_err(IoError::from_rtio_error) + self.inner.accept().map(|s| { + UnixStream { inner: s } + }) } } @@ -246,7 +243,7 @@ impl Clone for UnixAcceptor { /// This function is useful for creating a handle to invoke `close_accept` /// on to wake up any other task blocked in `accept`. fn clone(&self) -> UnixAcceptor { - UnixAcceptor { obj: self.obj.clone() } + UnixAcceptor { inner: self.inner.clone() } } } diff --git a/src/libstd/io/net/tcp.rs b/src/libstd/io/net/tcp.rs index 928c8586739..2545e07cbb5 100644 --- a/src/libstd/io/net/tcp.rs +++ b/src/libstd/io/net/tcp.rs @@ -20,19 +20,17 @@ use clone::Clone; use io::IoResult; use iter::Iterator; -use result::{Ok,Err}; +use result::Err; use io::net::ip::{SocketAddr, ToSocketAddr}; -use io::IoError; use io::{Reader, Writer, Listener, Acceptor}; use io::{standard_error, TimedOut}; -use kinds::Send; use option::{None, Some, Option}; -use boxed::Box; -use rt::rtio::{IoFactory, RtioSocket, RtioTcpListener}; -use rt::rtio::{RtioTcpAcceptor, RtioTcpStream}; -use rt::rtio; use time::Duration; +use sys::tcp::TcpStream as TcpStreamImp; +use sys::tcp::TcpListener as TcpListenerImp; +use sys::tcp::TcpAcceptor as TcpAcceptorImp; + /// A structure which represents a TCP stream between a local socket and a /// remote socket. /// @@ -50,12 +48,12 @@ use time::Duration; /// drop(stream); // close the connection /// ``` pub struct TcpStream { - obj: Box<RtioTcpStream + Send>, + inner: TcpStreamImp, } impl TcpStream { - fn new(s: Box<RtioTcpStream + Send>) -> TcpStream { - TcpStream { obj: s } + fn new(s: TcpStreamImp) -> TcpStream { + TcpStream { inner: s } } /// Open a TCP connection to a remote host. @@ -64,7 +62,9 @@ impl TcpStream { /// trait can be supplied for the address; see this trait documentation for /// concrete examples. pub fn connect<A: ToSocketAddr>(addr: A) -> IoResult<TcpStream> { - super::with_addresses_io(addr, |io, addr| io.tcp_connect(addr, None).map(TcpStream::new)) + super::with_addresses(addr, |addr| { + TcpStreamImp::connect(addr, None).map(TcpStream::new) + }) } /// Creates a TCP connection to a remote socket address, timing out after @@ -86,39 +86,26 @@ impl TcpStream { return Err(standard_error(TimedOut)); } - super::with_addresses_io(addr, |io, addr| - io.tcp_connect(addr, Some(timeout.num_milliseconds() as u64)).map(TcpStream::new) - ) + super::with_addresses(addr, |addr| { + TcpStreamImp::connect(addr, Some(timeout.num_milliseconds() as u64)) + .map(TcpStream::new) + }) } /// Returns the socket address of the remote peer of this TCP connection. pub fn peer_name(&mut self) -> IoResult<SocketAddr> { - match self.obj.peer_name() { - Ok(rtio::SocketAddr { ip, port }) => { - Ok(SocketAddr { ip: super::from_rtio(ip), port: port }) - } - Err(e) => Err(IoError::from_rtio_error(e)), - } + self.inner.peer_name() } /// Returns the socket address of the local half of this TCP connection. pub fn socket_name(&mut self) -> IoResult<SocketAddr> { - match self.obj.socket_name() { - Ok(rtio::SocketAddr { ip, port }) => { - Ok(SocketAddr { ip: super::from_rtio(ip), port: port }) - } - Err(e) => Err(IoError::from_rtio_error(e)), - } + self.inner.socket_name() } /// Sets the nodelay flag on this connection to the boolean specified #[experimental] pub fn set_nodelay(&mut self, nodelay: bool) -> IoResult<()> { - if nodelay { - self.obj.nodelay() - } else { - self.obj.control_congestion() - }.map_err(IoError::from_rtio_error) + self.inner.set_nodelay(nodelay) } /// Sets the keepalive timeout to the timeout specified. @@ -128,10 +115,7 @@ impl TcpStream { /// specified time, in seconds. #[experimental] pub fn set_keepalive(&mut self, delay_in_seconds: Option<uint>) -> IoResult<()> { - match delay_in_seconds { - Some(i) => self.obj.keepalive(i), - None => self.obj.letdie(), - }.map_err(IoError::from_rtio_error) + self.inner.set_keepalive(delay_in_seconds) } /// Closes the reading half of this connection. @@ -165,7 +149,7 @@ impl TcpStream { /// Note that this method affects all cloned handles associated with this /// stream, not just this one handle. pub fn close_read(&mut self) -> IoResult<()> { - self.obj.close_read().map_err(IoError::from_rtio_error) + self.inner.close_read() } /// Closes the writing half of this connection. @@ -176,7 +160,7 @@ impl TcpStream { /// Note that this method affects all cloned handles associated with this /// stream, not just this one handle. pub fn close_write(&mut self) -> IoResult<()> { - self.obj.close_write().map_err(IoError::from_rtio_error) + self.inner.close_write() } /// Sets a timeout, in milliseconds, for blocking operations on this stream. @@ -198,7 +182,7 @@ impl TcpStream { /// take a look at `set_read_timeout` and `set_write_timeout`. #[experimental = "the timeout argument may change in type and value"] pub fn set_timeout(&mut self, timeout_ms: Option<u64>) { - self.obj.set_timeout(timeout_ms) + self.inner.set_timeout(timeout_ms) } /// Sets the timeout for read operations on this stream. @@ -215,7 +199,7 @@ impl TcpStream { /// during the timeout period. #[experimental = "the timeout argument may change in type and value"] pub fn set_read_timeout(&mut self, timeout_ms: Option<u64>) { - self.obj.set_read_timeout(timeout_ms) + self.inner.set_read_timeout(timeout_ms) } /// Sets the timeout for write operations on this stream. @@ -242,7 +226,7 @@ impl TcpStream { /// asynchronous fashion after the call to write returns. #[experimental = "the timeout argument may change in type and value"] pub fn set_write_timeout(&mut self, timeout_ms: Option<u64>) { - self.obj.set_write_timeout(timeout_ms) + self.inner.set_write_timeout(timeout_ms) } } @@ -256,19 +240,19 @@ impl Clone for TcpStream { /// Instead, the first read will receive the first packet received, and the /// second read will receive the second packet. fn clone(&self) -> TcpStream { - TcpStream { obj: self.obj.clone() } + TcpStream { inner: self.inner.clone() } } } impl Reader for TcpStream { fn read(&mut self, buf: &mut [u8]) -> IoResult<uint> { - self.obj.read(buf).map_err(IoError::from_rtio_error) + self.inner.read(buf) } } impl Writer for TcpStream { fn write(&mut self, buf: &[u8]) -> IoResult<()> { - self.obj.write(buf).map_err(IoError::from_rtio_error) + self.inner.write(buf) } } @@ -309,7 +293,7 @@ impl Writer for TcpStream { /// # } /// ``` pub struct TcpListener { - obj: Box<RtioTcpListener + Send>, + inner: TcpListenerImp, } impl TcpListener { @@ -324,26 +308,20 @@ impl TcpListener { /// The address type can be any implementor of `ToSocketAddr` trait. See its /// documentation for concrete examples. pub fn bind<A: ToSocketAddr>(addr: A) -> IoResult<TcpListener> { - super::with_addresses_io(addr, |io, addr| io.tcp_bind(addr).map(|l| TcpListener { obj: l })) + super::with_addresses(addr, |addr| { + TcpListenerImp::bind(addr).map(|inner| TcpListener { inner: inner }) + }) } /// Returns the local socket address of this listener. pub fn socket_name(&mut self) -> IoResult<SocketAddr> { - match self.obj.socket_name() { - Ok(rtio::SocketAddr { ip, port }) => { - Ok(SocketAddr { ip: super::from_rtio(ip), port: port }) - } - Err(e) => Err(IoError::from_rtio_error(e)), - } + self.inner.socket_name() } } impl Listener<TcpStream, TcpAcceptor> for TcpListener { fn listen(self) -> IoResult<TcpAcceptor> { - match self.obj.listen() { - Ok(acceptor) => Ok(TcpAcceptor { obj: acceptor }), - Err(e) => Err(IoError::from_rtio_error(e)), - } + self.inner.listen(128).map(|a| TcpAcceptor { inner: a }) } } @@ -351,7 +329,7 @@ impl Listener<TcpStream, TcpAcceptor> for TcpListener { /// a `TcpListener`'s `listen` method, and this object can be used to accept new /// `TcpStream` instances. pub struct TcpAcceptor { - obj: Box<RtioTcpAcceptor + Send>, + inner: TcpAcceptorImp, } impl TcpAcceptor { @@ -399,7 +377,7 @@ impl TcpAcceptor { /// ``` #[experimental = "the type of the argument and name of this function are \ subject to change"] - pub fn set_timeout(&mut self, ms: Option<u64>) { self.obj.set_timeout(ms); } + pub fn set_timeout(&mut self, ms: Option<u64>) { self.inner.set_timeout(ms); } /// Closes the accepting capabilities of this acceptor. /// @@ -445,16 +423,13 @@ impl TcpAcceptor { /// ``` #[experimental] pub fn close_accept(&mut self) -> IoResult<()> { - self.obj.close_accept().map_err(IoError::from_rtio_error) + self.inner.close_accept() } } impl Acceptor<TcpStream> for TcpAcceptor { fn accept(&mut self) -> IoResult<TcpStream> { - match self.obj.accept(){ - Ok(s) => Ok(TcpStream::new(s)), - Err(e) => Err(IoError::from_rtio_error(e)), - } + self.inner.accept().map(TcpStream::new) } } @@ -473,7 +448,7 @@ impl Clone for TcpAcceptor { /// This function is useful for creating a handle to invoke `close_accept` /// on to wake up any other task blocked in `accept`. fn clone(&self) -> TcpAcceptor { - TcpAcceptor { obj: self.obj.clone() } + TcpAcceptor { inner: self.inner.clone() } } } @@ -1112,8 +1087,6 @@ mod test { #[test] fn shutdown_smoke() { - use rt::rtio::RtioTcpStream; - let addr = next_test_ip4(); let a = TcpListener::bind(addr).unwrap().listen(); spawn(proc() { @@ -1124,7 +1097,7 @@ mod test { }); let mut s = TcpStream::connect(addr).unwrap(); - assert!(s.obj.close_write().is_ok()); + assert!(s.inner.close_write().is_ok()); assert!(s.write([1]).is_err()); assert_eq!(s.read_to_end(), Ok(vec!(1))); } diff --git a/src/libstd/io/net/udp.rs b/src/libstd/io/net/udp.rs index 4ae054beadb..31b61989647 100644 --- a/src/libstd/io/net/udp.rs +++ b/src/libstd/io/net/udp.rs @@ -17,13 +17,10 @@ use clone::Clone; use io::net::ip::{SocketAddr, IpAddr, ToSocketAddr}; -use io::{Reader, Writer, IoResult, IoError}; -use kinds::Send; -use boxed::Box; +use io::{Reader, Writer, IoResult}; use option::Option; use result::{Ok, Err}; -use rt::rtio::{RtioSocket, RtioUdpSocket, IoFactory}; -use rt::rtio; +use sys::udp::UdpSocket as UdpSocketImp; /// A User Datagram Protocol socket. /// @@ -60,7 +57,7 @@ use rt::rtio; /// } /// ``` pub struct UdpSocket { - obj: Box<RtioUdpSocket + Send>, + inner: UdpSocketImp, } impl UdpSocket { @@ -69,18 +66,15 @@ impl UdpSocket { /// Address type can be any implementor of `ToSocketAddr` trait. See its /// documentation for concrete examples. pub fn bind<A: ToSocketAddr>(addr: A) -> IoResult<UdpSocket> { - super::with_addresses_io(addr, |io, addr| io.udp_bind(addr).map(|s| UdpSocket { obj: s })) + super::with_addresses(addr, |addr| { + UdpSocketImp::bind(addr).map(|s| UdpSocket { inner: s }) + }) } /// Receives data from the socket. On success, returns the number of bytes /// read and the address from whence the data came. pub fn recv_from(&mut self, buf: &mut [u8]) -> IoResult<(uint, SocketAddr)> { - match self.obj.recv_from(buf) { - Ok((amt, rtio::SocketAddr { ip, port })) => { - Ok((amt, SocketAddr { ip: super::from_rtio(ip), port: port })) - } - Err(e) => Err(IoError::from_rtio_error(e)), - } + self.inner.recv_from(buf) } /// Sends data on the socket to the given address. Returns nothing on @@ -89,10 +83,7 @@ impl UdpSocket { /// Address type can be any implementor of `ToSocketAddr` trait. See its /// documentation for concrete examples. pub fn send_to<A: ToSocketAddr>(&mut self, buf: &[u8], addr: A) -> IoResult<()> { - super::with_addresses(addr, |addr| self.obj.send_to(buf, rtio::SocketAddr { - ip: super::to_rtio(addr.ip), - port: addr.port, - }).map_err(IoError::from_rtio_error)) + super::with_addresses(addr, |addr| self.inner.send_to(buf, addr)) } /// Creates a `UdpStream`, which allows use of the `Reader` and `Writer` @@ -112,24 +103,19 @@ impl UdpSocket { /// Returns the socket address that this socket was created from. pub fn socket_name(&mut self) -> IoResult<SocketAddr> { - match self.obj.socket_name() { - Ok(a) => Ok(SocketAddr { ip: super::from_rtio(a.ip), port: a.port }), - Err(e) => Err(IoError::from_rtio_error(e)) - } + self.inner.socket_name() } /// Joins a multicast IP address (becomes a member of it) #[experimental] pub fn join_multicast(&mut self, multi: IpAddr) -> IoResult<()> { - let e = self.obj.join_multicast(super::to_rtio(multi)); - e.map_err(IoError::from_rtio_error) + self.inner.join_multicast(multi) } /// Leaves a multicast IP address (drops membership from it) #[experimental] pub fn leave_multicast(&mut self, multi: IpAddr) -> IoResult<()> { - let e = self.obj.leave_multicast(super::to_rtio(multi)); - e.map_err(IoError::from_rtio_error) + self.inner.leave_multicast(multi) } /// Set the multicast loop flag to the specified value @@ -137,33 +123,25 @@ impl UdpSocket { /// This lets multicast packets loop back to local sockets (if enabled) #[experimental] pub fn set_multicast_loop(&mut self, on: bool) -> IoResult<()> { - if on { - self.obj.loop_multicast_locally() - } else { - self.obj.dont_loop_multicast_locally() - }.map_err(IoError::from_rtio_error) + self.inner.set_multicast_loop(on) } /// Sets the multicast TTL #[experimental] pub fn set_multicast_ttl(&mut self, ttl: int) -> IoResult<()> { - self.obj.multicast_time_to_live(ttl).map_err(IoError::from_rtio_error) + self.inner.multicast_time_to_live(ttl) } /// Sets this socket's TTL #[experimental] pub fn set_ttl(&mut self, ttl: int) -> IoResult<()> { - self.obj.time_to_live(ttl).map_err(IoError::from_rtio_error) + self.inner.time_to_live(ttl) } /// Sets the broadcast flag on or off #[experimental] pub fn set_broadcast(&mut self, broadcast: bool) -> IoResult<()> { - if broadcast { - self.obj.hear_broadcasts() - } else { - self.obj.ignore_broadcasts() - }.map_err(IoError::from_rtio_error) + self.inner.set_broadcast(broadcast) } /// Sets the read/write timeout for this socket. @@ -171,7 +149,7 @@ impl UdpSocket { /// For more information, see `TcpStream::set_timeout` #[experimental = "the timeout argument may change in type and value"] pub fn set_timeout(&mut self, timeout_ms: Option<u64>) { - self.obj.set_timeout(timeout_ms) + self.inner.set_timeout(timeout_ms) } /// Sets the read timeout for this socket. @@ -179,7 +157,7 @@ impl UdpSocket { /// For more information, see `TcpStream::set_timeout` #[experimental = "the timeout argument may change in type and value"] pub fn set_read_timeout(&mut self, timeout_ms: Option<u64>) { - self.obj.set_read_timeout(timeout_ms) + self.inner.set_read_timeout(timeout_ms) } /// Sets the write timeout for this socket. @@ -187,7 +165,7 @@ impl UdpSocket { /// For more information, see `TcpStream::set_timeout` #[experimental = "the timeout argument may change in type and value"] pub fn set_write_timeout(&mut self, timeout_ms: Option<u64>) { - self.obj.set_write_timeout(timeout_ms) + self.inner.set_write_timeout(timeout_ms) } } @@ -201,7 +179,7 @@ impl Clone for UdpSocket { /// received, and the second read will receive the second packet. fn clone(&self) -> UdpSocket { UdpSocket { - obj: self.obj.clone(), + inner: self.inner.clone(), } } } diff --git a/src/libstd/io/pipe.rs b/src/libstd/io/pipe.rs index c77cffd561e..64b2518fab1 100644 --- a/src/libstd/io/pipe.rs +++ b/src/libstd/io/pipe.rs @@ -17,15 +17,17 @@ use prelude::*; -use io::{IoResult, IoError}; +use io::IoResult; use libc; -use os; -use rt::rtio::{RtioPipe, LocalIo}; +use sync::Arc; + +use sys_common; +use sys; +use sys::fs::FileDesc as FileDesc; /// A synchronous, in-memory pipe. pub struct PipeStream { - /// The internal, opaque runtime pipe object. - obj: Box<RtioPipe + Send>, + inner: Arc<FileDesc> } pub struct PipePair { @@ -55,14 +57,14 @@ impl PipeStream { /// } /// ``` pub fn open(fd: libc::c_int) -> IoResult<PipeStream> { - LocalIo::maybe_raise(|io| { - io.pipe_open(fd).map(|obj| PipeStream { obj: obj }) - }).map_err(IoError::from_rtio_error) + Ok(PipeStream::from_filedesc(FileDesc::new(fd, true))) } + // FIXME: expose this some other way + /// Wrap a FileDesc directly, taking ownership. #[doc(hidden)] - pub fn new(inner: Box<RtioPipe + Send>) -> PipeStream { - PipeStream { obj: inner } + pub fn from_filedesc(fd: FileDesc) -> PipeStream { + PipeStream { inner: Arc::new(fd) } } /// Creates a pair of in-memory OS pipes for a unidirectional communication @@ -76,43 +78,35 @@ impl PipeStream { /// This function can fail to succeed if the underlying OS has run out of /// available resources to allocate a new pipe. pub fn pair() -> IoResult<PipePair> { - struct Closer { fd: libc::c_int } - - let os::Pipe { reader, writer } = try!(unsafe { os::pipe() }); - let mut reader = Closer { fd: reader }; - let mut writer = Closer { fd: writer }; - - let io_reader = try!(PipeStream::open(reader.fd)); - reader.fd = -1; - let io_writer = try!(PipeStream::open(writer.fd)); - writer.fd = -1; - return Ok(PipePair { reader: io_reader, writer: io_writer }); - - impl Drop for Closer { - fn drop(&mut self) { - if self.fd != -1 { - let _ = unsafe { libc::close(self.fd) }; - } - } - } + let (reader, writer) = try!(unsafe { sys::os::pipe() }); + Ok(PipePair { + reader: PipeStream::from_filedesc(reader), + writer: PipeStream::from_filedesc(writer), + }) + } +} + +impl sys_common::AsFileDesc for PipeStream { + fn as_fd(&self) -> &sys::fs::FileDesc { + &*self.inner } } impl Clone for PipeStream { fn clone(&self) -> PipeStream { - PipeStream { obj: self.obj.clone() } + PipeStream { inner: self.inner.clone() } } } impl Reader for PipeStream { fn read(&mut self, buf: &mut [u8]) -> IoResult<uint> { - self.obj.read(buf).map_err(IoError::from_rtio_error) + self.inner.read(buf) } } impl Writer for PipeStream { fn write(&mut self, buf: &[u8]) -> IoResult<()> { - self.obj.write(buf).map_err(IoError::from_rtio_error) + self.inner.write(buf) } } diff --git a/src/libstd/io/process.rs b/src/libstd/io/process.rs index 698e0a3460f..d71bab0b48f 100644 --- a/src/libstd/io/process.rs +++ b/src/libstd/io/process.rs @@ -20,14 +20,17 @@ use os; use io::{IoResult, IoError}; use io; use libc; -use mem; -use rt::rtio::{RtioProcess, ProcessConfig, IoFactory, LocalIo}; -use rt::rtio; use c_str::CString; use collections::HashMap; use hash::Hash; #[cfg(windows)] use std::hash::sip::SipState; +use io::pipe::{PipeStream, PipePair}; +use path::BytesContainer; + +use sys; +use sys::fs::FileDesc; +use sys::process::Process as ProcessImp; /// Signal a process to exit, without forcibly killing it. Corresponds to /// SIGTERM on unix platforms. @@ -62,24 +65,29 @@ use std::hash::sip::SipState; /// assert!(child.wait().unwrap().success()); /// ``` pub struct Process { - handle: Box<RtioProcess + Send>, + handle: ProcessImp, forget: bool, + /// None until wait() is called. + exit_code: Option<ProcessExit>, + + /// Manually delivered signal + exit_signal: Option<int>, + + /// Deadline after which wait() will return + deadline: u64, + /// Handle to the child's stdin, if the `stdin` field of this process's /// `ProcessConfig` was `CreatePipe`. By default, this handle is `Some`. - pub stdin: Option<io::PipeStream>, + pub stdin: Option<PipeStream>, /// Handle to the child's stdout, if the `stdout` field of this process's /// `ProcessConfig` was `CreatePipe`. By default, this handle is `Some`. - pub stdout: Option<io::PipeStream>, + pub stdout: Option<PipeStream>, /// Handle to the child's stderr, if the `stderr` field of this process's /// `ProcessConfig` was `CreatePipe`. By default, this handle is `Some`. - pub stderr: Option<io::PipeStream>, - - /// Extra I/O handles as configured by the original `ProcessConfig` when - /// this process was created. This is by default empty. - pub extra_io: Vec<Option<io::PipeStream>>, + pub stderr: Option<PipeStream>, } /// A representation of environment variable name @@ -130,6 +138,13 @@ impl PartialEq for EnvKey { } } +impl BytesContainer for EnvKey { + fn container_as_bytes<'a>(&'a self) -> &'a [u8] { + let &EnvKey(ref k) = self; + k.container_as_bytes() + } +} + /// A HashMap representation of environment variables. pub type EnvMap = HashMap<EnvKey, CString>; @@ -160,7 +175,6 @@ pub struct Command { stdin: StdioContainer, stdout: StdioContainer, stderr: StdioContainer, - extra_io: Vec<StdioContainer>, uid: Option<uint>, gid: Option<uint>, detach: bool, @@ -194,7 +208,6 @@ impl Command { stdin: CreatePipe(true, false), stdout: CreatePipe(false, true), stderr: CreatePipe(false, true), - extra_io: Vec::new(), uid: None, gid: None, detach: false, @@ -281,14 +294,6 @@ impl Command { self.stderr = cfg; self } - /// Attaches a stream/file descriptor/pipe to the child process. Inherited - /// file descriptors are numbered consecutively, starting at 3; the first - /// three file descriptors (stdin/stdout/stderr) are configured with the - /// `stdin`, `stdout`, and `stderr` methods. - pub fn extra_io<'a>(&'a mut self, cfg: StdioContainer) -> &'a mut Command { - self.extra_io.push(cfg); - self - } /// Sets the child process's user id. This translates to a `setuid` call in /// the child process. Setting this value on windows will cause the spawn to @@ -315,50 +320,23 @@ impl Command { /// Executes the command as a child process, which is returned. pub fn spawn(&self) -> IoResult<Process> { - fn to_rtio(p: StdioContainer) -> rtio::StdioContainer { - match p { - Ignored => rtio::Ignored, - InheritFd(fd) => rtio::InheritFd(fd), - CreatePipe(a, b) => rtio::CreatePipe(a, b), - } - } - let extra_io: Vec<rtio::StdioContainer> = - self.extra_io.iter().map(|x| to_rtio(*x)).collect(); - LocalIo::maybe_raise(|io| { - let env = match self.env { - None => None, - Some(ref env_map) => - Some(env_map.iter() - .map(|(&EnvKey(ref key), val)| (key, val)) - .collect::<Vec<_>>()) - }; - let cfg = ProcessConfig { - program: &self.program, - args: self.args.as_slice(), - env: env.as_ref().map(|e| e.as_slice()), - cwd: self.cwd.as_ref(), - stdin: to_rtio(self.stdin), - stdout: to_rtio(self.stdout), - stderr: to_rtio(self.stderr), - extra_io: extra_io.as_slice(), - uid: self.uid, - gid: self.gid, - detach: self.detach, - }; - io.spawn(cfg).map(|(p, io)| { - let mut io = io.into_iter().map(|p| { - p.map(|p| io::PipeStream::new(p)) - }); - Process { - handle: p, - forget: false, - stdin: io.next().unwrap(), - stdout: io.next().unwrap(), - stderr: io.next().unwrap(), - extra_io: io.collect(), - } + let (their_stdin, our_stdin) = try!(setup_io(self.stdin)); + let (their_stdout, our_stdout) = try!(setup_io(self.stdout)); + let (their_stderr, our_stderr) = try!(setup_io(self.stderr)); + + match ProcessImp::spawn(self, their_stdin, their_stdout, their_stderr) { + Err(e) => Err(e), + Ok(handle) => Ok(Process { + handle: handle, + forget: false, + exit_code: None, + exit_signal: None, + deadline: 0, + stdin: our_stdin, + stdout: our_stdout, + stderr: our_stderr, }) - }).map_err(IoError::from_rtio_error) + } } /// Executes the command as a child process, waiting for it to finish and @@ -415,6 +393,58 @@ impl fmt::Show for Command { } } +fn setup_io(io: StdioContainer) -> IoResult<(Option<PipeStream>, Option<PipeStream>)> { + let ours; + let theirs; + match io { + Ignored => { + theirs = None; + ours = None; + } + InheritFd(fd) => { + theirs = Some(PipeStream::from_filedesc(FileDesc::new(fd, false))); + ours = None; + } + CreatePipe(readable, _writable) => { + let PipePair { reader, writer } = try!(PipeStream::pair()); + if readable { + theirs = Some(reader); + ours = Some(writer); + } else { + theirs = Some(writer); + ours = Some(reader); + } + } + } + Ok((theirs, ours)) +} + +// Allow the sys module to get access to the Command state +impl sys::process::ProcessConfig<EnvKey, CString> for Command { + fn program(&self) -> &CString { + &self.program + } + fn args(&self) -> &[CString] { + self.args.as_slice() + } + fn env(&self) -> Option<&EnvMap> { + self.env.as_ref() + } + fn cwd(&self) -> Option<&CString> { + self.cwd.as_ref() + } + fn uid(&self) -> Option<uint> { + self.uid.clone() + } + fn gid(&self) -> Option<uint> { + self.gid.clone() + } + fn detach(&self) -> bool { + self.detach + } + +} + /// The output of a finished process. #[deriving(PartialEq, Eq, Clone)] pub struct ProcessOutput { @@ -494,9 +524,7 @@ impl Process { /// be successfully delivered if the child has exited, but not yet been /// reaped. pub fn kill(id: libc::pid_t, signal: int) -> IoResult<()> { - LocalIo::maybe_raise(|io| { - io.kill(id, signal) - }).map_err(IoError::from_rtio_error) + unsafe { ProcessImp::killpid(id, signal) } } /// Returns the process id of this child process @@ -518,7 +546,42 @@ impl Process { /// /// If the signal delivery fails, the corresponding error is returned. pub fn signal(&mut self, signal: int) -> IoResult<()> { - self.handle.kill(signal).map_err(IoError::from_rtio_error) + #[cfg(unix)] fn collect_status(p: &mut Process) { + // On Linux (and possibly other unices), a process that has exited will + // continue to accept signals because it is "defunct". The delivery of + // signals will only fail once the child has been reaped. For this + // reason, if the process hasn't exited yet, then we attempt to collect + // their status with WNOHANG. + if p.exit_code.is_none() { + match p.handle.try_wait() { + Some(code) => { p.exit_code = Some(code); } + None => {} + } + } + } + #[cfg(windows)] fn collect_status(_p: &mut Process) {} + + collect_status(self); + + // 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 + if self.exit_code.is_some() { + return Err(IoError { + kind: io::InvalidInput, + desc: "invalid argument: can't kill an exited process", + detail: None, + }) + } + + // A successfully delivered signal that isn't 0 (just a poll for being + // alive) is recorded for windows (see wait()) + match unsafe { self.handle.kill(signal) } { + Ok(()) if signal == 0 => Ok(()), + Ok(()) => { self.exit_signal = Some(signal); Ok(()) } + Err(e) => Err(e), + } + } /// Sends a signal to this child requesting that it exits. This is @@ -545,10 +608,21 @@ impl Process { /// `set_timeout` and the timeout expires before the child exits. pub fn wait(&mut self) -> IoResult<ProcessExit> { drop(self.stdin.take()); - match self.handle.wait() { - Ok(rtio::ExitSignal(s)) => Ok(ExitSignal(s)), - Ok(rtio::ExitStatus(s)) => Ok(ExitStatus(s)), - Err(e) => Err(IoError::from_rtio_error(e)), + match self.exit_code { + Some(code) => Ok(code), + None => { + let code = try!(self.handle.wait(self.deadline)); + // On windows, waitpid will never return a signal. If a signal + // was successfully delivered to the process, however, we can + // consider it as having died via a signal. + let code = match self.exit_signal { + None => code, + Some(signal) if cfg!(windows) => ExitSignal(signal), + Some(..) => code, + }; + self.exit_code = Some(code); + Ok(code) + } } } @@ -594,7 +668,7 @@ impl Process { /// ``` #[experimental = "the type of the timeout is likely to change"] pub fn set_timeout(&mut self, timeout_ms: Option<u64>) { - self.handle.set_timeout(timeout_ms) + self.deadline = timeout_ms.map(|i| i + sys::timer::now()).unwrap_or(0); } /// Simultaneously wait for the child to exit and collect all remaining @@ -653,7 +727,6 @@ impl Drop for Process { drop(self.stdin.take()); drop(self.stdout.take()); drop(self.stderr.take()); - drop(mem::replace(&mut self.extra_io, Vec::new())); self.set_timeout(None); let _ = self.wait().unwrap(); @@ -1109,8 +1182,7 @@ mod tests { #[test] fn dont_close_fd_on_command_spawn() { - use std::rt::rtio::{Truncate, Write}; - use self::native::io::file; + use sys::fs; let path = if cfg!(windows) { Path::new("NUL") @@ -1118,7 +1190,7 @@ mod tests { Path::new("/dev/null") }; - let mut fdes = match file::open(&path.to_c_str(), Truncate, Write) { + let mut fdes = match fs::open(&path, Truncate, Write) { Ok(f) => f, Err(_) => panic!("failed to open file descriptor"), }; @@ -1126,7 +1198,7 @@ mod tests { let mut cmd = pwd_cmd(); let _ = cmd.stdout(InheritFd(fdes.fd())); assert!(cmd.status().unwrap().success()); - assert!(fdes.inner_write("extra write\n".as_bytes()).is_ok()); + assert!(fdes.write("extra write\n".as_bytes()).is_ok()); } #[test] diff --git a/src/libstd/io/stdio.rs b/src/libstd/io/stdio.rs index 7bae67c0aa6..158d596ea13 100644 --- a/src/libstd/io/stdio.rs +++ b/src/libstd/io/stdio.rs @@ -36,11 +36,11 @@ use kinds::Send; use libc; use option::{Option, Some, None}; use boxed::Box; +use sys::{fs, tty}; use result::{Ok, Err}; use rt; use rt::local::Local; use rt::task::Task; -use rt::rtio::{DontClose, IoFactory, LocalIo, RtioFileStream, RtioTTY}; use slice::SlicePrelude; use str::StrPrelude; use uint; @@ -74,17 +74,15 @@ use uint; // tl;dr; TTY works on everything but when windows stdout is redirected, in that // case pipe also doesn't work, but magically file does! enum StdSource { - TTY(Box<RtioTTY + Send>), - File(Box<RtioFileStream + Send>), + TTY(tty::TTY), + File(fs::FileDesc), } -fn src<T>(fd: libc::c_int, readable: bool, f: |StdSource| -> T) -> T { - LocalIo::maybe_raise(|io| { - Ok(match io.tty_open(fd, readable) { - Ok(tty) => f(TTY(tty)), - Err(_) => f(File(io.fs_from_raw_fd(fd, DontClose))), - }) - }).map_err(IoError::from_rtio_error).unwrap() +fn src<T>(fd: libc::c_int, _readable: bool, f: |StdSource| -> T) -> T { + match tty::TTY::new(fd) { + Ok(tty) => f(TTY(tty)), + Err(_) => f(File(fs::FileDesc::new(fd, false))), + } } local_data_key!(local_stdout: Box<Writer + Send>) @@ -278,10 +276,10 @@ impl Reader for StdReader { // print!'d prompt not being shown until after the user hits // enter. flush(); - tty.read(buf) + tty.read(buf).map(|i| i as uint) }, File(ref mut file) => file.read(buf).map(|i| i as uint), - }.map_err(IoError::from_rtio_error); + }; match ret { // When reading a piped stdin, libuv will return 0-length reads when // stdin reaches EOF. For pretty much all other streams it will @@ -313,7 +311,7 @@ impl StdWriter { pub fn winsize(&mut self) -> IoResult<(int, int)> { match self.inner { TTY(ref mut tty) => { - tty.get_winsize().map_err(IoError::from_rtio_error) + tty.get_winsize() } File(..) => { Err(IoError { @@ -335,7 +333,7 @@ impl StdWriter { pub fn set_raw(&mut self, raw: bool) -> IoResult<()> { match self.inner { TTY(ref mut tty) => { - tty.set_raw(raw).map_err(IoError::from_rtio_error) + tty.set_raw(raw) } File(..) => { Err(IoError { @@ -374,7 +372,7 @@ impl Writer for StdWriter { try!(match self.inner { TTY(ref mut tty) => tty.write(chunk), File(ref mut file) => file.write(chunk), - }.map_err(IoError::from_rtio_error)) + }) } Ok(()) } diff --git a/src/libstd/io/timer.rs b/src/libstd/io/timer.rs index d16199da77f..ec588f13478 100644 --- a/src/libstd/io/timer.rs +++ b/src/libstd/io/timer.rs @@ -21,10 +21,9 @@ and create receivers which will receive notifications after a period of time. use comm::{Receiver, Sender, channel}; use time::Duration; -use io::{IoResult, IoError}; -use kinds::Send; -use boxed::Box; -use rt::rtio::{IoFactory, LocalIo, RtioTimer, Callback}; +use io::IoResult; +use sys::timer::Callback; +use sys::timer::Timer as TimerImp; /// A synchronous timer object /// @@ -69,7 +68,7 @@ use rt::rtio::{IoFactory, LocalIo, RtioTimer, Callback}; /// # } /// ``` pub struct Timer { - obj: Box<RtioTimer + Send>, + inner: TimerImp, } struct TimerCallback { tx: Sender<()> } @@ -90,9 +89,7 @@ impl Timer { /// for a number of milliseconds, or to possibly create channels which will /// get notified after an amount of time has passed. pub fn new() -> IoResult<Timer> { - LocalIo::maybe_raise(|io| { - io.timer_init().map(|t| Timer { obj: t }) - }).map_err(IoError::from_rtio_error) + TimerImp::new().map(|t| Timer { inner: t }) } /// Blocks the current task for the specified duration. @@ -106,7 +103,7 @@ impl Timer { // Short-circuit the timer backend for 0 duration let ms = in_ms_u64(duration); if ms == 0 { return } - self.obj.sleep(ms); + self.inner.sleep(ms); } /// Creates a oneshot receiver which will have a notification sent when @@ -152,7 +149,7 @@ impl Timer { let (tx, rx) = channel(); // Short-circuit the timer backend for 0 duration if in_ms_u64(duration) != 0 { - self.obj.oneshot(in_ms_u64(duration), box TimerCallback { tx: tx }); + self.inner.oneshot(in_ms_u64(duration), box TimerCallback { tx: tx }); } else { tx.send(()); } @@ -213,7 +210,7 @@ impl Timer { // not clear what use a 0ms period is anyway... let ms = if ms == 0 { 1 } else { ms }; let (tx, rx) = channel(); - self.obj.period(ms, box TimerCallback { tx: tx }); + self.inner.period(ms, box TimerCallback { tx: tx }); return rx } } diff --git a/src/libstd/lib.rs b/src/libstd/lib.rs index f10a1d5e5ed..7eac455f97f 100644 --- a/src/libstd/lib.rs +++ b/src/libstd/lib.rs @@ -242,6 +242,13 @@ pub mod io; pub mod path; pub mod fmt; +#[cfg(unix)] +#[path = "sys/unix/mod.rs"] mod sys; +#[cfg(windows)] +#[path = "sys/windows/mod.rs"] mod sys; + +#[path = "sys/common/mod.rs"] mod sys_common; + // FIXME #7809: This shouldn't be pub, and it should be reexported under 'unstable' // but name resolution doesn't work without it being pub. pub mod rt; diff --git a/src/libstd/os.rs b/src/libstd/os.rs index 0042a3ae205..ea42117bab6 100644 --- a/src/libstd/os.rs +++ b/src/libstd/os.rs @@ -34,7 +34,7 @@ use clone::Clone; use error::{FromError, Error}; use fmt; -use io::{IoResult, IoError}; +use io::IoResult; use iter::Iterator; use libc::{c_void, c_int}; use libc; @@ -43,6 +43,8 @@ use ops::Drop; use option::{Some, None, Option}; use os; use path::{Path, GenericPath, BytesContainer}; +use sys; +use sys::os as os_imp; use ptr::RawPtr; use ptr; use result::{Err, Ok, Result}; @@ -602,35 +604,11 @@ pub struct Pipe { /// descriptors to be closed, the file descriptors will leak. For safe handling /// of this scenario, use `std::io::PipeStream` instead. pub unsafe fn pipe() -> IoResult<Pipe> { - return _pipe(); - - #[cfg(unix)] - unsafe fn _pipe() -> IoResult<Pipe> { - let mut fds = [0, ..2]; - match libc::pipe(fds.as_mut_ptr()) { - 0 => Ok(Pipe { reader: fds[0], writer: fds[1] }), - _ => Err(IoError::last_error()), - } - } - - #[cfg(windows)] - unsafe fn _pipe() -> IoResult<Pipe> { - // Windows pipes work subtly differently than unix pipes, and their - // inheritance has to be handled in a different way that I do not - // fully understand. Here we explicitly make the pipe non-inheritable, - // which means to pass it to a subprocess they need to be duplicated - // first, as in std::run. - let mut fds = [0, ..2]; - match libc::pipe(fds.as_mut_ptr(), 1024 as ::libc::c_uint, - (libc::O_BINARY | libc::O_NOINHERIT) as c_int) { - 0 => { - assert!(fds[0] != -1 && fds[0] != 0); - assert!(fds[1] != -1 && fds[1] != 0); - Ok(Pipe { reader: fds[0], writer: fds[1] }) - } - _ => Err(IoError::last_error()), - } - } + let (reader, writer) = try!(sys::os::pipe()); + Ok(Pipe { + reader: reader.unwrap(), + writer: writer.unwrap(), + }) } /// Returns the proper dll filename for the given basename of a file @@ -905,59 +883,9 @@ pub fn change_dir(p: &Path) -> bool { } } -#[cfg(unix)] -/// Returns the platform-specific value of errno -pub fn errno() -> int { - #[cfg(any(target_os = "macos", - target_os = "ios", - target_os = "freebsd"))] - fn errno_location() -> *const c_int { - extern { - fn __error() -> *const c_int; - } - unsafe { - __error() - } - } - - #[cfg(target_os = "dragonfly")] - fn errno_location() -> *const c_int { - extern { - fn __dfly_error() -> *const c_int; - } - unsafe { - __dfly_error() - } - } - - #[cfg(any(target_os = "linux", target_os = "android"))] - fn errno_location() -> *const c_int { - extern { - fn __errno_location() -> *const c_int; - } - unsafe { - __errno_location() - } - } - - unsafe { - (*errno_location()) as int - } -} - -#[cfg(windows)] /// Returns the platform-specific value of errno pub fn errno() -> uint { - use libc::types::os::arch::extra::DWORD; - - #[link_name = "kernel32"] - extern "system" { - fn GetLastError() -> DWORD; - } - - unsafe { - GetLastError() as uint - } + os_imp::errno() as uint } /// Return the string corresponding to an `errno()` value of `errnum`. @@ -969,105 +897,7 @@ pub fn errno() -> uint { /// println!("{}", os::error_string(os::errno() as uint)); /// ``` pub fn error_string(errnum: uint) -> String { - return strerror(errnum); - - #[cfg(unix)] - fn strerror(errnum: uint) -> String { - #[cfg(any(target_os = "macos", - target_os = "ios", - target_os = "android", - target_os = "freebsd", - target_os = "dragonfly"))] - fn strerror_r(errnum: c_int, buf: *mut c_char, buflen: libc::size_t) - -> c_int { - extern { - fn strerror_r(errnum: c_int, buf: *mut c_char, - buflen: libc::size_t) -> c_int; - } - unsafe { - strerror_r(errnum, buf, buflen) - } - } - - // GNU libc provides a non-compliant version of strerror_r by default - // and requires macros to instead use the POSIX compliant variant. - // So we just use __xpg_strerror_r which is always POSIX compliant - #[cfg(target_os = "linux")] - fn strerror_r(errnum: c_int, buf: *mut c_char, - buflen: libc::size_t) -> c_int { - extern { - fn __xpg_strerror_r(errnum: c_int, - buf: *mut c_char, - buflen: libc::size_t) - -> c_int; - } - unsafe { - __xpg_strerror_r(errnum, buf, buflen) - } - } - - let mut buf = [0 as c_char, ..TMPBUF_SZ]; - - let p = buf.as_mut_ptr(); - unsafe { - if strerror_r(errnum as c_int, p, buf.len() as libc::size_t) < 0 { - panic!("strerror_r failure"); - } - - ::string::raw::from_buf(p as *const u8) - } - } - - #[cfg(windows)] - fn strerror(errnum: uint) -> String { - use libc::types::os::arch::extra::DWORD; - use libc::types::os::arch::extra::LPWSTR; - use libc::types::os::arch::extra::LPVOID; - use libc::types::os::arch::extra::WCHAR; - - #[link_name = "kernel32"] - extern "system" { - fn FormatMessageW(flags: DWORD, - lpSrc: LPVOID, - msgId: DWORD, - langId: DWORD, - buf: LPWSTR, - nsize: DWORD, - args: *const c_void) - -> DWORD; - } - - static FORMAT_MESSAGE_FROM_SYSTEM: DWORD = 0x00001000; - static FORMAT_MESSAGE_IGNORE_INSERTS: DWORD = 0x00000200; - - // This value is calculated from the macro - // MAKELANGID(LANG_SYSTEM_DEFAULT, SUBLANG_SYS_DEFAULT) - let langId = 0x0800 as DWORD; - - let mut buf = [0 as WCHAR, ..TMPBUF_SZ]; - - unsafe { - let res = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - ptr::null_mut(), - errnum as DWORD, - langId, - buf.as_mut_ptr(), - buf.len() as DWORD, - ptr::null()); - if res == 0 { - // Sometimes FormatMessageW can fail e.g. system doesn't like langId, - let fm_err = errno(); - return format!("OS Error {} (FormatMessageW() returned error {})", errnum, fm_err); - } - - let msg = String::from_utf16(::str::truncate_utf16_at_nul(buf)); - match msg { - Some(msg) => format!("OS Error {}: {}", errnum, msg), - None => format!("OS Error {} (FormatMessageW() returned invalid UTF-16)", errnum), - } - } - } + return os_imp::error_string(errnum as i32); } /// Get a string representing the platform-dependent last error diff --git a/src/libstd/sys/common/helper_thread.rs b/src/libstd/sys/common/helper_thread.rs new file mode 100644 index 00000000000..87907fde277 --- /dev/null +++ b/src/libstd/sys/common/helper_thread.rs @@ -0,0 +1,131 @@ +// Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Implementation of the helper thread for the timer module +//! +//! This module contains the management necessary for the timer worker thread. +//! This thread is responsible for performing the send()s on channels for timers +//! that are using channels instead of a blocking call. +//! +//! The timer thread is lazily initialized, and it's shut down via the +//! `shutdown` function provided. It must be maintained as an invariant that +//! `shutdown` is only called when the entire program is finished. No new timers +//! can be created in the future and there must be no active timers at that +//! time. + +use mem; +use rt::bookkeeping; +use rt::mutex::StaticNativeMutex; +use rt; +use cell::UnsafeCell; +use sys::helper_signal; +use prelude::*; + +use task; + +/// A structure for management of a helper thread. +/// +/// This is generally a static structure which tracks the lifetime of a helper +/// thread. +/// +/// The fields of this helper are all public, but they should not be used, this +/// is for static initialization. +pub struct Helper<M> { + /// Internal lock which protects the remaining fields + pub lock: StaticNativeMutex, + + // You'll notice that the remaining fields are UnsafeCell<T>, and this is + // because all helper thread operations are done through &self, but we need + // these to be mutable (once `lock` is held). + + /// Lazily allocated channel to send messages to the helper thread. + pub chan: UnsafeCell<*mut Sender<M>>, + + /// OS handle used to wake up a blocked helper thread + pub signal: UnsafeCell<uint>, + + /// Flag if this helper thread has booted and been initialized yet. + pub initialized: UnsafeCell<bool>, +} + +impl<M: Send> Helper<M> { + /// Lazily boots a helper thread, becoming a no-op if the helper has already + /// been spawned. + /// + /// This function will check to see if the thread has been initialized, and + /// if it has it returns quickly. If initialization has not happened yet, + /// the closure `f` will be run (inside of the initialization lock) and + /// passed to the helper thread in a separate task. + /// + /// This function is safe to be called many times. + pub fn boot<T: Send>(&'static self, + f: || -> T, + helper: fn(helper_signal::signal, Receiver<M>, T)) { + unsafe { + let _guard = self.lock.lock(); + if !*self.initialized.get() { + let (tx, rx) = channel(); + *self.chan.get() = mem::transmute(box tx); + let (receive, send) = helper_signal::new(); + *self.signal.get() = send as uint; + + let t = f(); + task::spawn(proc() { + bookkeeping::decrement(); + helper(receive, rx, t); + self.lock.lock().signal() + }); + + rt::at_exit(proc() { self.shutdown() }); + *self.initialized.get() = true; + } + } + } + + /// Sends a message to a spawned worker thread. + /// + /// This is only valid if the worker thread has previously booted + pub fn send(&'static self, msg: M) { + unsafe { + let _guard = self.lock.lock(); + + // Must send and *then* signal to ensure that the child receives the + // message. Otherwise it could wake up and go to sleep before we + // send the message. + assert!(!self.chan.get().is_null()); + (**self.chan.get()).send(msg); + helper_signal::signal(*self.signal.get() as helper_signal::signal); + } + } + + fn shutdown(&'static self) { + unsafe { + // Shut down, but make sure this is done inside our lock to ensure + // that we'll always receive the exit signal when the thread + // returns. + let guard = self.lock.lock(); + + // Close the channel by destroying it + let chan: Box<Sender<M>> = mem::transmute(*self.chan.get()); + *self.chan.get() = 0 as *mut Sender<M>; + drop(chan); + helper_signal::signal(*self.signal.get() as helper_signal::signal); + + // Wait for the child to exit + guard.wait(); + drop(guard); + + // Clean up after ourselves + self.lock.destroy(); + helper_signal::close(*self.signal.get() as helper_signal::signal); + *self.signal.get() = 0; + } + } +} diff --git a/src/libstd/sys/common/mod.rs b/src/libstd/sys/common/mod.rs new file mode 100644 index 00000000000..c5f8214a5c3 --- /dev/null +++ b/src/libstd/sys/common/mod.rs @@ -0,0 +1,100 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![allow(missing_doc)] +#![allow(dead_code)] + +use io::{mod, IoError, IoResult}; +use prelude::*; +use num; +use sys::{last_error, retry, fs}; +use c_str::CString; +use path::BytesContainer; +use collections; + +pub mod net; +pub mod helper_thread; + +// common error constructors + +pub fn eof() -> IoError { + IoError { + kind: io::EndOfFile, + desc: "end of file", + detail: None, + } +} + +pub fn timeout(desc: &'static str) -> IoError { + IoError { + kind: io::TimedOut, + desc: desc, + detail: None, + } +} + +pub fn short_write(n: uint, desc: &'static str) -> IoError { + IoError { + kind: if n == 0 { io::TimedOut } else { io::ShortWrite(n) }, + desc: desc, + detail: None, + } +} + +pub fn unimpl() -> IoError { + IoError { + kind: io::IoUnavailable, + desc: "operations not yet supported", + detail: None, + } +} + +// unix has nonzero values as errors +pub fn mkerr_libc<Int: num::Zero>(ret: Int) -> IoResult<()> { + if !ret.is_zero() { + Err(last_error()) + } else { + Ok(()) + } +} + +pub fn keep_going(data: &[u8], f: |*const u8, uint| -> i64) -> i64 { + let origamt = data.len(); + let mut data = data.as_ptr(); + let mut amt = origamt; + while amt > 0 { + let ret = retry(|| f(data, amt)); + 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; +} + +// traits for extracting representations from + +pub trait AsFileDesc { + fn as_fd(&self) -> &fs::FileDesc; +} + +pub trait ProcessConfig<K: BytesContainer, V: BytesContainer> { + fn program(&self) -> &CString; + fn args(&self) -> &[CString]; + fn env(&self) -> Option<&collections::HashMap<K, V>>; + fn cwd(&self) -> Option<&CString>; + fn uid(&self) -> Option<uint>; + fn gid(&self) -> Option<uint>; + fn detach(&self) -> bool; +} diff --git a/src/libstd/sys/common/net.rs b/src/libstd/sys/common/net.rs new file mode 100644 index 00000000000..7c44142d93c --- /dev/null +++ b/src/libstd/sys/common/net.rs @@ -0,0 +1,911 @@ +// Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use alloc::arc::Arc; +use libc::{mod, c_char, c_int}; +use mem; +use ptr::{mod, null, null_mut}; +use rt::mutex; +use io::net::ip::{SocketAddr, IpAddr, Ipv4Addr, Ipv6Addr}; +use io::net::addrinfo; +use io::{IoResult, IoError}; +use sys::{mod, retry, c, sock_t, last_error, last_net_error, last_gai_error, close_sock, + wrlen, msglen_t, os, wouldblock, set_nonblocking, timer, ms_to_timeval, + decode_error_detailed}; +use sys_common::{mod, keep_going, short_write, timeout}; +use prelude::*; +use cmp; +use io; + +// FIXME: move uses of Arc and deadline tracking to std::io + +#[deriving(Show)] +pub enum SocketStatus { + Readable, + Writable, +} + +//////////////////////////////////////////////////////////////////////////////// +// sockaddr and misc bindings +//////////////////////////////////////////////////////////////////////////////// + +pub fn htons(u: u16) -> u16 { + u.to_be() +} +pub fn ntohs(u: u16) -> u16 { + Int::from_be(u) +} + +pub enum InAddr { + In4Addr(libc::in_addr), + In6Addr(libc::in6_addr), +} + +pub fn ip_to_inaddr(ip: IpAddr) -> InAddr { + match ip { + Ipv4Addr(a, b, c, d) => { + let ip = (a as u32 << 24) | + (b as u32 << 16) | + (c as u32 << 8) | + (d as u32 << 0); + In4Addr(libc::in_addr { + s_addr: Int::from_be(ip) + }) + } + Ipv6Addr(a, b, c, d, e, f, g, h) => { + In6Addr(libc::in6_addr { + s6_addr: [ + htons(a), + htons(b), + htons(c), + htons(d), + htons(e), + htons(f), + htons(g), + htons(h), + ] + }) + } + } +} + +pub fn addr_to_sockaddr(addr: SocketAddr, + storage: &mut libc::sockaddr_storage) + -> libc::socklen_t { + unsafe { + let len = match ip_to_inaddr(addr.ip) { + In4Addr(inaddr) => { + let storage = storage as *mut _ as *mut libc::sockaddr_in; + (*storage).sin_family = libc::AF_INET as libc::sa_family_t; + (*storage).sin_port = htons(addr.port); + (*storage).sin_addr = inaddr; + mem::size_of::<libc::sockaddr_in>() + } + In6Addr(inaddr) => { + let storage = storage as *mut _ as *mut libc::sockaddr_in6; + (*storage).sin6_family = libc::AF_INET6 as libc::sa_family_t; + (*storage).sin6_port = htons(addr.port); + (*storage).sin6_addr = inaddr; + mem::size_of::<libc::sockaddr_in6>() + } + }; + return len as libc::socklen_t; + } +} + +pub fn socket(addr: SocketAddr, ty: libc::c_int) -> IoResult<sock_t> { + unsafe { + let fam = match addr.ip { + Ipv4Addr(..) => libc::AF_INET, + Ipv6Addr(..) => libc::AF_INET6, + }; + match libc::socket(fam, ty, 0) { + -1 => Err(last_net_error()), + fd => Ok(fd), + } + } +} + +pub fn setsockopt<T>(fd: sock_t, opt: libc::c_int, val: libc::c_int, + payload: T) -> IoResult<()> { + unsafe { + let payload = &payload as *const T as *const libc::c_void; + let ret = libc::setsockopt(fd, opt, val, + payload, + mem::size_of::<T>() as libc::socklen_t); + if ret != 0 { + Err(last_net_error()) + } else { + Ok(()) + } + } +} + +pub fn getsockopt<T: Copy>(fd: sock_t, opt: libc::c_int, + val: libc::c_int) -> IoResult<T> { + unsafe { + let mut slot: T = mem::zeroed(); + let mut len = mem::size_of::<T>() as libc::socklen_t; + let ret = c::getsockopt(fd, opt, val, + &mut slot as *mut _ as *mut _, + &mut len); + if ret != 0 { + Err(last_net_error()) + } else { + assert!(len as uint == mem::size_of::<T>()); + Ok(slot) + } + } +} + +pub fn sockname(fd: sock_t, + f: unsafe extern "system" fn(sock_t, *mut libc::sockaddr, + *mut libc::socklen_t) -> libc::c_int) + -> IoResult<SocketAddr> +{ + let mut storage: libc::sockaddr_storage = unsafe { mem::zeroed() }; + let mut len = mem::size_of::<libc::sockaddr_storage>() as libc::socklen_t; + unsafe { + let storage = &mut storage as *mut libc::sockaddr_storage; + let ret = f(fd, + storage as *mut libc::sockaddr, + &mut len as *mut libc::socklen_t); + if ret != 0 { + return Err(last_net_error()) + } + } + return sockaddr_to_addr(&storage, len as uint); +} + +pub fn sockaddr_to_addr(storage: &libc::sockaddr_storage, + len: uint) -> IoResult<SocketAddr> { + match storage.ss_family as libc::c_int { + libc::AF_INET => { + assert!(len as uint >= mem::size_of::<libc::sockaddr_in>()); + let storage: &libc::sockaddr_in = unsafe { + mem::transmute(storage) + }; + let ip = (storage.sin_addr.s_addr as u32).to_be(); + let a = (ip >> 24) as u8; + let b = (ip >> 16) as u8; + let c = (ip >> 8) as u8; + let d = (ip >> 0) as u8; + Ok(SocketAddr { + ip: Ipv4Addr(a, b, c, d), + port: ntohs(storage.sin_port), + }) + } + libc::AF_INET6 => { + assert!(len as uint >= mem::size_of::<libc::sockaddr_in6>()); + let storage: &libc::sockaddr_in6 = unsafe { + mem::transmute(storage) + }; + let a = ntohs(storage.sin6_addr.s6_addr[0]); + let b = ntohs(storage.sin6_addr.s6_addr[1]); + let c = ntohs(storage.sin6_addr.s6_addr[2]); + let d = ntohs(storage.sin6_addr.s6_addr[3]); + let e = ntohs(storage.sin6_addr.s6_addr[4]); + let f = ntohs(storage.sin6_addr.s6_addr[5]); + let g = ntohs(storage.sin6_addr.s6_addr[6]); + let h = ntohs(storage.sin6_addr.s6_addr[7]); + Ok(SocketAddr { + ip: Ipv6Addr(a, b, c, d, e, f, g, h), + port: ntohs(storage.sin6_port), + }) + } + _ => { + Err(IoError { + kind: io::InvalidInput, + desc: "invalid argument", + detail: None, + }) + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// get_host_addresses +//////////////////////////////////////////////////////////////////////////////// + +extern "system" { + fn getaddrinfo(node: *const c_char, service: *const c_char, + hints: *const libc::addrinfo, + res: *mut *mut libc::addrinfo) -> c_int; + fn freeaddrinfo(res: *mut libc::addrinfo); +} + +pub fn get_host_addresses(host: Option<&str>, servname: Option<&str>, + hint: Option<addrinfo::Hint>) + -> Result<Vec<addrinfo::Info>, IoError> +{ + sys::init_net(); + + assert!(host.is_some() || servname.is_some()); + + let c_host = host.map(|x| x.to_c_str()); + let c_host = c_host.as_ref().map(|x| x.as_ptr()).unwrap_or(null()); + let c_serv = servname.map(|x| x.to_c_str()); + let c_serv = c_serv.as_ref().map(|x| x.as_ptr()).unwrap_or(null()); + + let hint = hint.map(|hint| { + libc::addrinfo { + ai_flags: hint.flags as c_int, + ai_family: hint.family as c_int, + ai_socktype: 0, + ai_protocol: 0, + ai_addrlen: 0, + ai_canonname: null_mut(), + ai_addr: null_mut(), + ai_next: null_mut() + } + }); + + let hint_ptr = hint.as_ref().map_or(null(), |x| { + x as *const libc::addrinfo + }); + let mut res = null_mut(); + + // Make the call + let s = unsafe { + getaddrinfo(c_host, c_serv, hint_ptr, &mut res) + }; + + // Error? + if s != 0 { + return Err(last_gai_error(s)); + } + + // Collect all the results we found + let mut addrs = Vec::new(); + let mut rp = res; + while rp.is_not_null() { + unsafe { + let addr = try!(sockaddr_to_addr(mem::transmute((*rp).ai_addr), + (*rp).ai_addrlen as uint)); + addrs.push(addrinfo::Info { + address: addr, + family: (*rp).ai_family as uint, + socktype: None, + protocol: None, + flags: (*rp).ai_flags as uint + }); + + rp = (*rp).ai_next as *mut libc::addrinfo; + } + } + + unsafe { freeaddrinfo(res); } + + Ok(addrs) +} + +//////////////////////////////////////////////////////////////////////////////// +// Timeout helpers +// +// The read/write functions below are the helpers for reading/writing a socket +// with a possible deadline specified. This is generally viewed as a timed out +// I/O operation. +// +// From the application's perspective, timeouts apply to the I/O object, not to +// the underlying file descriptor (it's one timeout per object). This means that +// we can't use the SO_RCVTIMEO and corresponding send timeout option. +// +// The next idea to implement timeouts would be to use nonblocking I/O. An +// invocation of select() would wait (with a timeout) for a socket to be ready. +// Once its ready, we can perform the operation. Note that the operation *must* +// be nonblocking, even though select() says the socket is ready. This is +// because some other thread could have come and stolen our data (handles can be +// cloned). +// +// To implement nonblocking I/O, the first option we have is to use the +// O_NONBLOCK flag. Remember though that this is a global setting, affecting all +// I/O objects, so this was initially viewed as unwise. +// +// It turns out that there's this nifty MSG_DONTWAIT flag which can be passed to +// send/recv, but the niftiness wears off once you realize it only works well on +// Linux [1] [2]. This means that it's pretty easy to get a nonblocking +// operation on Linux (no flag fiddling, no affecting other objects), but not on +// other platforms. +// +// To work around this constraint on other platforms, we end up using the +// original strategy of flipping the O_NONBLOCK flag. As mentioned before, this +// could cause other objects' blocking operations to suddenly become +// nonblocking. To get around this, a "blocking operation" which returns EAGAIN +// falls back to using the same code path as nonblocking operations, but with an +// infinite timeout (select + send/recv). This helps emulate blocking +// reads/writes despite the underlying descriptor being nonblocking, as well as +// optimizing the fast path of just hitting one syscall in the good case. +// +// As a final caveat, this implementation uses a mutex so only one thread is +// doing a nonblocking operation at at time. This is the operation that comes +// after the select() (at which point we think the socket is ready). This is +// done for sanity to ensure that the state of the O_NONBLOCK flag is what we +// expect (wouldn't want someone turning it on when it should be off!). All +// operations performed in the lock are *nonblocking* to avoid holding the mutex +// forever. +// +// So, in summary, Linux uses MSG_DONTWAIT and doesn't need mutexes, everyone +// else uses O_NONBLOCK and mutexes with some trickery to make sure blocking +// reads/writes are still blocking. +// +// Fun, fun! +// +// [1] http://twistedmatrix.com/pipermail/twisted-commits/2012-April/034692.html +// [2] http://stackoverflow.com/questions/19819198/does-send-msg-dontwait + +pub fn read<T>(fd: sock_t, + deadline: u64, + lock: || -> T, + read: |bool| -> libc::c_int) -> IoResult<uint> { + let mut ret = -1; + if deadline == 0 { + ret = retry(|| read(false)); + } + + if deadline != 0 || (ret == -1 && wouldblock()) { + let deadline = match deadline { + 0 => None, + n => Some(n), + }; + loop { + // With a timeout, first we wait for the socket to become + // readable using select(), specifying the relevant timeout for + // our previously set deadline. + try!(await([fd], deadline, Readable)); + + // At this point, we're still within the timeout, and we've + // determined that the socket is readable (as returned by + // select). We must still read the socket in *nonblocking* mode + // because some other thread could come steal our data. If we + // fail to read some data, we retry (hence the outer loop) and + // wait for the socket to become readable again. + let _guard = lock(); + match retry(|| read(deadline.is_some())) { + -1 if wouldblock() => {} + -1 => return Err(last_net_error()), + n => { ret = n; break } + } + } + } + + match ret { + 0 => Err(sys_common::eof()), + n if n < 0 => Err(last_net_error()), + n => Ok(n as uint) + } +} + +pub fn write<T>(fd: sock_t, + deadline: u64, + buf: &[u8], + write_everything: bool, + lock: || -> T, + write: |bool, *const u8, uint| -> i64) -> IoResult<uint> { + let mut ret = -1; + let mut written = 0; + if deadline == 0 { + if write_everything { + ret = keep_going(buf, |inner, len| { + written = buf.len() - len; + write(false, inner, len) + }); + } else { + ret = retry(|| { write(false, buf.as_ptr(), buf.len()) }); + if ret > 0 { written = ret as uint; } + } + } + + if deadline != 0 || (ret == -1 && wouldblock()) { + let deadline = match deadline { + 0 => None, + n => Some(n), + }; + while written < buf.len() && (write_everything || written == 0) { + // As with read(), first wait for the socket to be ready for + // the I/O operation. + match await([fd], deadline, Writable) { + Err(ref e) if e.kind == io::EndOfFile && written > 0 => { + assert!(deadline.is_some()); + return Err(short_write(written, "short write")) + } + Err(e) => return Err(e), + Ok(()) => {} + } + + // Also as with read(), we use MSG_DONTWAIT to guard ourselves + // against unforeseen circumstances. + let _guard = lock(); + let ptr = buf[written..].as_ptr(); + let len = buf.len() - written; + match retry(|| write(deadline.is_some(), ptr, len)) { + -1 if wouldblock() => {} + -1 => return Err(last_net_error()), + n => { written += n as uint; } + } + } + ret = 0; + } + if ret < 0 { + Err(last_net_error()) + } else { + Ok(written) + } +} + +// See http://developerweb.net/viewtopic.php?id=3196 for where this is +// derived from. +pub fn connect_timeout(fd: sock_t, + addrp: *const libc::sockaddr, + len: libc::socklen_t, + timeout_ms: u64) -> IoResult<()> { + #[cfg(unix)] use libc::EINPROGRESS as INPROGRESS; + #[cfg(windows)] use libc::WSAEINPROGRESS as INPROGRESS; + #[cfg(unix)] use libc::EWOULDBLOCK as WOULDBLOCK; + #[cfg(windows)] use libc::WSAEWOULDBLOCK as WOULDBLOCK; + + // Make sure the call to connect() doesn't block + try!(set_nonblocking(fd, true)); + + let ret = match unsafe { libc::connect(fd, addrp, len) } { + // If the connection is in progress, then we need to wait for it to + // finish (with a timeout). The current strategy for doing this is + // to use select() with a timeout. + -1 if os::errno() as int == INPROGRESS as int || + os::errno() as int == WOULDBLOCK as int => { + let mut set: c::fd_set = unsafe { mem::zeroed() }; + c::fd_set(&mut set, fd); + match await(fd, &mut set, timeout_ms) { + 0 => Err(timeout("connection timed out")), + -1 => Err(last_net_error()), + _ => { + let err: libc::c_int = try!( + getsockopt(fd, libc::SOL_SOCKET, libc::SO_ERROR)); + if err == 0 { + Ok(()) + } else { + Err(decode_error_detailed(err)) + } + } + } + } + + -1 => Err(last_net_error()), + _ => Ok(()), + }; + + // be sure to turn blocking I/O back on + try!(set_nonblocking(fd, false)); + return ret; + + #[cfg(unix)] + fn await(fd: sock_t, set: &mut c::fd_set, timeout: u64) -> libc::c_int { + let start = timer::now(); + retry(|| unsafe { + // Recalculate the timeout each iteration (it is generally + // undefined what the value of the 'tv' is after select + // returns EINTR). + let mut tv = ms_to_timeval(timeout - (timer::now() - start)); + c::select(fd + 1, ptr::null_mut(), set as *mut _, + ptr::null_mut(), &mut tv) + }) + } + #[cfg(windows)] + fn await(_fd: sock_t, set: &mut c::fd_set, timeout: u64) -> libc::c_int { + let mut tv = ms_to_timeval(timeout); + unsafe { c::select(1, ptr::null_mut(), set, ptr::null_mut(), &mut tv) } + } +} + +pub fn await(fds: &[sock_t], deadline: Option<u64>, + status: SocketStatus) -> IoResult<()> { + let mut set: c::fd_set = unsafe { mem::zeroed() }; + let mut max = 0; + for &fd in fds.iter() { + c::fd_set(&mut set, fd); + max = cmp::max(max, fd + 1); + } + if cfg!(windows) { + max = fds.len() as sock_t; + } + + let (read, write) = match status { + Readable => (&mut set as *mut _, ptr::null_mut()), + Writable => (ptr::null_mut(), &mut set as *mut _), + }; + let mut tv: libc::timeval = unsafe { mem::zeroed() }; + + match retry(|| { + let now = timer::now(); + let tvp = match deadline { + None => ptr::null_mut(), + Some(deadline) => { + // If we're past the deadline, then pass a 0 timeout to + // select() so we can poll the status + let ms = if deadline < now {0} else {deadline - now}; + tv = ms_to_timeval(ms); + &mut tv as *mut _ + } + }; + let r = unsafe { + c::select(max as libc::c_int, read, write, ptr::null_mut(), tvp) + }; + r + }) { + -1 => Err(last_net_error()), + 0 => Err(timeout("timed out")), + _ => Ok(()), + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Basic socket representation +//////////////////////////////////////////////////////////////////////////////// + +struct Inner { + fd: sock_t, + + // Unused on Linux, where this lock is not necessary. + #[allow(dead_code)] + lock: mutex::NativeMutex +} + +impl Inner { + fn new(fd: sock_t) -> Inner { + Inner { fd: fd, lock: unsafe { mutex::NativeMutex::new() } } + } +} + +impl Drop for Inner { + fn drop(&mut self) { unsafe { close_sock(self.fd); } } +} + +pub struct Guard<'a> { + pub fd: sock_t, + pub guard: mutex::LockGuard<'a>, +} + +#[unsafe_destructor] +impl<'a> Drop for Guard<'a> { + fn drop(&mut self) { + assert!(set_nonblocking(self.fd, false).is_ok()); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// TCP streams +//////////////////////////////////////////////////////////////////////////////// + +pub struct TcpStream { + inner: Arc<Inner>, + read_deadline: u64, + write_deadline: u64, +} + +impl TcpStream { + pub fn connect(addr: SocketAddr, timeout: Option<u64>) -> IoResult<TcpStream> { + sys::init_net(); + + let fd = try!(socket(addr, libc::SOCK_STREAM)); + let ret = TcpStream::new(fd); + + let mut storage = unsafe { mem::zeroed() }; + let len = addr_to_sockaddr(addr, &mut storage); + let addrp = &storage as *const _ as *const libc::sockaddr; + + match timeout { + Some(timeout) => { + try!(connect_timeout(fd, addrp, len, timeout)); + Ok(ret) + }, + None => { + match retry(|| unsafe { libc::connect(fd, addrp, len) }) { + -1 => Err(last_error()), + _ => Ok(ret), + } + } + } + } + + pub fn new(fd: sock_t) -> TcpStream { + TcpStream { + inner: Arc::new(Inner::new(fd)), + read_deadline: 0, + write_deadline: 0, + } + } + + pub fn fd(&self) -> sock_t { self.inner.fd } + + pub fn set_nodelay(&mut self, nodelay: bool) -> IoResult<()> { + setsockopt(self.fd(), libc::IPPROTO_TCP, libc::TCP_NODELAY, + nodelay as libc::c_int) + } + + pub fn set_keepalive(&mut self, seconds: Option<uint>) -> IoResult<()> { + let ret = setsockopt(self.fd(), libc::SOL_SOCKET, libc::SO_KEEPALIVE, + seconds.is_some() as libc::c_int); + match seconds { + Some(n) => ret.and_then(|()| self.set_tcp_keepalive(n)), + None => ret, + } + } + + #[cfg(any(target_os = "macos", target_os = "ios"))] + fn set_tcp_keepalive(&mut self, seconds: uint) -> IoResult<()> { + setsockopt(self.fd(), libc::IPPROTO_TCP, libc::TCP_KEEPALIVE, + seconds as libc::c_int) + } + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + fn set_tcp_keepalive(&mut self, seconds: uint) -> IoResult<()> { + setsockopt(self.fd(), libc::IPPROTO_TCP, libc::TCP_KEEPIDLE, + seconds as libc::c_int) + } + #[cfg(not(any(target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "dragonfly")))] + fn set_tcp_keepalive(&mut self, _seconds: uint) -> IoResult<()> { + Ok(()) + } + + #[cfg(target_os = "linux")] + fn lock_nonblocking(&self) {} + + #[cfg(not(target_os = "linux"))] + fn lock_nonblocking<'a>(&'a self) -> Guard<'a> { + let ret = Guard { + fd: self.fd(), + guard: unsafe { self.inner.lock.lock() }, + }; + assert!(set_nonblocking(self.fd(), true).is_ok()); + ret + } + + pub fn read(&mut self, buf: &mut [u8]) -> IoResult<uint> { + let fd = self.fd(); + let dolock = || self.lock_nonblocking(); + let doread = |nb| unsafe { + let flags = if nb {c::MSG_DONTWAIT} else {0}; + libc::recv(fd, + buf.as_mut_ptr() as *mut libc::c_void, + buf.len() as wrlen, + flags) as libc::c_int + }; + read(fd, self.read_deadline, dolock, doread) + } + + pub fn write(&mut self, buf: &[u8]) -> IoResult<()> { + let fd = self.fd(); + let dolock = || self.lock_nonblocking(); + let dowrite = |nb: bool, buf: *const u8, len: uint| unsafe { + let flags = if nb {c::MSG_DONTWAIT} else {0}; + libc::send(fd, + buf as *const _, + len as wrlen, + flags) as i64 + }; + write(fd, self.write_deadline, buf, true, dolock, dowrite).map(|_| ()) + } + pub fn peer_name(&mut self) -> IoResult<SocketAddr> { + sockname(self.fd(), libc::getpeername) + } + + pub fn close_write(&mut self) -> IoResult<()> { + super::mkerr_libc(unsafe { libc::shutdown(self.fd(), libc::SHUT_WR) }) + } + pub fn close_read(&mut self) -> IoResult<()> { + super::mkerr_libc(unsafe { libc::shutdown(self.fd(), libc::SHUT_RD) }) + } + + pub fn set_timeout(&mut self, timeout: Option<u64>) { + let deadline = timeout.map(|a| timer::now() + a).unwrap_or(0); + self.read_deadline = deadline; + self.write_deadline = deadline; + } + pub fn set_read_timeout(&mut self, timeout: Option<u64>) { + self.read_deadline = timeout.map(|a| timer::now() + a).unwrap_or(0); + } + pub fn set_write_timeout(&mut self, timeout: Option<u64>) { + self.write_deadline = timeout.map(|a| timer::now() + a).unwrap_or(0); + } + + pub fn socket_name(&mut self) -> IoResult<SocketAddr> { + sockname(self.fd(), libc::getsockname) + } +} + +impl Clone for TcpStream { + fn clone(&self) -> TcpStream { + TcpStream { + inner: self.inner.clone(), + read_deadline: 0, + write_deadline: 0, + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// UDP +//////////////////////////////////////////////////////////////////////////////// + +pub struct UdpSocket { + inner: Arc<Inner>, + read_deadline: u64, + write_deadline: u64, +} + +impl UdpSocket { + pub fn bind(addr: SocketAddr) -> IoResult<UdpSocket> { + sys::init_net(); + + let fd = try!(socket(addr, libc::SOCK_DGRAM)); + let ret = UdpSocket { + inner: Arc::new(Inner::new(fd)), + read_deadline: 0, + write_deadline: 0, + }; + + let mut storage = unsafe { mem::zeroed() }; + let len = addr_to_sockaddr(addr, &mut storage); + let addrp = &storage as *const _ as *const libc::sockaddr; + + match unsafe { libc::bind(fd, addrp, len) } { + -1 => Err(last_error()), + _ => Ok(ret), + } + } + + pub fn fd(&self) -> sock_t { self.inner.fd } + + pub fn set_broadcast(&mut self, on: bool) -> IoResult<()> { + setsockopt(self.fd(), libc::SOL_SOCKET, libc::SO_BROADCAST, + on as libc::c_int) + } + + pub fn set_multicast_loop(&mut self, on: bool) -> IoResult<()> { + setsockopt(self.fd(), libc::IPPROTO_IP, libc::IP_MULTICAST_LOOP, + on as libc::c_int) + } + + pub fn set_membership(&mut self, addr: IpAddr, opt: libc::c_int) -> IoResult<()> { + match ip_to_inaddr(addr) { + In4Addr(addr) => { + let mreq = libc::ip_mreq { + imr_multiaddr: addr, + // interface == INADDR_ANY + imr_interface: libc::in_addr { s_addr: 0x0 }, + }; + setsockopt(self.fd(), libc::IPPROTO_IP, opt, mreq) + } + In6Addr(addr) => { + let mreq = libc::ip6_mreq { + ipv6mr_multiaddr: addr, + ipv6mr_interface: 0, + }; + setsockopt(self.fd(), libc::IPPROTO_IPV6, opt, mreq) + } + } + } + + #[cfg(target_os = "linux")] + fn lock_nonblocking(&self) {} + + #[cfg(not(target_os = "linux"))] + fn lock_nonblocking<'a>(&'a self) -> Guard<'a> { + let ret = Guard { + fd: self.fd(), + guard: unsafe { self.inner.lock.lock() }, + }; + assert!(set_nonblocking(self.fd(), true).is_ok()); + ret + } + + pub fn socket_name(&mut self) -> IoResult<SocketAddr> { + sockname(self.fd(), libc::getsockname) + } + + pub fn recv_from(&mut self, buf: &mut [u8]) -> IoResult<(uint, SocketAddr)> { + let fd = self.fd(); + let mut storage: libc::sockaddr_storage = unsafe { mem::zeroed() }; + let storagep = &mut storage as *mut _ as *mut libc::sockaddr; + let mut addrlen: libc::socklen_t = + mem::size_of::<libc::sockaddr_storage>() as libc::socklen_t; + + let dolock = || self.lock_nonblocking(); + let n = try!(read(fd, self.read_deadline, dolock, |nb| unsafe { + let flags = if nb {c::MSG_DONTWAIT} else {0}; + libc::recvfrom(fd, + buf.as_mut_ptr() as *mut libc::c_void, + buf.len() as msglen_t, + flags, + storagep, + &mut addrlen) as libc::c_int + })); + sockaddr_to_addr(&storage, addrlen as uint).and_then(|addr| { + Ok((n as uint, addr)) + }) + } + + pub fn send_to(&mut self, buf: &[u8], dst: SocketAddr) -> IoResult<()> { + let mut storage = unsafe { mem::zeroed() }; + let dstlen = addr_to_sockaddr(dst, &mut storage); + let dstp = &storage as *const _ as *const libc::sockaddr; + + let fd = self.fd(); + let dolock = || self.lock_nonblocking(); + let dowrite = |nb, buf: *const u8, len: uint| unsafe { + let flags = if nb {c::MSG_DONTWAIT} else {0}; + libc::sendto(fd, + buf as *const libc::c_void, + len as msglen_t, + flags, + dstp, + dstlen) as i64 + }; + + let n = try!(write(fd, self.write_deadline, buf, false, dolock, dowrite)); + if n != buf.len() { + Err(short_write(n, "couldn't send entire packet at once")) + } else { + Ok(()) + } + } + + pub fn join_multicast(&mut self, multi: IpAddr) -> IoResult<()> { + match multi { + Ipv4Addr(..) => { + self.set_membership(multi, libc::IP_ADD_MEMBERSHIP) + } + Ipv6Addr(..) => { + self.set_membership(multi, libc::IPV6_ADD_MEMBERSHIP) + } + } + } + pub fn leave_multicast(&mut self, multi: IpAddr) -> IoResult<()> { + match multi { + Ipv4Addr(..) => { + self.set_membership(multi, libc::IP_DROP_MEMBERSHIP) + } + Ipv6Addr(..) => { + self.set_membership(multi, libc::IPV6_DROP_MEMBERSHIP) + } + } + } + + pub fn multicast_time_to_live(&mut self, ttl: int) -> IoResult<()> { + setsockopt(self.fd(), libc::IPPROTO_IP, libc::IP_MULTICAST_TTL, + ttl as libc::c_int) + } + pub fn time_to_live(&mut self, ttl: int) -> IoResult<()> { + setsockopt(self.fd(), libc::IPPROTO_IP, libc::IP_TTL, ttl as libc::c_int) + } + + pub fn set_timeout(&mut self, timeout: Option<u64>) { + let deadline = timeout.map(|a| timer::now() + a).unwrap_or(0); + self.read_deadline = deadline; + self.write_deadline = deadline; + } + pub fn set_read_timeout(&mut self, timeout: Option<u64>) { + self.read_deadline = timeout.map(|a| timer::now() + a).unwrap_or(0); + } + pub fn set_write_timeout(&mut self, timeout: Option<u64>) { + self.write_deadline = timeout.map(|a| timer::now() + a).unwrap_or(0); + } +} + +impl Clone for UdpSocket { + fn clone(&self) -> UdpSocket { + UdpSocket { + inner: self.inner.clone(), + read_deadline: 0, + write_deadline: 0, + } + } +} diff --git a/src/libstd/sys/unix/c.rs b/src/libstd/sys/unix/c.rs new file mode 100644 index 00000000000..e76f2a2b872 --- /dev/null +++ b/src/libstd/sys/unix/c.rs @@ -0,0 +1,262 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! C definitions used by libnative that don't belong in liblibc + +#![allow(dead_code)] +#![allow(non_camel_case_types)] + +pub use self::select::fd_set; +pub use self::signal::{sigaction, siginfo, sigset_t}; +pub use self::signal::{SA_ONSTACK, SA_RESTART, SA_RESETHAND, SA_NOCLDSTOP}; +pub use self::signal::{SA_NODEFER, SA_NOCLDWAIT, SA_SIGINFO, SIGCHLD}; + +use libc; + +#[cfg(any(target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "dragonfly"))] +pub const FIONBIO: libc::c_ulong = 0x8004667e; +#[cfg(any(all(target_os = "linux", + any(target_arch = "x86", + target_arch = "x86_64", + target_arch = "arm")), + target_os = "android"))] +pub const FIONBIO: libc::c_ulong = 0x5421; +#[cfg(all(target_os = "linux", + any(target_arch = "mips", target_arch = "mipsel")))] +pub const FIONBIO: libc::c_ulong = 0x667e; + +#[cfg(any(target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "dragonfly"))] +pub const FIOCLEX: libc::c_ulong = 0x20006601; +#[cfg(any(all(target_os = "linux", + any(target_arch = "x86", + target_arch = "x86_64", + target_arch = "arm")), + target_os = "android"))] +pub const FIOCLEX: libc::c_ulong = 0x5451; +#[cfg(all(target_os = "linux", + any(target_arch = "mips", target_arch = "mipsel")))] +pub const FIOCLEX: libc::c_ulong = 0x6601; + +#[cfg(any(target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "dragonfly"))] +pub const MSG_DONTWAIT: libc::c_int = 0x80; +#[cfg(any(target_os = "linux", target_os = "android"))] +pub const MSG_DONTWAIT: libc::c_int = 0x40; + +pub const WNOHANG: libc::c_int = 1; + +extern { + pub fn gettimeofday(timeval: *mut libc::timeval, + tzp: *mut libc::c_void) -> libc::c_int; + pub fn select(nfds: libc::c_int, + readfds: *mut fd_set, + writefds: *mut fd_set, + errorfds: *mut fd_set, + timeout: *mut libc::timeval) -> libc::c_int; + pub fn getsockopt(sockfd: libc::c_int, + level: libc::c_int, + optname: libc::c_int, + optval: *mut libc::c_void, + optlen: *mut libc::socklen_t) -> libc::c_int; + pub fn ioctl(fd: libc::c_int, req: libc::c_ulong, ...) -> libc::c_int; + + + pub fn waitpid(pid: libc::pid_t, status: *mut libc::c_int, + options: libc::c_int) -> libc::pid_t; + + pub fn sigaction(signum: libc::c_int, + act: *const sigaction, + oldact: *mut sigaction) -> libc::c_int; + + pub fn sigaddset(set: *mut sigset_t, signum: libc::c_int) -> libc::c_int; + pub fn sigdelset(set: *mut sigset_t, signum: libc::c_int) -> libc::c_int; + pub fn sigemptyset(set: *mut sigset_t) -> libc::c_int; +} + +#[cfg(any(target_os = "macos", target_os = "ios"))] +mod select { + pub const FD_SETSIZE: uint = 1024; + + #[repr(C)] + pub struct fd_set { + fds_bits: [i32, ..(FD_SETSIZE / 32)] + } + + pub fn fd_set(set: &mut fd_set, fd: i32) { + set.fds_bits[(fd / 32) as uint] |= 1 << ((fd % 32) as uint); + } +} + +#[cfg(any(target_os = "android", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "linux"))] +mod select { + use uint; + use libc; + + pub const FD_SETSIZE: uint = 1024; + + #[repr(C)] + pub struct fd_set { + // FIXME: shouldn't this be a c_ulong? + fds_bits: [libc::uintptr_t, ..(FD_SETSIZE / uint::BITS)] + } + + pub fn fd_set(set: &mut fd_set, fd: i32) { + let fd = fd as uint; + set.fds_bits[fd / uint::BITS] |= 1 << (fd % uint::BITS); + } +} + +#[cfg(any(all(target_os = "linux", + any(target_arch = "x86", + target_arch = "x86_64", + target_arch = "arm")), + target_os = "android"))] +mod signal { + use libc; + + pub const SA_NOCLDSTOP: libc::c_ulong = 0x00000001; + pub const SA_NOCLDWAIT: libc::c_ulong = 0x00000002; + pub const SA_NODEFER: libc::c_ulong = 0x40000000; + pub const SA_ONSTACK: libc::c_ulong = 0x08000000; + pub const SA_RESETHAND: libc::c_ulong = 0x80000000; + pub const SA_RESTART: libc::c_ulong = 0x10000000; + pub const SA_SIGINFO: libc::c_ulong = 0x00000004; + pub const SIGCHLD: libc::c_int = 17; + + // This definition is not as accurate as it could be, {pid, uid, status} is + // actually a giant union. Currently we're only interested in these fields, + // however. + #[repr(C)] + pub struct siginfo { + si_signo: libc::c_int, + si_errno: libc::c_int, + si_code: libc::c_int, + pub pid: libc::pid_t, + pub uid: libc::uid_t, + pub status: libc::c_int, + } + + #[repr(C)] + pub struct sigaction { + pub sa_handler: extern fn(libc::c_int), + pub sa_mask: sigset_t, + pub sa_flags: libc::c_ulong, + sa_restorer: *mut libc::c_void, + } + + #[repr(C)] + #[cfg(target_word_size = "32")] + pub struct sigset_t { + __val: [libc::c_ulong, ..32], + } + + #[repr(C)] + #[cfg(target_word_size = "64")] + pub struct sigset_t { + __val: [libc::c_ulong, ..16], + } +} + +#[cfg(all(target_os = "linux", + any(target_arch = "mips", target_arch = "mipsel")))] +mod signal { + use libc; + + pub const SA_NOCLDSTOP: libc::c_ulong = 0x00000001; + pub const SA_NOCLDWAIT: libc::c_ulong = 0x00010000; + pub const SA_NODEFER: libc::c_ulong = 0x40000000; + pub const SA_ONSTACK: libc::c_ulong = 0x08000000; + pub const SA_RESETHAND: libc::c_ulong = 0x80000000; + pub const SA_RESTART: libc::c_ulong = 0x10000000; + pub const SA_SIGINFO: libc::c_ulong = 0x00000008; + pub const SIGCHLD: libc::c_int = 18; + + // This definition is not as accurate as it could be, {pid, uid, status} is + // actually a giant union. Currently we're only interested in these fields, + // however. + #[repr(C)] + pub struct siginfo { + si_signo: libc::c_int, + si_code: libc::c_int, + si_errno: libc::c_int, + pub pid: libc::pid_t, + pub uid: libc::uid_t, + pub status: libc::c_int, + } + + #[repr(C)] + pub struct sigaction { + pub sa_flags: libc::c_uint, + pub sa_handler: extern fn(libc::c_int), + pub sa_mask: sigset_t, + sa_restorer: *mut libc::c_void, + sa_resv: [libc::c_int, ..1], + } + + #[repr(C)] + pub struct sigset_t { + __val: [libc::c_ulong, ..32], + } +} + +#[cfg(any(target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "dragonfly"))] +mod signal { + use libc; + + pub const SA_ONSTACK: libc::c_int = 0x0001; + pub const SA_RESTART: libc::c_int = 0x0002; + pub const SA_RESETHAND: libc::c_int = 0x0004; + pub const SA_NOCLDSTOP: libc::c_int = 0x0008; + pub const SA_NODEFER: libc::c_int = 0x0010; + pub const SA_NOCLDWAIT: libc::c_int = 0x0020; + pub const SA_SIGINFO: libc::c_int = 0x0040; + pub const SIGCHLD: libc::c_int = 20; + + #[cfg(any(target_os = "macos", target_os = "ios"))] + pub type sigset_t = u32; + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + #[repr(C)] + pub struct sigset_t { + bits: [u32, ..4], + } + + // This structure has more fields, but we're not all that interested in + // them. + #[repr(C)] + pub struct siginfo { + pub si_signo: libc::c_int, + pub si_errno: libc::c_int, + pub si_code: libc::c_int, + pub pid: libc::pid_t, + pub uid: libc::uid_t, + pub status: libc::c_int, + } + + #[repr(C)] + pub struct sigaction { + pub sa_handler: extern fn(libc::c_int), + pub sa_flags: libc::c_int, + pub sa_mask: sigset_t, + } +} diff --git a/src/libstd/sys/unix/fs.rs b/src/libstd/sys/unix/fs.rs new file mode 100644 index 00000000000..2d02c34e958 --- /dev/null +++ b/src/libstd/sys/unix/fs.rs @@ -0,0 +1,392 @@ +// Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Blocking posix-based file I/O + +use libc::{mod, c_int, c_void}; +use c_str::CString; +use mem; +use io; + +use prelude::*; + +use io::{FilePermission, Write, UnstableFileStat, Open, FileAccess, FileMode}; +use io::{IoResult, FileStat, SeekStyle, Reader}; +use io::{Read, Truncate, SeekCur, SeekSet, ReadWrite, SeekEnd, Append}; +use result::{Ok, Err}; +use sys::retry; +use sys_common::{keep_going, eof, mkerr_libc}; + +pub use path::PosixPath as Path; + +pub type fd_t = libc::c_int; + +pub struct FileDesc { + /// The underlying C file descriptor. + fd: fd_t, + + /// Whether to close the file descriptor on drop. + close_on_drop: bool, +} + +impl FileDesc { + pub fn new(fd: fd_t, close_on_drop: bool) -> FileDesc { + FileDesc { fd: fd, close_on_drop: close_on_drop } + } + + pub fn read(&self, buf: &mut [u8]) -> IoResult<uint> { + let ret = retry(|| unsafe { + libc::read(self.fd(), + buf.as_mut_ptr() as *mut libc::c_void, + buf.len() as libc::size_t) + }); + if ret == 0 { + Err(eof()) + } else if ret < 0 { + Err(super::last_error()) + } else { + Ok(ret as uint) + } + } + pub fn write(&self, buf: &[u8]) -> IoResult<()> { + let ret = keep_going(buf, |buf, len| { + unsafe { + libc::write(self.fd(), buf as *const libc::c_void, + len as libc::size_t) as i64 + } + }); + if ret < 0 { + Err(super::last_error()) + } else { + Ok(()) + } + } + + pub fn fd(&self) -> fd_t { self.fd } + + pub fn seek(&self, pos: i64, whence: SeekStyle) -> IoResult<u64> { + let whence = match whence { + SeekSet => libc::SEEK_SET, + SeekEnd => libc::SEEK_END, + SeekCur => libc::SEEK_CUR, + }; + let n = unsafe { libc::lseek(self.fd(), pos as libc::off_t, whence) }; + if n < 0 { + Err(super::last_error()) + } else { + Ok(n as u64) + } + } + + pub fn tell(&self) -> IoResult<u64> { + let n = unsafe { libc::lseek(self.fd(), 0, libc::SEEK_CUR) }; + if n < 0 { + Err(super::last_error()) + } else { + Ok(n as u64) + } + } + + pub fn fsync(&self) -> IoResult<()> { + mkerr_libc(retry(|| unsafe { libc::fsync(self.fd()) })) + } + + pub fn datasync(&self) -> IoResult<()> { + return mkerr_libc(os_datasync(self.fd())); + + #[cfg(any(target_os = "macos", target_os = "ios"))] + fn os_datasync(fd: c_int) -> c_int { + unsafe { libc::fcntl(fd, libc::F_FULLFSYNC) } + } + #[cfg(target_os = "linux")] + fn os_datasync(fd: c_int) -> c_int { + retry(|| unsafe { libc::fdatasync(fd) }) + } + #[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "linux")))] + fn os_datasync(fd: c_int) -> c_int { + retry(|| unsafe { libc::fsync(fd) }) + } + } + + pub fn truncate(&self, offset: i64) -> IoResult<()> { + mkerr_libc(retry(|| unsafe { + libc::ftruncate(self.fd(), offset as libc::off_t) + })) + } + + pub fn fstat(&self) -> IoResult<FileStat> { + let mut stat: libc::stat = unsafe { mem::zeroed() }; + match unsafe { libc::fstat(self.fd(), &mut stat) } { + 0 => Ok(mkstat(&stat)), + _ => Err(super::last_error()), + } + } + + /// Extract the actual filedescriptor without closing it. + pub fn unwrap(self) -> fd_t { + let fd = self.fd; + unsafe { mem::forget(self) }; + fd + } +} + +impl Drop for FileDesc { + fn drop(&mut self) { + // closing stdio file handles makes no sense, so never do it. Also, note + // that errors are ignored when closing a file descriptor. The reason + // for this is that if an error occurs we don't actually know if the + // file descriptor was closed or not, and if we retried (for something + // like EINTR), we might close another valid file descriptor (opened + // after we closed ours. + if self.close_on_drop && self.fd > libc::STDERR_FILENO { + let n = unsafe { libc::close(self.fd) }; + if n != 0 { + println!("error {} when closing file descriptor {}", n, self.fd); + } + } + } +} + +pub fn open(path: &Path, fm: FileMode, fa: FileAccess) -> IoResult<FileDesc> { + let flags = match fm { + Open => 0, + Append => libc::O_APPEND, + Truncate => libc::O_TRUNC, + }; + // Opening with a write permission must silently create the file. + let (flags, mode) = match fa { + Read => (flags | libc::O_RDONLY, 0), + Write => (flags | libc::O_WRONLY | libc::O_CREAT, + libc::S_IRUSR | libc::S_IWUSR), + ReadWrite => (flags | libc::O_RDWR | libc::O_CREAT, + libc::S_IRUSR | libc::S_IWUSR), + }; + + let path = path.to_c_str(); + match retry(|| unsafe { libc::open(path.as_ptr(), flags, mode) }) { + -1 => Err(super::last_error()), + fd => Ok(FileDesc::new(fd, true)), + } +} + +pub fn mkdir(p: &Path, mode: uint) -> IoResult<()> { + let p = p.to_c_str(); + mkerr_libc(unsafe { libc::mkdir(p.as_ptr(), mode as libc::mode_t) }) +} + +pub fn readdir(p: &Path) -> IoResult<Vec<Path>> { + use libc::{dirent_t}; + use libc::{opendir, readdir_r, closedir}; + + fn prune(root: &CString, dirs: Vec<Path>) -> Vec<Path> { + let root = unsafe { CString::new(root.as_ptr(), false) }; + let root = Path::new(root); + + dirs.into_iter().filter(|path| { + path.as_vec() != b"." && path.as_vec() != b".." + }).map(|path| root.join(path)).collect() + } + + extern { + fn rust_dirent_t_size() -> libc::c_int; + fn rust_list_dir_val(ptr: *mut dirent_t) -> *const libc::c_char; + } + + let size = unsafe { rust_dirent_t_size() }; + let mut buf = Vec::<u8>::with_capacity(size as uint); + let ptr = buf.as_mut_slice().as_mut_ptr() as *mut dirent_t; + + let p = p.to_c_str(); + let dir_ptr = unsafe {opendir(p.as_ptr())}; + + if dir_ptr as uint != 0 { + let mut paths = vec!(); + let mut entry_ptr = 0 as *mut dirent_t; + while unsafe { readdir_r(dir_ptr, ptr, &mut entry_ptr) == 0 } { + if entry_ptr.is_null() { break } + let cstr = unsafe { + CString::new(rust_list_dir_val(entry_ptr), false) + }; + paths.push(Path::new(cstr)); + } + assert_eq!(unsafe { closedir(dir_ptr) }, 0); + Ok(prune(&p, paths)) + } else { + Err(super::last_error()) + } +} + +pub fn unlink(p: &Path) -> IoResult<()> { + let p = p.to_c_str(); + mkerr_libc(unsafe { libc::unlink(p.as_ptr()) }) +} + +pub fn rename(old: &Path, new: &Path) -> IoResult<()> { + let old = old.to_c_str(); + let new = new.to_c_str(); + mkerr_libc(unsafe { + libc::rename(old.as_ptr(), new.as_ptr()) + }) +} + +pub fn chmod(p: &Path, mode: uint) -> IoResult<()> { + let p = p.to_c_str(); + mkerr_libc(retry(|| unsafe { + libc::chmod(p.as_ptr(), mode as libc::mode_t) + })) +} + +pub fn rmdir(p: &Path) -> IoResult<()> { + let p = p.to_c_str(); + mkerr_libc(unsafe { libc::rmdir(p.as_ptr()) }) +} + +pub fn chown(p: &Path, uid: int, gid: int) -> IoResult<()> { + let p = p.to_c_str(); + mkerr_libc(retry(|| unsafe { + libc::chown(p.as_ptr(), uid as libc::uid_t, gid as libc::gid_t) + })) +} + +pub fn readlink(p: &Path) -> IoResult<Path> { + let c_path = p.to_c_str(); + let p = c_path.as_ptr(); + let mut len = unsafe { libc::pathconf(p as *mut _, libc::_PC_NAME_MAX) }; + if len == -1 { + len = 1024; // FIXME: read PATH_MAX from C ffi? + } + let mut buf: Vec<u8> = Vec::with_capacity(len as uint); + match unsafe { + libc::readlink(p, buf.as_ptr() as *mut libc::c_char, + len as libc::size_t) as libc::c_int + } { + -1 => Err(super::last_error()), + n => { + assert!(n > 0); + unsafe { buf.set_len(n as uint); } + Ok(Path::new(buf)) + } + } +} + +pub fn symlink(src: &Path, dst: &Path) -> IoResult<()> { + let src = src.to_c_str(); + let dst = dst.to_c_str(); + mkerr_libc(unsafe { libc::symlink(src.as_ptr(), dst.as_ptr()) }) +} + +pub fn link(src: &Path, dst: &Path) -> IoResult<()> { + let src = src.to_c_str(); + let dst = dst.to_c_str(); + mkerr_libc(unsafe { libc::link(src.as_ptr(), dst.as_ptr()) }) +} + +fn mkstat(stat: &libc::stat) -> FileStat { + // FileStat times are in milliseconds + fn mktime(secs: u64, nsecs: u64) -> u64 { secs * 1000 + nsecs / 1000000 } + + #[cfg(not(any(target_os = "linux", target_os = "android")))] + fn flags(stat: &libc::stat) -> u64 { stat.st_flags as u64 } + #[cfg(any(target_os = "linux", target_os = "android"))] + fn flags(_stat: &libc::stat) -> u64 { 0 } + + #[cfg(not(any(target_os = "linux", target_os = "android")))] + fn gen(stat: &libc::stat) -> u64 { stat.st_gen as u64 } + #[cfg(any(target_os = "linux", target_os = "android"))] + fn gen(_stat: &libc::stat) -> u64 { 0 } + + FileStat { + size: stat.st_size as u64, + kind: match (stat.st_mode as libc::mode_t) & libc::S_IFMT { + libc::S_IFREG => io::TypeFile, + libc::S_IFDIR => io::TypeDirectory, + libc::S_IFIFO => io::TypeNamedPipe, + libc::S_IFBLK => io::TypeBlockSpecial, + libc::S_IFLNK => io::TypeSymlink, + _ => io::TypeUnknown, + }, + perm: FilePermission::from_bits_truncate(stat.st_mode as u32), + created: mktime(stat.st_ctime as u64, stat.st_ctime_nsec as u64), + modified: mktime(stat.st_mtime as u64, stat.st_mtime_nsec as u64), + accessed: mktime(stat.st_atime as u64, stat.st_atime_nsec as u64), + unstable: UnstableFileStat { + device: stat.st_dev as u64, + inode: stat.st_ino as u64, + rdev: stat.st_rdev as u64, + nlink: stat.st_nlink as u64, + uid: stat.st_uid as u64, + gid: stat.st_gid as u64, + blksize: stat.st_blksize as u64, + blocks: stat.st_blocks as u64, + flags: flags(stat), + gen: gen(stat), + }, + } +} + +pub fn stat(p: &Path) -> IoResult<FileStat> { + let p = p.to_c_str(); + let mut stat: libc::stat = unsafe { mem::zeroed() }; + match unsafe { libc::stat(p.as_ptr(), &mut stat) } { + 0 => Ok(mkstat(&stat)), + _ => Err(super::last_error()), + } +} + +pub fn lstat(p: &Path) -> IoResult<FileStat> { + let p = p.to_c_str(); + let mut stat: libc::stat = unsafe { mem::zeroed() }; + match unsafe { libc::lstat(p.as_ptr(), &mut stat) } { + 0 => Ok(mkstat(&stat)), + _ => Err(super::last_error()), + } +} + +pub fn utime(p: &Path, atime: u64, mtime: u64) -> IoResult<()> { + let p = p.to_c_str(); + let buf = libc::utimbuf { + actime: (atime / 1000) as libc::time_t, + modtime: (mtime / 1000) as libc::time_t, + }; + mkerr_libc(unsafe { libc::utime(p.as_ptr(), &buf) }) +} + +#[cfg(test)] +mod tests { + use super::FileDesc; + use libc; + use os; + use prelude::*; + + #[cfg_attr(target_os = "freebsd", ignore)] // hmm, maybe pipes have a tiny buffer + #[test] + fn test_file_desc() { + // Run this test with some pipes so we don't have to mess around with + // opening or closing files. + let os::Pipe { reader, writer } = unsafe { os::pipe().unwrap() }; + let mut reader = FileDesc::new(reader, true); + let mut writer = FileDesc::new(writer, true); + + writer.write(b"test").ok().unwrap(); + let mut buf = [0u8, ..4]; + match reader.read(buf) { + Ok(4) => { + assert_eq!(buf[0], 't' as u8); + assert_eq!(buf[1], 'e' as u8); + assert_eq!(buf[2], 's' as u8); + assert_eq!(buf[3], 't' as u8); + } + r => panic!("invalid read: {}", r), + } + + assert!(writer.read(buf).is_err()); + assert!(reader.write(buf).is_err()); + } +} diff --git a/src/libstd/sys/unix/helper_signal.rs b/src/libstd/sys/unix/helper_signal.rs new file mode 100644 index 00000000000..a806bea2568 --- /dev/null +++ b/src/libstd/sys/unix/helper_signal.rs @@ -0,0 +1,29 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use libc; +use os; + +use sys::fs::FileDesc; + +pub type signal = libc::c_int; + +pub fn new() -> (signal, signal) { + let os::Pipe { reader, writer } = unsafe { os::pipe().unwrap() }; + (reader, writer) +} + +pub fn signal(fd: libc::c_int) { + FileDesc::new(fd, false).write([0]).ok().unwrap(); +} + +pub fn close(fd: libc::c_int) { + let _fd = FileDesc::new(fd, true); +} diff --git a/src/libstd/sys/unix/mod.rs b/src/libstd/sys/unix/mod.rs new file mode 100644 index 00000000000..d3f55d59534 --- /dev/null +++ b/src/libstd/sys/unix/mod.rs @@ -0,0 +1,146 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![allow(missing_doc)] +#![allow(non_camel_case_types)] +#![allow(unused_imports)] +#![allow(dead_code)] +#![allow(unused_unsafe)] +#![allow(unused_mut)] + +extern crate libc; + +use num; +use prelude::*; +use io::{mod, IoResult, IoError}; +use sys_common::mkerr_libc; + +macro_rules! helper_init( (static $name:ident: Helper<$m:ty>) => ( + static $name: Helper<$m> = Helper { + lock: ::rt::mutex::NATIVE_MUTEX_INIT, + chan: ::cell::UnsafeCell { value: 0 as *mut Sender<$m> }, + signal: ::cell::UnsafeCell { value: 0 }, + initialized: ::cell::UnsafeCell { value: false }, + }; +) ) + +pub mod c; +pub mod fs; +pub mod os; +pub mod tcp; +pub mod udp; +pub mod pipe; +pub mod helper_signal; +pub mod process; +pub mod timer; +pub mod tty; + +pub mod addrinfo { + pub use sys_common::net::get_host_addresses; +} + +// FIXME: move these to c module +pub type sock_t = self::fs::fd_t; +pub type wrlen = libc::size_t; +pub type msglen_t = libc::size_t; +pub unsafe fn close_sock(sock: sock_t) { let _ = libc::close(sock); } + +pub fn last_error() -> IoError { + decode_error_detailed(os::errno() as i32) +} + +pub fn last_net_error() -> IoError { + last_error() +} + +extern "system" { + fn gai_strerror(errcode: libc::c_int) -> *const libc::c_char; +} + +pub fn last_gai_error(s: libc::c_int) -> IoError { + use c_str::CString; + + let mut err = decode_error(s); + err.detail = Some(unsafe { + CString::new(gai_strerror(s), false).as_str().unwrap().to_string() + }); + err +} + +/// Convert an `errno` value into a high-level error variant and description. +pub fn decode_error(errno: i32) -> IoError { + // FIXME: this should probably be a bit more descriptive... + let (kind, desc) = match errno { + libc::EOF => (io::EndOfFile, "end of file"), + libc::ECONNREFUSED => (io::ConnectionRefused, "connection refused"), + libc::ECONNRESET => (io::ConnectionReset, "connection reset"), + libc::EPERM | libc::EACCES => + (io::PermissionDenied, "permission denied"), + libc::EPIPE => (io::BrokenPipe, "broken pipe"), + libc::ENOTCONN => (io::NotConnected, "not connected"), + libc::ECONNABORTED => (io::ConnectionAborted, "connection aborted"), + libc::EADDRNOTAVAIL => (io::ConnectionRefused, "address not available"), + libc::EADDRINUSE => (io::ConnectionRefused, "address in use"), + libc::ENOENT => (io::FileNotFound, "no such file or directory"), + libc::EISDIR => (io::InvalidInput, "illegal operation on a directory"), + libc::ENOSYS => (io::IoUnavailable, "function not implemented"), + libc::EINVAL => (io::InvalidInput, "invalid argument"), + libc::ENOTTY => + (io::MismatchedFileTypeForOperation, + "file descriptor is not a TTY"), + libc::ETIMEDOUT => (io::TimedOut, "operation timed out"), + libc::ECANCELED => (io::TimedOut, "operation aborted"), + + // 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") + }; + IoError { kind: kind, desc: desc, detail: None } +} + +pub fn decode_error_detailed(errno: i32) -> IoError { + let mut err = decode_error(errno); + err.detail = Some(os::error_string(errno)); + err +} + +#[inline] +pub fn retry<I: PartialEq + num::One + Neg<I>> (f: || -> I) -> I { + let minus_one = -num::one::<I>(); + loop { + let n = f(); + if n == minus_one && os::errno() == libc::EINTR as int { } + else { return n } + } +} + +pub fn ms_to_timeval(ms: u64) -> libc::timeval { + libc::timeval { + tv_sec: (ms / 1000) as libc::time_t, + tv_usec: ((ms % 1000) * 1000) as libc::suseconds_t, + } +} + +pub fn wouldblock() -> bool { + let err = os::errno(); + err == libc::EWOULDBLOCK as int || err == libc::EAGAIN as int +} + +pub fn set_nonblocking(fd: sock_t, nb: bool) -> IoResult<()> { + let set = nb as libc::c_int; + mkerr_libc(retry(|| unsafe { c::ioctl(fd, c::FIONBIO, &set) })) +} + +// nothing needed on unix platforms +pub fn init_net() {} diff --git a/src/libstd/sys/unix/os.rs b/src/libstd/sys/unix/os.rs new file mode 100644 index 00000000000..4e495f043bc --- /dev/null +++ b/src/libstd/sys/unix/os.rs @@ -0,0 +1,112 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use libc; +use libc::{c_int, c_char}; +use prelude::*; +use io::IoResult; +use sys::fs::FileDesc; + +use os::TMPBUF_SZ; + +/// Returns the platform-specific value of errno +pub fn errno() -> int { + #[cfg(any(target_os = "macos", + target_os = "ios", + target_os = "freebsd"))] + fn errno_location() -> *const c_int { + extern { + fn __error() -> *const c_int; + } + unsafe { + __error() + } + } + + #[cfg(target_os = "dragonfly")] + fn errno_location() -> *const c_int { + extern { + fn __dfly_error() -> *const c_int; + } + unsafe { + __dfly_error() + } + } + + #[cfg(any(target_os = "linux", target_os = "android"))] + fn errno_location() -> *const c_int { + extern { + fn __errno_location() -> *const c_int; + } + unsafe { + __errno_location() + } + } + + unsafe { + (*errno_location()) as int + } +} + +/// Get a detailed string description for the given error number +pub fn error_string(errno: i32) -> String { + #[cfg(any(target_os = "macos", + target_os = "ios", + target_os = "android", + target_os = "freebsd", + target_os = "dragonfly"))] + fn strerror_r(errnum: c_int, buf: *mut c_char, buflen: libc::size_t) + -> c_int { + extern { + fn strerror_r(errnum: c_int, buf: *mut c_char, + buflen: libc::size_t) -> c_int; + } + unsafe { + strerror_r(errnum, buf, buflen) + } + } + + // GNU libc provides a non-compliant version of strerror_r by default + // and requires macros to instead use the POSIX compliant variant. + // So we just use __xpg_strerror_r which is always POSIX compliant + #[cfg(target_os = "linux")] + fn strerror_r(errnum: c_int, buf: *mut c_char, + buflen: libc::size_t) -> c_int { + extern { + fn __xpg_strerror_r(errnum: c_int, + buf: *mut c_char, + buflen: libc::size_t) + -> c_int; + } + unsafe { + __xpg_strerror_r(errnum, buf, buflen) + } + } + + let mut buf = [0 as c_char, ..TMPBUF_SZ]; + + let p = buf.as_mut_ptr(); + unsafe { + if strerror_r(errno as c_int, p, buf.len() as libc::size_t) < 0 { + panic!("strerror_r failure"); + } + + ::string::raw::from_buf(p as *const u8) + } +} + +pub unsafe fn pipe() -> IoResult<(FileDesc, FileDesc)> { + let mut fds = [0, ..2]; + if libc::pipe(fds.as_mut_ptr()) == 0 { + Ok((FileDesc::new(fds[0], true), FileDesc::new(fds[1], true))) + } else { + Err(super::last_error()) + } +} diff --git a/src/libstd/sys/unix/pipe.rs b/src/libstd/sys/unix/pipe.rs new file mode 100644 index 00000000000..67384848a94 --- /dev/null +++ b/src/libstd/sys/unix/pipe.rs @@ -0,0 +1,321 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use alloc::arc::Arc; +use libc; +use c_str::CString; +use mem; +use rt::mutex; +use sync::atomic; +use io::{mod, IoResult, IoError}; +use prelude::*; + +use sys::{mod, timer, retry, c, set_nonblocking, wouldblock}; +use sys::fs::{fd_t, FileDesc}; +use sys_common::net::*; +use sys_common::{eof, mkerr_libc}; + +fn unix_socket(ty: libc::c_int) -> IoResult<fd_t> { + match unsafe { libc::socket(libc::AF_UNIX, ty, 0) } { + -1 => Err(super::last_error()), + fd => Ok(fd) + } +} + +fn addr_to_sockaddr_un(addr: &CString, + storage: &mut libc::sockaddr_storage) + -> IoResult<libc::socklen_t> { + // the sun_path length is limited to SUN_LEN (with null) + assert!(mem::size_of::<libc::sockaddr_storage>() >= + mem::size_of::<libc::sockaddr_un>()); + let s = unsafe { &mut *(storage as *mut _ as *mut libc::sockaddr_un) }; + + let len = addr.len(); + if len > s.sun_path.len() - 1 { + return Err(IoError { + kind: io::InvalidInput, + desc: "invalid argument: path must be smaller than SUN_LEN", + detail: None, + }) + } + s.sun_family = libc::AF_UNIX as libc::sa_family_t; + for (slot, value) in s.sun_path.iter_mut().zip(addr.iter()) { + *slot = value; + } + + // count the null terminator + let len = mem::size_of::<libc::sa_family_t>() + len + 1; + return Ok(len as libc::socklen_t); +} + +struct Inner { + fd: fd_t, + + // Unused on Linux, where this lock is not necessary. + #[allow(dead_code)] + lock: mutex::NativeMutex +} + +impl Inner { + fn new(fd: fd_t) -> Inner { + Inner { fd: fd, lock: unsafe { mutex::NativeMutex::new() } } + } +} + +impl Drop for Inner { + fn drop(&mut self) { unsafe { let _ = libc::close(self.fd); } } +} + +fn connect(addr: &CString, ty: libc::c_int, + timeout: Option<u64>) -> IoResult<Inner> { + let mut storage = unsafe { mem::zeroed() }; + let len = try!(addr_to_sockaddr_un(addr, &mut storage)); + let inner = Inner::new(try!(unix_socket(ty))); + let addrp = &storage as *const _ as *const libc::sockaddr; + + match timeout { + None => { + match retry(|| unsafe { libc::connect(inner.fd, addrp, len) }) { + -1 => Err(super::last_error()), + _ => Ok(inner) + } + } + Some(timeout_ms) => { + try!(connect_timeout(inner.fd, addrp, len, timeout_ms)); + Ok(inner) + } + } +} + +fn bind(addr: &CString, ty: libc::c_int) -> IoResult<Inner> { + let mut storage = unsafe { mem::zeroed() }; + let len = try!(addr_to_sockaddr_un(addr, &mut storage)); + let inner = Inner::new(try!(unix_socket(ty))); + let addrp = &storage as *const _ as *const libc::sockaddr; + match unsafe { + libc::bind(inner.fd, addrp, len) + } { + -1 => Err(super::last_error()), + _ => Ok(inner) + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Unix Streams +//////////////////////////////////////////////////////////////////////////////// + +pub struct UnixStream { + inner: Arc<Inner>, + read_deadline: u64, + write_deadline: u64, +} + +impl UnixStream { + pub fn connect(addr: &CString, + timeout: Option<u64>) -> IoResult<UnixStream> { + connect(addr, libc::SOCK_STREAM, timeout).map(|inner| { + UnixStream::new(Arc::new(inner)) + }) + } + + fn new(inner: Arc<Inner>) -> UnixStream { + UnixStream { + inner: inner, + read_deadline: 0, + write_deadline: 0, + } + } + + fn fd(&self) -> fd_t { self.inner.fd } + + #[cfg(target_os = "linux")] + fn lock_nonblocking(&self) {} + + #[cfg(not(target_os = "linux"))] + fn lock_nonblocking<'a>(&'a self) -> Guard<'a> { + let ret = Guard { + fd: self.fd(), + guard: unsafe { self.inner.lock.lock() }, + }; + assert!(set_nonblocking(self.fd(), true).is_ok()); + ret + } + + pub fn read(&mut self, buf: &mut [u8]) -> IoResult<uint> { + let fd = self.fd(); + let dolock = || self.lock_nonblocking(); + let doread = |nb| unsafe { + let flags = if nb {c::MSG_DONTWAIT} else {0}; + libc::recv(fd, + buf.as_mut_ptr() as *mut libc::c_void, + buf.len() as libc::size_t, + flags) as libc::c_int + }; + read(fd, self.read_deadline, dolock, doread) + } + + pub fn write(&mut self, buf: &[u8]) -> IoResult<()> { + let fd = self.fd(); + let dolock = || self.lock_nonblocking(); + let dowrite = |nb: bool, buf: *const u8, len: uint| unsafe { + let flags = if nb {c::MSG_DONTWAIT} else {0}; + libc::send(fd, + buf as *const _, + len as libc::size_t, + flags) as i64 + }; + match write(fd, self.write_deadline, buf, true, dolock, dowrite) { + Ok(_) => Ok(()), + Err(e) => Err(e) + } + } + + pub fn close_write(&mut self) -> IoResult<()> { + mkerr_libc(unsafe { libc::shutdown(self.fd(), libc::SHUT_WR) }) + } + + pub fn close_read(&mut self) -> IoResult<()> { + mkerr_libc(unsafe { libc::shutdown(self.fd(), libc::SHUT_RD) }) + } + + pub fn set_timeout(&mut self, timeout: Option<u64>) { + let deadline = timeout.map(|a| timer::now() + a).unwrap_or(0); + self.read_deadline = deadline; + self.write_deadline = deadline; + } + + pub fn set_read_timeout(&mut self, timeout: Option<u64>) { + self.read_deadline = timeout.map(|a| timer::now() + a).unwrap_or(0); + } + + pub fn set_write_timeout(&mut self, timeout: Option<u64>) { + self.write_deadline = timeout.map(|a| timer::now() + a).unwrap_or(0); + } +} + +impl Clone for UnixStream { + fn clone(&self) -> UnixStream { + UnixStream::new(self.inner.clone()) + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Unix Listener +//////////////////////////////////////////////////////////////////////////////// + +pub struct UnixListener { + inner: Inner, + path: CString, +} + +impl UnixListener { + pub fn bind(addr: &CString) -> IoResult<UnixListener> { + bind(addr, libc::SOCK_STREAM).map(|fd| { + UnixListener { inner: fd, path: addr.clone() } + }) + } + + fn fd(&self) -> fd_t { self.inner.fd } + + pub fn listen(self) -> IoResult<UnixAcceptor> { + match unsafe { libc::listen(self.fd(), 128) } { + -1 => Err(super::last_error()), + + _ => { + let (reader, writer) = try!(unsafe { sys::os::pipe() }); + try!(set_nonblocking(reader.fd(), true)); + try!(set_nonblocking(writer.fd(), true)); + try!(set_nonblocking(self.fd(), true)); + Ok(UnixAcceptor { + inner: Arc::new(AcceptorInner { + listener: self, + reader: reader, + writer: writer, + closed: atomic::AtomicBool::new(false), + }), + deadline: 0, + }) + } + } + } +} + +pub struct UnixAcceptor { + inner: Arc<AcceptorInner>, + deadline: u64, +} + +struct AcceptorInner { + listener: UnixListener, + reader: FileDesc, + writer: FileDesc, + closed: atomic::AtomicBool, +} + +impl UnixAcceptor { + fn fd(&self) -> fd_t { self.inner.listener.fd() } + + pub fn accept(&mut self) -> IoResult<UnixStream> { + let deadline = if self.deadline == 0 {None} else {Some(self.deadline)}; + + while !self.inner.closed.load(atomic::SeqCst) { + unsafe { + let mut storage: libc::sockaddr_storage = mem::zeroed(); + let storagep = &mut storage as *mut libc::sockaddr_storage; + let size = mem::size_of::<libc::sockaddr_storage>(); + let mut size = size as libc::socklen_t; + match retry(|| { + libc::accept(self.fd(), + storagep as *mut libc::sockaddr, + &mut size as *mut libc::socklen_t) as libc::c_int + }) { + -1 if wouldblock() => {} + -1 => return Err(super::last_error()), + fd => return Ok(UnixStream::new(Arc::new(Inner::new(fd)))), + } + } + try!(await([self.fd(), self.inner.reader.fd()], + deadline, Readable)); + } + + Err(eof()) + } + + pub fn set_timeout(&mut self, timeout: Option<u64>) { + self.deadline = timeout.map(|a| timer::now() + a).unwrap_or(0); + } + + pub fn close_accept(&mut self) -> IoResult<()> { + self.inner.closed.store(true, atomic::SeqCst); + let fd = FileDesc::new(self.inner.writer.fd(), false); + match fd.write([0]) { + Ok(..) => Ok(()), + Err(..) if wouldblock() => Ok(()), + Err(e) => Err(e), + } + } +} + +impl Clone for UnixAcceptor { + fn clone(&self) -> UnixAcceptor { + UnixAcceptor { inner: self.inner.clone(), deadline: 0 } + } +} + +impl Drop for UnixListener { + fn drop(&mut self) { + // Unlink the path to the socket to ensure that it doesn't linger. We're + // careful to unlink the path before we close the file descriptor to + // prevent races where we unlink someone else's path. + unsafe { + let _ = libc::unlink(self.path.as_ptr()); + } + } +} diff --git a/src/libstd/sys/unix/process.rs b/src/libstd/sys/unix/process.rs new file mode 100644 index 00000000000..0965d98d9b0 --- /dev/null +++ b/src/libstd/sys/unix/process.rs @@ -0,0 +1,587 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use libc::{mod, pid_t, c_void, c_int}; +use c_str::CString; +use io::{mod, IoResult, IoError}; +use mem; +use os; +use ptr; +use prelude::*; +use io::process::{ProcessExit, ExitStatus, ExitSignal}; +use collections; +use path::BytesContainer; +use hash::Hash; + +use sys::{mod, retry, c, wouldblock, set_nonblocking, ms_to_timeval}; +use sys::fs::FileDesc; +use sys_common::helper_thread::Helper; +use sys_common::{AsFileDesc, mkerr_libc, timeout}; + +pub use sys_common::ProcessConfig; + +helper_init!(static HELPER: Helper<Req>) + +/// The unique id of the process (this should never be negative). +pub struct Process { + pub pid: pid_t +} + +enum Req { + NewChild(libc::pid_t, Sender<ProcessExit>, u64), +} + +impl Process { + pub fn id(&self) -> pid_t { + self.pid + } + + pub unsafe fn kill(&self, signal: int) -> IoResult<()> { + Process::killpid(self.pid, signal) + } + + pub unsafe fn killpid(pid: pid_t, signal: int) -> IoResult<()> { + let r = libc::funcs::posix88::signal::kill(pid, signal as c_int); + mkerr_libc(r) + } + + pub fn spawn<K, V, C, P>(cfg: &C, in_fd: Option<P>, + out_fd: Option<P>, err_fd: Option<P>) + -> IoResult<Process> + where C: ProcessConfig<K, V>, P: AsFileDesc, + K: BytesContainer + Eq + Hash, V: BytesContainer + { + use libc::funcs::posix88::unistd::{fork, dup2, close, chdir, execvp}; + use libc::funcs::bsd44::getdtablesize; + + mod rustrt { + extern { + pub fn rust_unset_sigprocmask(); + } + } + + #[cfg(target_os = "macos")] + unsafe fn set_environ(envp: *const c_void) { + extern { fn _NSGetEnviron() -> *mut *const c_void; } + + *_NSGetEnviron() = envp; + } + #[cfg(not(target_os = "macos"))] + unsafe fn set_environ(envp: *const c_void) { + extern { static mut environ: *const c_void; } + environ = envp; + } + + unsafe fn set_cloexec(fd: c_int) { + let ret = c::ioctl(fd, c::FIOCLEX); + assert_eq!(ret, 0); + } + + let dirp = cfg.cwd().map(|c| c.as_ptr()).unwrap_or(ptr::null()); + + // temporary until unboxed closures land + let cfg = unsafe { + mem::transmute::<&ProcessConfig<K,V>,&'static ProcessConfig<K,V>>(cfg) + }; + + with_envp(cfg.env(), proc(envp) { + with_argv(cfg.program(), cfg.args(), proc(argv) unsafe { + let (input, mut output) = try!(sys::os::pipe()); + + // We may use this in the child, so perform allocations before the + // fork + let devnull = "/dev/null".to_c_str(); + + set_cloexec(output.fd()); + + let pid = fork(); + if pid < 0 { + return Err(super::last_error()) + } else if pid > 0 { + drop(output); + let mut bytes = [0, ..4]; + return match input.read(bytes) { + Ok(4) => { + let errno = (bytes[0] as i32 << 24) | + (bytes[1] as i32 << 16) | + (bytes[2] as i32 << 8) | + (bytes[3] as i32 << 0); + Err(super::decode_error(errno)) + } + Err(..) => Ok(Process { pid: pid }), + Ok(..) => panic!("short read on the cloexec pipe"), + }; + } + + // And at this point we've reached a special time in the life of the + // child. The child must now be considered hamstrung and unable to + // do anything other than syscalls really. Consider the following + // scenario: + // + // 1. Thread A of process 1 grabs the malloc() mutex + // 2. Thread B of process 1 forks(), creating thread C + // 3. Thread C of process 2 then attempts to malloc() + // 4. The memory of process 2 is the same as the memory of + // process 1, so the mutex is locked. + // + // This situation looks a lot like deadlock, right? It turns out + // that this is what pthread_atfork() takes care of, which is + // presumably implemented across platforms. The first thing that + // threads to *before* forking is to do things like grab the malloc + // mutex, and then after the fork they unlock it. + // + // Despite this information, libnative's spawn has been witnessed to + // deadlock on both OSX and FreeBSD. I'm not entirely sure why, but + // all collected backtraces point at malloc/free traffic in the + // child spawned process. + // + // For this reason, the block of code below should contain 0 + // invocations of either malloc of free (or their related friends). + // + // As an example of not having malloc/free traffic, we don't close + // this file descriptor by dropping the FileDesc (which contains an + // allocation). Instead we just close it manually. This will never + // have the drop glue anyway because this code never returns (the + // child will either exec() or invoke libc::exit) + let _ = libc::close(input.fd()); + + fn fail(output: &mut FileDesc) -> ! { + let errno = sys::os::errno(); + let bytes = [ + (errno >> 24) as u8, + (errno >> 16) as u8, + (errno >> 8) as u8, + (errno >> 0) as u8, + ]; + assert!(output.write(bytes).is_ok()); + unsafe { libc::_exit(1) } + } + + rustrt::rust_unset_sigprocmask(); + + // If a stdio file descriptor is set to be ignored (via a -1 file + // descriptor), then we don't actually close it, but rather open + // up /dev/null into that file descriptor. Otherwise, the first file + // descriptor opened up in the child would be numbered as one of the + // stdio file descriptors, which is likely to wreak havoc. + let setup = |src: Option<P>, dst: c_int| { + let src = match src { + None => { + let flags = if dst == libc::STDIN_FILENO { + libc::O_RDONLY + } else { + libc::O_RDWR + }; + libc::open(devnull.as_ptr(), flags, 0) + } + Some(obj) => { + let fd = obj.as_fd().fd(); + // Leak the memory and the file descriptor. We're in the + // child now an all our resources are going to be + // cleaned up very soon + mem::forget(obj); + fd + } + }; + src != -1 && retry(|| dup2(src, dst)) != -1 + }; + + if !setup(in_fd, libc::STDIN_FILENO) { fail(&mut output) } + if !setup(out_fd, libc::STDOUT_FILENO) { fail(&mut output) } + if !setup(err_fd, libc::STDERR_FILENO) { fail(&mut output) } + + // close all other fds + for fd in range(3, getdtablesize()).rev() { + if fd != output.fd() { + let _ = close(fd as c_int); + } + } + + match cfg.gid() { + Some(u) => { + if libc::setgid(u as libc::gid_t) != 0 { + fail(&mut output); + } + } + None => {} + } + match cfg.uid() { + Some(u) => { + // When dropping privileges from root, the `setgroups` call + // will remove any extraneous groups. If we don't call this, + // then even though our uid has dropped, we may still have + // groups that enable us to do super-user things. This will + // fail if we aren't root, so don't bother checking the + // return value, this is just done as an optimistic + // privilege dropping function. + extern { + fn setgroups(ngroups: libc::c_int, + ptr: *const libc::c_void) -> libc::c_int; + } + let _ = setgroups(0, 0 as *const libc::c_void); + + if libc::setuid(u as libc::uid_t) != 0 { + fail(&mut output); + } + } + None => {} + } + if cfg.detach() { + // Don't check the error of setsid because it fails if we're the + // process leader already. We just forked so it shouldn't return + // error, but ignore it anyway. + let _ = libc::setsid(); + } + if !dirp.is_null() && chdir(dirp) == -1 { + fail(&mut output); + } + if !envp.is_null() { + set_environ(envp); + } + let _ = execvp(*argv, argv as *mut _); + fail(&mut output); + }) + }) + } + + pub fn wait(&self, deadline: u64) -> IoResult<ProcessExit> { + use std::cmp; + use std::comm; + + static mut WRITE_FD: libc::c_int = 0; + + let mut status = 0 as c_int; + if deadline == 0 { + return match retry(|| unsafe { c::waitpid(self.pid, &mut status, 0) }) { + -1 => panic!("unknown waitpid error: {}", super::last_error()), + _ => Ok(translate_status(status)), + } + } + + // On unix, wait() and its friends have no timeout parameters, so there is + // no way to time out a thread in wait(). From some googling and some + // thinking, it appears that there are a few ways to handle timeouts in + // wait(), but the only real reasonable one for a multi-threaded program is + // to listen for SIGCHLD. + // + // With this in mind, the waiting mechanism with a timeout barely uses + // waitpid() at all. There are a few times that waitpid() is invoked with + // WNOHANG, but otherwise all the necessary blocking is done by waiting for + // a SIGCHLD to arrive (and that blocking has a timeout). Note, however, + // that waitpid() is still used to actually reap the child. + // + // Signal handling is super tricky in general, and this is no exception. Due + // to the async nature of SIGCHLD, we use the self-pipe trick to transmit + // data out of the signal handler to the rest of the application. The first + // idea would be to have each thread waiting with a timeout to read this + // output file descriptor, but a write() is akin to a signal(), not a + // broadcast(), so it would only wake up one thread, and possibly the wrong + // thread. Hence a helper thread is used. + // + // The helper thread here is responsible for farming requests for a + // waitpid() with a timeout, and then processing all of the wait requests. + // By guaranteeing that only this helper thread is reading half of the + // self-pipe, we're sure that we'll never lose a SIGCHLD. This helper thread + // is also responsible for select() to wait for incoming messages or + // incoming SIGCHLD messages, along with passing an appropriate timeout to + // select() to wake things up as necessary. + // + // The ordering of the following statements is also very purposeful. First, + // we must be guaranteed that the helper thread is booted and available to + // receive SIGCHLD signals, and then we must also ensure that we do a + // nonblocking waitpid() at least once before we go ask the sigchld helper. + // This prevents the race where the child exits, we boot the helper, and + // then we ask for the child's exit status (never seeing a sigchld). + // + // The actual communication between the helper thread and this thread is + // quite simple, just a channel moving data around. + + unsafe { HELPER.boot(register_sigchld, waitpid_helper) } + + match self.try_wait() { + Some(ret) => return Ok(ret), + None => {} + } + + let (tx, rx) = channel(); + unsafe { HELPER.send(NewChild(self.pid, tx, deadline)); } + return match rx.recv_opt() { + Ok(e) => Ok(e), + Err(()) => Err(timeout("wait timed out")), + }; + + // Register a new SIGCHLD handler, returning the reading half of the + // self-pipe plus the old handler registered (return value of sigaction). + // + // Be sure to set up the self-pipe first because as soon as we register a + // handler we're going to start receiving signals. + fn register_sigchld() -> (libc::c_int, c::sigaction) { + unsafe { + let mut pipes = [0, ..2]; + assert_eq!(libc::pipe(pipes.as_mut_ptr()), 0); + set_nonblocking(pipes[0], true).ok().unwrap(); + set_nonblocking(pipes[1], true).ok().unwrap(); + WRITE_FD = pipes[1]; + + let mut old: c::sigaction = mem::zeroed(); + let mut new: c::sigaction = mem::zeroed(); + new.sa_handler = sigchld_handler; + new.sa_flags = c::SA_NOCLDSTOP; + assert_eq!(c::sigaction(c::SIGCHLD, &new, &mut old), 0); + (pipes[0], old) + } + } + + // Helper thread for processing SIGCHLD messages + fn waitpid_helper(input: libc::c_int, + messages: Receiver<Req>, + (read_fd, old): (libc::c_int, c::sigaction)) { + set_nonblocking(input, true).ok().unwrap(); + let mut set: c::fd_set = unsafe { mem::zeroed() }; + let mut tv: libc::timeval; + let mut active = Vec::<(libc::pid_t, Sender<ProcessExit>, u64)>::new(); + let max = cmp::max(input, read_fd) + 1; + + 'outer: loop { + // Figure out the timeout of our syscall-to-happen. If we're waiting + // for some processes, then they'll have a timeout, otherwise we + // wait indefinitely for a message to arrive. + // + // FIXME: sure would be nice to not have to scan the entire array + let min = active.iter().map(|a| *a.ref2()).enumerate().min_by(|p| { + p.val1() + }); + let (p, idx) = match min { + Some((idx, deadline)) => { + let now = sys::timer::now(); + let ms = if now < deadline {deadline - now} else {0}; + tv = ms_to_timeval(ms); + (&mut tv as *mut _, idx) + } + None => (ptr::null_mut(), -1), + }; + + // Wait for something to happen + c::fd_set(&mut set, input); + c::fd_set(&mut set, read_fd); + match unsafe { c::select(max, &mut set, ptr::null_mut(), + ptr::null_mut(), p) } { + // interrupted, retry + -1 if os::errno() == libc::EINTR as uint => continue, + + // We read something, break out and process + 1 | 2 => {} + + // Timeout, the pending request is removed + 0 => { + drop(active.remove(idx)); + continue + } + + n => panic!("error in select {} ({})", os::errno(), n), + } + + // Process any pending messages + if drain(input) { + loop { + match messages.try_recv() { + Ok(NewChild(pid, tx, deadline)) => { + active.push((pid, tx, deadline)); + } + Err(comm::Disconnected) => { + assert!(active.len() == 0); + break 'outer; + } + Err(comm::Empty) => break, + } + } + } + + // If a child exited (somehow received SIGCHLD), then poll all + // children to see if any of them exited. + // + // We also attempt to be responsible netizens when dealing with + // SIGCHLD by invoking any previous SIGCHLD handler instead of just + // ignoring any previous SIGCHLD handler. Note that we don't provide + // a 1:1 mapping of our handler invocations to the previous handler + // invocations because we drain the `read_fd` entirely. This is + // probably OK because the kernel is already allowed to coalesce + // simultaneous signals, we're just doing some extra coalescing. + // + // Another point of note is that this likely runs the signal handler + // on a different thread than the one that received the signal. I + // *think* this is ok at this time. + // + // The main reason for doing this is to allow stdtest to run native + // tests as well. Both libgreen and libnative are running around + // with process timeouts, but libgreen should get there first + // (currently libuv doesn't handle old signal handlers). + if drain(read_fd) { + let i: uint = unsafe { mem::transmute(old.sa_handler) }; + if i != 0 { + assert!(old.sa_flags & c::SA_SIGINFO == 0); + (old.sa_handler)(c::SIGCHLD); + } + + // FIXME: sure would be nice to not have to scan the entire + // array... + active.retain(|&(pid, ref tx, _)| { + let pr = Process { pid: pid }; + match pr.try_wait() { + Some(msg) => { tx.send(msg); false } + None => true, + } + }); + } + } + + // Once this helper thread is done, we re-register the old sigchld + // handler and close our intermediate file descriptors. + unsafe { + assert_eq!(c::sigaction(c::SIGCHLD, &old, ptr::null_mut()), 0); + let _ = libc::close(read_fd); + let _ = libc::close(WRITE_FD); + WRITE_FD = -1; + } + } + + // Drain all pending data from the file descriptor, returning if any data + // could be drained. This requires that the file descriptor is in + // nonblocking mode. + fn drain(fd: libc::c_int) -> bool { + let mut ret = false; + loop { + let mut buf = [0u8, ..1]; + match unsafe { + libc::read(fd, buf.as_mut_ptr() as *mut libc::c_void, + buf.len() as libc::size_t) + } { + n if n > 0 => { ret = true; } + 0 => return true, + -1 if wouldblock() => return ret, + n => panic!("bad read {} ({})", os::last_os_error(), n), + } + } + } + + // Signal handler for SIGCHLD signals, must be async-signal-safe! + // + // This function will write to the writing half of the "self pipe" to wake + // up the helper thread if it's waiting. Note that this write must be + // nonblocking because if it blocks and the reader is the thread we + // interrupted, then we'll deadlock. + // + // When writing, if the write returns EWOULDBLOCK then we choose to ignore + // it. At that point we're guaranteed that there's something in the pipe + // which will wake up the other end at some point, so we just allow this + // signal to be coalesced with the pending signals on the pipe. + extern fn sigchld_handler(_signum: libc::c_int) { + let msg = 1i; + match unsafe { + libc::write(WRITE_FD, &msg as *const _ as *const libc::c_void, 1) + } { + 1 => {} + -1 if wouldblock() => {} // see above comments + n => panic!("bad error on write fd: {} {}", n, os::errno()), + } + } + } + + pub fn try_wait(&self) -> Option<ProcessExit> { + let mut status = 0 as c_int; + match retry(|| unsafe { + c::waitpid(self.pid, &mut status, c::WNOHANG) + }) { + n if n == self.pid => Some(translate_status(status)), + 0 => None, + n => panic!("unknown waitpid error `{}`: {}", n, + super::last_error()), + } + } +} + +fn with_argv<T>(prog: &CString, args: &[CString], + cb: proc(*const *const libc::c_char) -> T) -> T { + let mut ptrs: Vec<*const libc::c_char> = Vec::with_capacity(args.len()+1); + + // Convert the CStrings into an array of pointers. Note: the + // lifetime of the various CStrings involved is guaranteed to be + // larger than the lifetime of our invocation of cb, but this is + // technically unsafe as the callback could leak these pointers + // out of our scope. + ptrs.push(prog.as_ptr()); + ptrs.extend(args.iter().map(|tmp| tmp.as_ptr())); + + // Add a terminating null pointer (required by libc). + ptrs.push(ptr::null()); + + cb(ptrs.as_ptr()) +} + +fn with_envp<K, V, T>(env: Option<&collections::HashMap<K, V>>, + cb: proc(*const c_void) -> T) -> T + where K: BytesContainer + Eq + Hash, V: BytesContainer +{ + // On posixy systems we can pass a char** for envp, which is a + // null-terminated array of "k=v\0" strings. Since we must create + // these strings locally, yet expose a raw pointer to them, we + // create a temporary vector to own the CStrings that outlives the + // call to cb. + match env { + Some(env) => { + let mut tmps = Vec::with_capacity(env.len()); + + for pair in env.iter() { + let mut kv = Vec::new(); + kv.push_all(pair.ref0().container_as_bytes()); + kv.push('=' as u8); + kv.push_all(pair.ref1().container_as_bytes()); + kv.push(0); // terminating null + tmps.push(kv); + } + + // As with `with_argv`, this is unsafe, since cb could leak the pointers. + let mut ptrs: Vec<*const libc::c_char> = + tmps.iter() + .map(|tmp| tmp.as_ptr() as *const libc::c_char) + .collect(); + ptrs.push(ptr::null()); + + cb(ptrs.as_ptr() as *const c_void) + } + _ => cb(ptr::null()) + } +} + +fn translate_status(status: c_int) -> ProcessExit { + #![allow(non_snake_case)] + #[cfg(any(target_os = "linux", target_os = "android"))] + mod imp { + pub fn WIFEXITED(status: i32) -> bool { (status & 0xff) == 0 } + pub fn WEXITSTATUS(status: i32) -> i32 { (status >> 8) & 0xff } + pub fn WTERMSIG(status: i32) -> i32 { status & 0x7f } + } + + #[cfg(any(target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "dragonfly"))] + mod imp { + pub fn WIFEXITED(status: i32) -> bool { (status & 0x7f) == 0 } + pub fn WEXITSTATUS(status: i32) -> i32 { status >> 8 } + pub fn WTERMSIG(status: i32) -> i32 { status & 0o177 } + } + + if imp::WIFEXITED(status) { + ExitStatus(imp::WEXITSTATUS(status) as int) + } else { + ExitSignal(imp::WTERMSIG(status) as int) + } +} diff --git a/src/libstd/sys/unix/tcp.rs b/src/libstd/sys/unix/tcp.rs new file mode 100644 index 00000000000..962475e4177 --- /dev/null +++ b/src/libstd/sys/unix/tcp.rs @@ -0,0 +1,157 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use io::net::ip; +use io::IoResult; +use libc; +use mem; +use ptr; +use prelude::*; +use super::{last_error, last_net_error, retry, sock_t}; +use sync::{Arc, atomic}; +use sys::fs::FileDesc; +use sys::{set_nonblocking, wouldblock}; +use sys; +use sys_common; +use sys_common::net::*; + +pub use sys_common::net::TcpStream; + +//////////////////////////////////////////////////////////////////////////////// +// TCP listeners +//////////////////////////////////////////////////////////////////////////////// + +pub struct TcpListener { + pub inner: FileDesc, +} + +impl TcpListener { + pub fn bind(addr: ip::SocketAddr) -> IoResult<TcpListener> { + let fd = try!(socket(addr, libc::SOCK_STREAM)); + let ret = TcpListener { inner: FileDesc::new(fd, true) }; + + let mut storage = unsafe { mem::zeroed() }; + let len = addr_to_sockaddr(addr, &mut storage); + let addrp = &storage as *const _ as *const libc::sockaddr; + + // On platforms with Berkeley-derived sockets, this allows + // to quickly rebind a socket, without needing to wait for + // the OS to clean up the previous one. + try!(setsockopt(fd, libc::SOL_SOCKET, libc::SO_REUSEADDR, 1 as libc::c_int)); + + + match unsafe { libc::bind(fd, addrp, len) } { + -1 => Err(last_error()), + _ => Ok(ret), + } + } + + pub fn fd(&self) -> sock_t { self.inner.fd() } + + pub fn listen(self, backlog: int) -> IoResult<TcpAcceptor> { + match unsafe { libc::listen(self.fd(), backlog as libc::c_int) } { + -1 => Err(last_net_error()), + _ => { + let (reader, writer) = try!(unsafe { sys::os::pipe() }); + try!(set_nonblocking(reader.fd(), true)); + try!(set_nonblocking(writer.fd(), true)); + try!(set_nonblocking(self.fd(), true)); + Ok(TcpAcceptor { + inner: Arc::new(AcceptorInner { + listener: self, + reader: reader, + writer: writer, + closed: atomic::AtomicBool::new(false), + }), + deadline: 0, + }) + } + } + } + + pub fn socket_name(&mut self) -> IoResult<ip::SocketAddr> { + sockname(self.fd(), libc::getsockname) + } +} + +pub struct TcpAcceptor { + inner: Arc<AcceptorInner>, + deadline: u64, +} + +struct AcceptorInner { + listener: TcpListener, + reader: FileDesc, + writer: FileDesc, + closed: atomic::AtomicBool, +} + +impl TcpAcceptor { + pub fn fd(&self) -> sock_t { self.inner.listener.fd() } + + pub fn accept(&mut self) -> IoResult<TcpStream> { + // In implementing accept, the two main concerns are dealing with + // close_accept() and timeouts. The unix implementation is based on a + // nonblocking accept plus a call to select(). Windows ends up having + // an entirely separate implementation than unix, which is explained + // below. + // + // To implement timeouts, all blocking is done via select() instead of + // accept() by putting the socket in non-blocking mode. Because + // select() takes a timeout argument, we just pass through the timeout + // to select(). + // + // To implement close_accept(), we have a self-pipe to ourselves which + // is passed to select() along with the socket being accepted on. The + // self-pipe is never written to unless close_accept() is called. + let deadline = if self.deadline == 0 {None} else {Some(self.deadline)}; + + while !self.inner.closed.load(atomic::SeqCst) { + match retry(|| unsafe { + libc::accept(self.fd(), ptr::null_mut(), ptr::null_mut()) + }) { + -1 if wouldblock() => {} + -1 => return Err(last_net_error()), + fd => return Ok(TcpStream::new(fd as sock_t)), + } + try!(await([self.fd(), self.inner.reader.fd()], + deadline, Readable)); + } + + Err(sys_common::eof()) + } + + pub fn socket_name(&mut self) -> IoResult<ip::SocketAddr> { + sockname(self.fd(), libc::getsockname) + } + + pub fn set_timeout(&mut self, timeout: Option<u64>) { + self.deadline = timeout.map(|a| sys::timer::now() + a).unwrap_or(0); + } + + pub fn close_accept(&mut self) -> IoResult<()> { + self.inner.closed.store(true, atomic::SeqCst); + let fd = FileDesc::new(self.inner.writer.fd(), false); + match fd.write([0]) { + Ok(..) => Ok(()), + Err(..) if wouldblock() => Ok(()), + Err(e) => Err(e), + } + } +} + +impl Clone for TcpAcceptor { + fn clone(&self) -> TcpAcceptor { + TcpAcceptor { + inner: self.inner.clone(), + deadline: 0, + } + } +} diff --git a/src/libstd/sys/unix/timer.rs b/src/libstd/sys/unix/timer.rs new file mode 100644 index 00000000000..a1e6ac3db7e --- /dev/null +++ b/src/libstd/sys/unix/timer.rs @@ -0,0 +1,282 @@ +// 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. + +//! Timers for non-Linux/non-Windows OSes +//! +//! This module implements timers with a worker thread, select(), and a lot of +//! witchcraft that turns out to be horribly inaccurate timers. The unfortunate +//! part is that I'm at a loss of what else to do one these OSes. This is also +//! why Linux has a specialized timerfd implementation and windows has its own +//! implementation (they're more accurate than this one). +//! +//! The basic idea is that there is a worker thread that's communicated to via a +//! channel and a pipe, the pipe is used by the worker thread in a select() +//! syscall with a timeout. The timeout is the "next timer timeout" while the +//! channel is used to send data over to the worker thread. +//! +//! Whenever the call to select() times out, then a channel receives a message. +//! Whenever the call returns that the file descriptor has information, then the +//! channel from timers is drained, enqueuing all incoming requests. +//! +//! The actual implementation of the helper thread is a sorted array of +//! timers in terms of target firing date. The target is the absolute time at +//! which the timer should fire. Timers are then re-enqueued after a firing if +//! the repeat boolean is set. +//! +//! Naturally, all this logic of adding times and keeping track of +//! relative/absolute time is a little lossy and not quite exact. I've done the +//! best I could to reduce the amount of calls to 'now()', but there's likely +//! still inaccuracies trickling in here and there. +//! +//! One of the tricky parts of this implementation is that whenever a timer is +//! acted upon, it must cancel whatever the previous action was (if one is +//! active) in order to act like the other implementations of this timer. In +//! order to do this, the timer's inner pointer is transferred to the worker +//! thread. Whenever the timer is modified, it first takes ownership back from +//! the worker thread in order to modify the same data structure. This has the +//! side effect of "cancelling" the previous requests while allowing a +//! re-enqueuing later on. +//! +//! Note that all time units in this file are in *milliseconds*. + +use libc; +use mem; +use os; +use ptr; +use sync::atomic; +use comm; +use sys::c; +use sys::fs::FileDesc; +use sys_common::helper_thread::Helper; +use prelude::*; +use io::IoResult; + +helper_init!(static HELPER: Helper<Req>) + +pub trait Callback { + fn call(&mut self); +} + +pub struct Timer { + id: uint, + inner: Option<Box<Inner>>, +} + +pub struct Inner { + cb: Option<Box<Callback + Send>>, + interval: u64, + repeat: bool, + target: u64, + id: uint, +} + +pub enum Req { + // Add a new timer to the helper thread. + NewTimer(Box<Inner>), + + // Remove a timer based on its id and then send it back on the channel + // provided + RemoveTimer(uint, Sender<Box<Inner>>), +} + +// returns the current time (in milliseconds) +pub fn now() -> u64 { + unsafe { + let mut now: libc::timeval = mem::zeroed(); + assert_eq!(c::gettimeofday(&mut now, ptr::null_mut()), 0); + return (now.tv_sec as u64) * 1000 + (now.tv_usec as u64) / 1000; + } +} + +fn helper(input: libc::c_int, messages: Receiver<Req>, _: ()) { + let mut set: c::fd_set = unsafe { mem::zeroed() }; + + let mut fd = FileDesc::new(input, true); + let mut timeout: libc::timeval = unsafe { mem::zeroed() }; + + // active timers are those which are able to be selected upon (and it's a + // sorted list, and dead timers are those which have expired, but ownership + // hasn't yet been transferred back to the timer itself. + let mut active: Vec<Box<Inner>> = vec![]; + let mut dead = vec![]; + + // inserts a timer into an array of timers (sorted by firing time) + fn insert(t: Box<Inner>, active: &mut Vec<Box<Inner>>) { + match active.iter().position(|tm| tm.target > t.target) { + Some(pos) => { active.insert(pos, t); } + None => { active.push(t); } + } + } + + // signals the first requests in the queue, possible re-enqueueing it. + fn signal(active: &mut Vec<Box<Inner>>, + dead: &mut Vec<(uint, Box<Inner>)>) { + let mut timer = match active.remove(0) { + Some(timer) => timer, None => return + }; + let mut cb = timer.cb.take().unwrap(); + cb.call(); + if timer.repeat { + timer.cb = Some(cb); + timer.target += timer.interval; + insert(timer, active); + } else { + dead.push((timer.id, timer)); + } + } + + 'outer: loop { + let timeout = if active.len() == 0 { + // Empty array? no timeout (wait forever for the next request) + ptr::null_mut() + } else { + let now = now(); + // If this request has already expired, then signal it and go + // through another iteration + if active[0].target <= now { + signal(&mut active, &mut dead); + continue; + } + + // The actual timeout listed in the requests array is an + // absolute date, so here we translate the absolute time to a + // relative time. + let tm = active[0].target - now; + timeout.tv_sec = (tm / 1000) as libc::time_t; + timeout.tv_usec = ((tm % 1000) * 1000) as libc::suseconds_t; + &mut timeout as *mut libc::timeval + }; + + c::fd_set(&mut set, input); + match unsafe { + c::select(input + 1, &mut set, ptr::null_mut(), + ptr::null_mut(), timeout) + } { + // timed out + 0 => signal(&mut active, &mut dead), + + // file descriptor write woke us up, we've got some new requests + 1 => { + loop { + match messages.try_recv() { + Err(comm::Disconnected) => { + assert!(active.len() == 0); + break 'outer; + } + + Ok(NewTimer(timer)) => insert(timer, &mut active), + + Ok(RemoveTimer(id, ack)) => { + match dead.iter().position(|&(i, _)| id == i) { + Some(i) => { + let (_, i) = dead.remove(i).unwrap(); + ack.send(i); + continue + } + None => {} + } + let i = active.iter().position(|i| i.id == id); + let i = i.expect("no timer found"); + let t = active.remove(i).unwrap(); + ack.send(t); + } + Err(..) => break + } + } + + // drain the file descriptor + let mut buf = [0]; + assert_eq!(fd.read(buf).ok().unwrap(), 1); + } + + -1 if os::errno() == libc::EINTR as uint => {} + n => panic!("helper thread failed in select() with error: {} ({})", + n, os::last_os_error()) + } + } +} + +impl Timer { + pub fn new() -> IoResult<Timer> { + // See notes above regarding using int return value + // instead of () + HELPER.boot(|| {}, helper); + + static ID: atomic::AtomicUint = atomic::INIT_ATOMIC_UINT; + let id = ID.fetch_add(1, atomic::Relaxed); + Ok(Timer { + id: id, + inner: Some(box Inner { + cb: None, + interval: 0, + target: 0, + repeat: false, + id: id, + }) + }) + } + + pub fn sleep(&mut self, ms: u64) { + let mut inner = self.inner(); + inner.cb = None; // cancel any previous request + self.inner = Some(inner); + + let mut to_sleep = libc::timespec { + tv_sec: (ms / 1000) as libc::time_t, + tv_nsec: ((ms % 1000) * 1000000) as libc::c_long, + }; + while unsafe { libc::nanosleep(&to_sleep, &mut to_sleep) } != 0 { + if os::errno() as int != libc::EINTR as int { + panic!("failed to sleep, but not because of EINTR?"); + } + } + } + + pub fn oneshot(&mut self, msecs: u64, cb: Box<Callback + Send>) { + let now = now(); + let mut inner = self.inner(); + + inner.repeat = false; + inner.cb = Some(cb); + inner.interval = msecs; + inner.target = now + msecs; + + HELPER.send(NewTimer(inner)); + } + + pub fn period(&mut self, msecs: u64, cb: Box<Callback + Send>) { + let now = now(); + let mut inner = self.inner(); + + inner.repeat = true; + inner.cb = Some(cb); + inner.interval = msecs; + inner.target = now + msecs; + + HELPER.send(NewTimer(inner)); + } + + fn inner(&mut self) -> Box<Inner> { + match self.inner.take() { + Some(i) => i, + None => { + let (tx, rx) = channel(); + HELPER.send(RemoveTimer(self.id, tx)); + rx.recv() + } + } + } +} + +impl Drop for Timer { + fn drop(&mut self) { + self.inner = Some(self.inner()); + } +} diff --git a/src/libstd/sys/unix/tty.rs b/src/libstd/sys/unix/tty.rs new file mode 100644 index 00000000000..28c17fd4966 --- /dev/null +++ b/src/libstd/sys/unix/tty.rs @@ -0,0 +1,47 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use sys::fs::FileDesc; +use prelude::*; +use libc::{mod, c_int}; +use io::{mod, IoResult, IoError}; +use sys_common; + +pub struct TTY { + pub fd: FileDesc, +} + +impl TTY { + pub fn new(fd: c_int) -> IoResult<TTY> { + if unsafe { libc::isatty(fd) } != 0 { + Ok(TTY { fd: FileDesc::new(fd, true) }) + } else { + Err(IoError { + kind: io::MismatchedFileTypeForOperation, + desc: "file descriptor is not a TTY", + detail: None, + }) + } + } + + pub fn read(&mut self, buf: &mut [u8]) -> IoResult<uint> { + self.fd.read(buf) + } + pub fn write(&mut self, buf: &[u8]) -> IoResult<()> { + self.fd.write(buf) + } + pub fn set_raw(&mut self, _raw: bool) -> IoResult<()> { + Err(sys_common::unimpl()) + } + pub fn get_winsize(&mut self) -> IoResult<(int, int)> { + Err(sys_common::unimpl()) + } + pub fn isatty(&self) -> bool { false } +} diff --git a/src/libstd/sys/unix/udp.rs b/src/libstd/sys/unix/udp.rs new file mode 100644 index 00000000000..50f8fb828ad --- /dev/null +++ b/src/libstd/sys/unix/udp.rs @@ -0,0 +1,11 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +pub use sys_common::net::UdpSocket; diff --git a/src/libstd/sys/windows/c.rs b/src/libstd/sys/windows/c.rs new file mode 100644 index 00000000000..b8e9b1dca3a --- /dev/null +++ b/src/libstd/sys/windows/c.rs @@ -0,0 +1,250 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! C definitions used by libnative that don't belong in liblibc + +#![allow(overflowing_literals)] +#![allow(dead_code)] +#![allow(non_camel_case_types)] + +use libc; +use prelude::*; + +pub const WSADESCRIPTION_LEN: uint = 256; +pub const WSASYS_STATUS_LEN: uint = 128; +pub const FIONBIO: libc::c_long = 0x8004667e; +pub const FD_SETSIZE: uint = 64; +pub const MSG_DONTWAIT: libc::c_int = 0; +pub const ERROR_ILLEGAL_CHARACTER: libc::c_int = 582; +pub const ENABLE_ECHO_INPUT: libc::DWORD = 0x4; +pub const ENABLE_EXTENDED_FLAGS: libc::DWORD = 0x80; +pub const ENABLE_INSERT_MODE: libc::DWORD = 0x20; +pub const ENABLE_LINE_INPUT: libc::DWORD = 0x2; +pub const ENABLE_PROCESSED_INPUT: libc::DWORD = 0x1; +pub const ENABLE_QUICK_EDIT_MODE: libc::DWORD = 0x40; +pub const WSA_INVALID_EVENT: WSAEVENT = 0 as WSAEVENT; + +pub const FD_ACCEPT: libc::c_long = 0x08; +pub const FD_MAX_EVENTS: uint = 10; +pub const WSA_INFINITE: libc::DWORD = libc::INFINITE; +pub const WSA_WAIT_TIMEOUT: libc::DWORD = libc::consts::os::extra::WAIT_TIMEOUT; +pub const WSA_WAIT_EVENT_0: libc::DWORD = libc::consts::os::extra::WAIT_OBJECT_0; +pub const WSA_WAIT_FAILED: libc::DWORD = libc::consts::os::extra::WAIT_FAILED; + +#[repr(C)] +#[cfg(target_arch = "x86")] +pub struct WSADATA { + pub wVersion: libc::WORD, + pub wHighVersion: libc::WORD, + pub szDescription: [u8, ..WSADESCRIPTION_LEN + 1], + pub szSystemStatus: [u8, ..WSASYS_STATUS_LEN + 1], + pub iMaxSockets: u16, + pub iMaxUdpDg: u16, + pub lpVendorInfo: *mut u8, +} +#[repr(C)] +#[cfg(target_arch = "x86_64")] +pub struct WSADATA { + pub wVersion: libc::WORD, + pub wHighVersion: libc::WORD, + pub iMaxSockets: u16, + pub iMaxUdpDg: u16, + pub lpVendorInfo: *mut u8, + pub szDescription: [u8, ..WSADESCRIPTION_LEN + 1], + pub szSystemStatus: [u8, ..WSASYS_STATUS_LEN + 1], +} + +pub type LPWSADATA = *mut WSADATA; + +#[repr(C)] +pub struct WSANETWORKEVENTS { + pub lNetworkEvents: libc::c_long, + pub iErrorCode: [libc::c_int, ..FD_MAX_EVENTS], +} + +pub type LPWSANETWORKEVENTS = *mut WSANETWORKEVENTS; + +pub type WSAEVENT = libc::HANDLE; + +#[repr(C)] +pub struct fd_set { + fd_count: libc::c_uint, + fd_array: [libc::SOCKET, ..FD_SETSIZE], +} + +pub fn fd_set(set: &mut fd_set, s: libc::SOCKET) { + set.fd_array[set.fd_count as uint] = s; + set.fd_count += 1; +} + +#[link(name = "ws2_32")] +extern "system" { + pub fn WSAStartup(wVersionRequested: libc::WORD, + lpWSAData: LPWSADATA) -> libc::c_int; + pub fn WSAGetLastError() -> libc::c_int; + pub fn WSACloseEvent(hEvent: WSAEVENT) -> libc::BOOL; + pub fn WSACreateEvent() -> WSAEVENT; + pub fn WSAEventSelect(s: libc::SOCKET, + hEventObject: WSAEVENT, + lNetworkEvents: libc::c_long) -> libc::c_int; + pub fn WSASetEvent(hEvent: WSAEVENT) -> libc::BOOL; + pub fn WSAWaitForMultipleEvents(cEvents: libc::DWORD, + lphEvents: *const WSAEVENT, + fWaitAll: libc::BOOL, + dwTimeout: libc::DWORD, + fAltertable: libc::BOOL) -> libc::DWORD; + pub fn WSAEnumNetworkEvents(s: libc::SOCKET, + hEventObject: WSAEVENT, + lpNetworkEvents: LPWSANETWORKEVENTS) + -> libc::c_int; + + pub fn ioctlsocket(s: libc::SOCKET, cmd: libc::c_long, + argp: *mut libc::c_ulong) -> libc::c_int; + pub fn select(nfds: libc::c_int, + readfds: *mut fd_set, + writefds: *mut fd_set, + exceptfds: *mut fd_set, + timeout: *mut libc::timeval) -> libc::c_int; + pub fn getsockopt(sockfd: libc::SOCKET, + level: libc::c_int, + optname: libc::c_int, + optval: *mut libc::c_char, + optlen: *mut libc::c_int) -> libc::c_int; + + pub fn SetEvent(hEvent: libc::HANDLE) -> libc::BOOL; + pub fn WaitForMultipleObjects(nCount: libc::DWORD, + lpHandles: *const libc::HANDLE, + bWaitAll: libc::BOOL, + dwMilliseconds: libc::DWORD) -> libc::DWORD; + + pub fn CancelIo(hFile: libc::HANDLE) -> libc::BOOL; + pub fn CancelIoEx(hFile: libc::HANDLE, + lpOverlapped: libc::LPOVERLAPPED) -> libc::BOOL; +} + +pub mod compat { + use intrinsics::{atomic_store_relaxed, transmute}; + use iter::Iterator; + use libc::types::os::arch::extra::{LPCWSTR, HMODULE, LPCSTR, LPVOID}; + use prelude::*; + + extern "system" { + fn GetModuleHandleW(lpModuleName: LPCWSTR) -> HMODULE; + fn GetProcAddress(hModule: HMODULE, lpProcName: LPCSTR) -> LPVOID; + } + + // store_func() is idempotent, so using relaxed ordering for the atomics + // should be enough. This way, calling a function in this compatibility + // layer (after it's loaded) shouldn't be any slower than a regular DLL + // call. + unsafe fn store_func(ptr: *mut uint, module: &str, symbol: &str, fallback: uint) { + let mut module: Vec<u16> = module.utf16_units().collect(); + module.push(0); + symbol.with_c_str(|symbol| { + let handle = GetModuleHandleW(module.as_ptr()); + let func: uint = transmute(GetProcAddress(handle, symbol)); + atomic_store_relaxed(ptr, if func == 0 { + fallback + } else { + func + }) + }) + } + + /// Macro for creating a compatibility fallback for a Windows function + /// + /// # Example + /// ``` + /// compat_fn!(adll32::SomeFunctionW(_arg: LPCWSTR) { + /// // Fallback implementation + /// }) + /// ``` + /// + /// Note that arguments unused by the fallback implementation should not be called `_` as + /// they are used to be passed to the real function if available. + macro_rules! compat_fn( + ($module:ident::$symbol:ident($($argname:ident: $argtype:ty),*) + -> $rettype:ty $fallback:block) => ( + #[inline(always)] + pub unsafe fn $symbol($($argname: $argtype),*) -> $rettype { + static mut ptr: extern "system" fn($($argname: $argtype),*) -> $rettype = thunk; + + extern "system" fn thunk($($argname: $argtype),*) -> $rettype { + unsafe { + ::sys::c::compat::store_func(&mut ptr as *mut _ as *mut uint, + stringify!($module), + stringify!($symbol), + fallback as uint); + ::intrinsics::atomic_load_relaxed(&ptr)($($argname),*) + } + } + + extern "system" fn fallback($($argname: $argtype),*) -> $rettype $fallback + + ::intrinsics::atomic_load_relaxed(&ptr)($($argname),*) + } + ); + + ($module:ident::$symbol:ident($($argname:ident: $argtype:ty),*) $fallback:block) => ( + compat_fn!($module::$symbol($($argname: $argtype),*) -> () $fallback) + ) + ) + + /// Compatibility layer for functions in `kernel32.dll` + /// + /// Latest versions of Windows this is needed for: + /// + /// * `CreateSymbolicLinkW`: Windows XP, Windows Server 2003 + /// * `GetFinalPathNameByHandleW`: Windows XP, Windows Server 2003 + pub mod kernel32 { + use libc::types::os::arch::extra::{DWORD, LPCWSTR, BOOLEAN, HANDLE}; + use libc::consts::os::extra::ERROR_CALL_NOT_IMPLEMENTED; + + extern "system" { + fn SetLastError(dwErrCode: DWORD); + } + + compat_fn!(kernel32::CreateSymbolicLinkW(_lpSymlinkFileName: LPCWSTR, + _lpTargetFileName: LPCWSTR, + _dwFlags: DWORD) -> BOOLEAN { + unsafe { SetLastError(ERROR_CALL_NOT_IMPLEMENTED as DWORD); } + 0 + }) + + compat_fn!(kernel32::GetFinalPathNameByHandleW(_hFile: HANDLE, + _lpszFilePath: LPCWSTR, + _cchFilePath: DWORD, + _dwFlags: DWORD) -> DWORD { + unsafe { SetLastError(ERROR_CALL_NOT_IMPLEMENTED as DWORD); } + 0 + }) + } +} + +extern "system" { + // FIXME - pInputControl should be PCONSOLE_READCONSOLE_CONTROL + pub fn ReadConsoleW(hConsoleInput: libc::HANDLE, + lpBuffer: libc::LPVOID, + nNumberOfCharsToRead: libc::DWORD, + lpNumberOfCharsRead: libc::LPDWORD, + pInputControl: libc::LPVOID) -> libc::BOOL; + + pub fn WriteConsoleW(hConsoleOutput: libc::HANDLE, + lpBuffer: libc::types::os::arch::extra::LPCVOID, + nNumberOfCharsToWrite: libc::DWORD, + lpNumberOfCharsWritten: libc::LPDWORD, + lpReserved: libc::LPVOID) -> libc::BOOL; + + pub fn GetConsoleMode(hConsoleHandle: libc::HANDLE, + lpMode: libc::LPDWORD) -> libc::BOOL; + + pub fn SetConsoleMode(hConsoleHandle: libc::HANDLE, + lpMode: libc::DWORD) -> libc::BOOL; +} diff --git a/src/libstd/sys/windows/fs.rs b/src/libstd/sys/windows/fs.rs new file mode 100644 index 00000000000..a07688b2fed --- /dev/null +++ b/src/libstd/sys/windows/fs.rs @@ -0,0 +1,460 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Blocking Windows-based file I/O + +use alloc::arc::Arc; +use libc::{mod, c_int}; + +use c_str::CString; +use mem; +use os::windows::fill_utf16_buf_and_decode; +use path; +use ptr; +use str; +use io; + +use prelude::*; +use sys; +use sys_common::{keep_going, eof, mkerr_libc}; + +use io::{FilePermission, Write, UnstableFileStat, Open, FileAccess, FileMode}; +use io::{IoResult, IoError, FileStat, SeekStyle, Seek, Writer, Reader}; +use io::{Read, Truncate, SeekCur, SeekSet, ReadWrite, SeekEnd, Append}; + +pub use path::WindowsPath as Path; +pub type fd_t = libc::c_int; + +pub struct FileDesc { + /// The underlying C file descriptor. + pub fd: fd_t, + + /// Whether to close the file descriptor on drop. + close_on_drop: bool, +} + +impl FileDesc { + pub fn new(fd: fd_t, close_on_drop: bool) -> FileDesc { + FileDesc { fd: fd, close_on_drop: close_on_drop } + } + + pub fn read(&self, buf: &mut [u8]) -> IoResult<uint> { + let mut read = 0; + let ret = unsafe { + libc::ReadFile(self.handle(), buf.as_ptr() as libc::LPVOID, + buf.len() as libc::DWORD, &mut read, + ptr::null_mut()) + }; + if ret != 0 { + Ok(read as uint) + } else { + Err(super::last_error()) + } + } + + pub fn write(&self, buf: &[u8]) -> IoResult<()> { + let mut cur = buf.as_ptr(); + let mut remaining = buf.len(); + while remaining > 0 { + let mut amt = 0; + let ret = unsafe { + libc::WriteFile(self.handle(), cur as libc::LPVOID, + remaining as libc::DWORD, &mut amt, + ptr::null_mut()) + }; + if ret != 0 { + remaining -= amt as uint; + cur = unsafe { cur.offset(amt as int) }; + } else { + return Err(super::last_error()) + } + } + Ok(()) + } + + pub fn fd(&self) -> fd_t { self.fd } + + pub fn handle(&self) -> libc::HANDLE { + unsafe { libc::get_osfhandle(self.fd()) as libc::HANDLE } + } + + // A version of seek that takes &self so that tell can call it + // - the private seek should of course take &mut self. + fn seek_common(&self, pos: i64, style: SeekStyle) -> IoResult<u64> { + let whence = match style { + SeekSet => libc::FILE_BEGIN, + SeekEnd => libc::FILE_END, + SeekCur => libc::FILE_CURRENT, + }; + unsafe { + let mut newpos = 0; + match libc::SetFilePointerEx(self.handle(), pos, &mut newpos, whence) { + 0 => Err(super::last_error()), + _ => Ok(newpos as u64), + } + } + } + + pub fn seek(&mut self, pos: i64, style: SeekStyle) -> IoResult<u64> { + self.seek_common(pos, style) + } + + pub fn tell(&self) -> IoResult<u64> { + self.seek_common(0, SeekCur) + } + + pub fn fsync(&mut self) -> IoResult<()> { + super::mkerr_winbool(unsafe { + libc::FlushFileBuffers(self.handle()) + }) + } + + pub fn datasync(&mut self) -> IoResult<()> { return self.fsync(); } + + pub fn truncate(&mut self, offset: i64) -> IoResult<()> { + let orig_pos = try!(self.tell()); + let _ = try!(self.seek(offset, SeekSet)); + let ret = unsafe { + match libc::SetEndOfFile(self.handle()) { + 0 => Err(super::last_error()), + _ => Ok(()) + } + }; + let _ = self.seek(orig_pos as i64, SeekSet); + return ret; + } + + pub fn fstat(&mut self) -> IoResult<io::FileStat> { + let mut stat: libc::stat = unsafe { mem::zeroed() }; + match unsafe { libc::fstat(self.fd(), &mut stat) } { + 0 => Ok(mkstat(&stat)), + _ => Err(super::last_error()), + } + } + + /// Extract the actual filedescriptor without closing it. + pub fn unwrap(self) -> fd_t { + let fd = self.fd; + unsafe { mem::forget(self) }; + fd + } +} + +impl Drop for FileDesc { + fn drop(&mut self) { + // closing stdio file handles makes no sense, so never do it. Also, note + // that errors are ignored when closing a file descriptor. The reason + // for this is that if an error occurs we don't actually know if the + // file descriptor was closed or not, and if we retried (for something + // like EINTR), we might close another valid file descriptor (opened + // after we closed ours. + if self.close_on_drop && self.fd > libc::STDERR_FILENO { + let n = unsafe { libc::close(self.fd) }; + if n != 0 { + println!("error {} when closing file descriptor {}", n, self.fd); + } + } + } +} + +pub fn to_utf16(s: &Path) -> IoResult<Vec<u16>> { + sys::to_utf16(s.as_str()) +} + +pub fn open(path: &Path, fm: FileMode, fa: FileAccess) -> IoResult<FileDesc> { + // Flags passed to open_osfhandle + let flags = match fm { + Open => 0, + Append => libc::O_APPEND, + Truncate => libc::O_TRUNC, + }; + let flags = match fa { + Read => flags | libc::O_RDONLY, + Write => flags | libc::O_WRONLY | libc::O_CREAT, + ReadWrite => flags | libc::O_RDWR | libc::O_CREAT, + }; + let mut dwDesiredAccess = match fa { + Read => libc::FILE_GENERIC_READ, + Write => libc::FILE_GENERIC_WRITE, + ReadWrite => libc::FILE_GENERIC_READ | libc::FILE_GENERIC_WRITE + }; + + // libuv has a good comment about this, but the basic idea is what we try to + // emulate unix semantics by enabling all sharing by allowing things such as + // deleting a file while it's still open. + let dwShareMode = libc::FILE_SHARE_READ | libc::FILE_SHARE_WRITE | + libc::FILE_SHARE_DELETE; + + let dwCreationDisposition = match (fm, fa) { + (Truncate, Read) => libc::TRUNCATE_EXISTING, + (Truncate, _) => libc::CREATE_ALWAYS, + (Open, Read) => libc::OPEN_EXISTING, + (Open, _) => libc::OPEN_ALWAYS, + (Append, Read) => { + dwDesiredAccess |= libc::FILE_APPEND_DATA; + libc::OPEN_EXISTING + } + (Append, _) => { + dwDesiredAccess &= !libc::FILE_WRITE_DATA; + dwDesiredAccess |= libc::FILE_APPEND_DATA; + libc::OPEN_ALWAYS + } + }; + + let mut dwFlagsAndAttributes = libc::FILE_ATTRIBUTE_NORMAL; + // Compat with unix, this allows opening directories (see libuv) + dwFlagsAndAttributes |= libc::FILE_FLAG_BACKUP_SEMANTICS; + + let path = try!(to_utf16(path)); + let handle = unsafe { + libc::CreateFileW(path.as_ptr(), + dwDesiredAccess, + dwShareMode, + ptr::null_mut(), + dwCreationDisposition, + dwFlagsAndAttributes, + ptr::null_mut()) + }; + if handle == libc::INVALID_HANDLE_VALUE { + Err(super::last_error()) + } else { + let fd = unsafe { + libc::open_osfhandle(handle as libc::intptr_t, flags) + }; + if fd < 0 { + let _ = unsafe { libc::CloseHandle(handle) }; + Err(super::last_error()) + } else { + Ok(FileDesc::new(fd, true)) + } + } +} + +pub fn mkdir(p: &Path, _mode: uint) -> IoResult<()> { + let p = try!(to_utf16(p)); + super::mkerr_winbool(unsafe { + // FIXME: turn mode into something useful? #2623 + libc::CreateDirectoryW(p.as_ptr(), ptr::null_mut()) + }) +} + +pub fn readdir(p: &Path) -> IoResult<Vec<Path>> { + fn prune(root: &Path, dirs: Vec<Path>) -> Vec<Path> { + dirs.into_iter().filter(|path| { + path.as_vec() != b"." && path.as_vec() != b".." + }).map(|path| root.join(path)).collect() + } + + let star = p.join("*"); + let path = try!(to_utf16(&star)); + + unsafe { + let mut wfd = mem::zeroed(); + let find_handle = libc::FindFirstFileW(path.as_ptr(), &mut wfd); + if find_handle != libc::INVALID_HANDLE_VALUE { + let mut paths = vec![]; + let mut more_files = 1 as libc::BOOL; + while more_files != 0 { + { + let filename = str::truncate_utf16_at_nul(wfd.cFileName); + match String::from_utf16(filename) { + Some(filename) => paths.push(Path::new(filename)), + None => { + assert!(libc::FindClose(find_handle) != 0); + return Err(IoError { + kind: io::InvalidInput, + desc: "path was not valid UTF-16", + detail: Some(format!("path was not valid UTF-16: {}", filename)), + }) + }, // FIXME #12056: Convert the UCS-2 to invalid utf-8 instead of erroring + } + } + more_files = libc::FindNextFileW(find_handle, &mut wfd); + } + assert!(libc::FindClose(find_handle) != 0); + Ok(prune(p, paths)) + } else { + Err(super::last_error()) + } + } +} + +pub fn unlink(p: &Path) -> IoResult<()> { + fn do_unlink(p_utf16: &Vec<u16>) -> IoResult<()> { + super::mkerr_winbool(unsafe { libc::DeleteFileW(p_utf16.as_ptr()) }) + } + + let p_utf16 = try!(to_utf16(p)); + let res = do_unlink(&p_utf16); + match res { + Ok(()) => Ok(()), + Err(e) => { + // FIXME: change the code below to use more direct calls + // than `stat` and `chmod`, to avoid re-conversion to + // utf16 etc. + + // On unix, a readonly file can be successfully removed. On windows, + // however, it cannot. To keep the two platforms in line with + // respect to their behavior, catch this case on windows, attempt to + // change it to read-write, and then remove the file. + if e.kind == io::PermissionDenied { + let stat = match stat(p) { + Ok(stat) => stat, + Err(..) => return Err(e), + }; + if stat.perm.intersects(io::USER_WRITE) { return Err(e) } + + match chmod(p, (stat.perm | io::USER_WRITE).bits() as uint) { + Ok(()) => do_unlink(&p_utf16), + Err(..) => { + // Try to put it back as we found it + let _ = chmod(p, stat.perm.bits() as uint); + Err(e) + } + } + } else { + Err(e) + } + } + } +} + +pub fn rename(old: &Path, new: &Path) -> IoResult<()> { + let old = try!(to_utf16(old)); + let new = try!(to_utf16(new)); + super::mkerr_winbool(unsafe { + libc::MoveFileExW(old.as_ptr(), new.as_ptr(), libc::MOVEFILE_REPLACE_EXISTING) + }) +} + +pub fn chmod(p: &Path, mode: uint) -> IoResult<()> { + let p = try!(to_utf16(p)); + mkerr_libc(unsafe { + libc::wchmod(p.as_ptr(), mode as libc::c_int) + }) +} + +pub fn rmdir(p: &Path) -> IoResult<()> { + let p = try!(to_utf16(p)); + mkerr_libc(unsafe { libc::wrmdir(p.as_ptr()) }) +} + +pub fn chown(_p: &Path, _uid: int, _gid: int) -> IoResult<()> { + // libuv has this as a no-op, so seems like this should as well? + Ok(()) +} + +pub fn readlink(p: &Path) -> IoResult<Path> { + // FIXME: I have a feeling that this reads intermediate symlinks as well. + use sys::c::compat::kernel32::GetFinalPathNameByHandleW; + let p = try!(to_utf16(p)); + let handle = unsafe { + libc::CreateFileW(p.as_ptr(), + libc::GENERIC_READ, + libc::FILE_SHARE_READ, + ptr::null_mut(), + libc::OPEN_EXISTING, + libc::FILE_ATTRIBUTE_NORMAL, + ptr::null_mut()) + }; + if handle == libc::INVALID_HANDLE_VALUE { + return Err(super::last_error()) + } + // Specify (sz - 1) because the documentation states that it's the size + // without the null pointer + let ret = fill_utf16_buf_and_decode(|buf, sz| unsafe { + GetFinalPathNameByHandleW(handle, + buf as *const u16, + sz - 1, + libc::VOLUME_NAME_DOS) + }); + let ret = match ret { + Some(ref s) if s.as_slice().starts_with(r"\\?\") => { // " + Ok(Path::new(s.as_slice().slice_from(4))) + } + Some(s) => Ok(Path::new(s)), + None => Err(super::last_error()), + }; + assert!(unsafe { libc::CloseHandle(handle) } != 0); + return ret; +} + +pub fn symlink(src: &Path, dst: &Path) -> IoResult<()> { + use sys::c::compat::kernel32::CreateSymbolicLinkW; + let src = try!(to_utf16(src)); + let dst = try!(to_utf16(dst)); + super::mkerr_winbool(unsafe { + CreateSymbolicLinkW(dst.as_ptr(), src.as_ptr(), 0) as libc::BOOL + }) +} + +pub fn link(src: &Path, dst: &Path) -> IoResult<()> { + let src = try!(to_utf16(src)); + let dst = try!(to_utf16(dst)); + super::mkerr_winbool(unsafe { + libc::CreateHardLinkW(dst.as_ptr(), src.as_ptr(), ptr::null_mut()) + }) +} + +fn mkstat(stat: &libc::stat) -> FileStat { + FileStat { + size: stat.st_size as u64, + kind: match (stat.st_mode as libc::c_int) & libc::S_IFMT { + libc::S_IFREG => io::TypeFile, + libc::S_IFDIR => io::TypeDirectory, + libc::S_IFIFO => io::TypeNamedPipe, + libc::S_IFBLK => io::TypeBlockSpecial, + libc::S_IFLNK => io::TypeSymlink, + _ => io::TypeUnknown, + }, + perm: FilePermission::from_bits_truncate(stat.st_mode as u32), + created: stat.st_ctime as u64, + modified: stat.st_mtime as u64, + accessed: stat.st_atime as u64, + unstable: UnstableFileStat { + device: stat.st_dev as u64, + inode: stat.st_ino as u64, + rdev: stat.st_rdev as u64, + nlink: stat.st_nlink as u64, + uid: stat.st_uid as u64, + gid: stat.st_gid as u64, + blksize:0, + blocks: 0, + flags: 0, + gen: 0, + }, + } +} + +pub fn stat(p: &Path) -> IoResult<FileStat> { + let mut stat: libc::stat = unsafe { mem::zeroed() }; + let p = try!(to_utf16(p)); + match unsafe { libc::wstat(p.as_ptr(), &mut stat) } { + 0 => Ok(mkstat(&stat)), + _ => Err(super::last_error()), + } +} + +// FIXME: move this to platform-specific modules (for now)? +pub fn lstat(_p: &Path) -> IoResult<FileStat> { + // FIXME: implementation is missing + Err(super::unimpl()) +} + +pub fn utime(p: &Path, atime: u64, mtime: u64) -> IoResult<()> { + let mut buf = libc::utimbuf { + actime: atime as libc::time64_t, + modtime: mtime as libc::time64_t, + }; + let p = try!(to_utf16(p)); + mkerr_libc(unsafe { + libc::wutime(p.as_ptr(), &mut buf) + }) +} diff --git a/src/libstd/sys/windows/helper_signal.rs b/src/libstd/sys/windows/helper_signal.rs new file mode 100644 index 00000000000..c547c79e83a --- /dev/null +++ b/src/libstd/sys/windows/helper_signal.rs @@ -0,0 +1,38 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use libc::{mod, BOOL, LPCSTR, HANDLE, LPSECURITY_ATTRIBUTES, CloseHandle}; +use ptr; + +pub type signal = HANDLE; + +pub fn new() -> (HANDLE, HANDLE) { + unsafe { + let handle = CreateEventA(ptr::null_mut(), libc::FALSE, libc::FALSE, + ptr::null()); + (handle, handle) + } +} + +pub fn signal(handle: HANDLE) { + assert!(unsafe { SetEvent(handle) != 0 }); +} + +pub fn close(handle: HANDLE) { + assert!(unsafe { CloseHandle(handle) != 0 }); +} + +extern "system" { + fn CreateEventA(lpSecurityAttributes: LPSECURITY_ATTRIBUTES, + bManualReset: BOOL, + bInitialState: BOOL, + lpName: LPCSTR) -> HANDLE; + fn SetEvent(hEvent: HANDLE) -> BOOL; +} diff --git a/src/libstd/sys/windows/mod.rs b/src/libstd/sys/windows/mod.rs new file mode 100644 index 00000000000..98da4d4e763 --- /dev/null +++ b/src/libstd/sys/windows/mod.rs @@ -0,0 +1,190 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![allow(missing_doc)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(unused_imports)] +#![allow(dead_code)] +#![allow(unused_unsafe)] +#![allow(unused_mut)] + +extern crate libc; + +use num; +use mem; +use prelude::*; +use io::{mod, IoResult, IoError}; +use sync::{Once, ONCE_INIT}; + +macro_rules! helper_init( (static $name:ident: Helper<$m:ty>) => ( + static $name: Helper<$m> = Helper { + lock: ::rt::mutex::NATIVE_MUTEX_INIT, + chan: ::cell::UnsafeCell { value: 0 as *mut Sender<$m> }, + signal: ::cell::UnsafeCell { value: 0 }, + initialized: ::cell::UnsafeCell { value: false }, + }; +) ) + +pub mod c; +pub mod fs; +pub mod os; +pub mod tcp; +pub mod udp; +pub mod pipe; +pub mod helper_signal; +pub mod process; +pub mod timer; +pub mod tty; + +pub mod addrinfo { + pub use sys_common::net::get_host_addresses; +} + +// FIXME: move these to c module +pub type sock_t = libc::SOCKET; +pub type wrlen = libc::c_int; +pub type msglen_t = libc::c_int; +pub unsafe fn close_sock(sock: sock_t) { let _ = libc::closesocket(sock); } + +// windows has zero values as errors +fn mkerr_winbool(ret: libc::c_int) -> IoResult<()> { + if ret == 0 { + Err(last_error()) + } else { + Ok(()) + } +} + +pub fn last_error() -> IoError { + let errno = os::errno() as i32; + let mut err = decode_error(errno); + err.detail = Some(os::error_string(errno)); + err +} + +pub fn last_net_error() -> IoError { + let errno = unsafe { c::WSAGetLastError() as i32 }; + let mut err = decode_error(errno); + err.detail = Some(os::error_string(errno)); + err +} + +pub fn last_gai_error(_errno: i32) -> IoError { + last_net_error() +} + +/// Convert an `errno` value into a high-level error variant and description. +pub fn decode_error(errno: i32) -> IoError { + let (kind, desc) = match errno { + libc::EOF => (io::EndOfFile, "end of file"), + libc::ERROR_NO_DATA => (io::BrokenPipe, "the pipe is being closed"), + libc::ERROR_FILE_NOT_FOUND => (io::FileNotFound, "file not found"), + libc::ERROR_INVALID_NAME => (io::InvalidInput, "invalid file name"), + libc::WSAECONNREFUSED => (io::ConnectionRefused, "connection refused"), + libc::WSAECONNRESET => (io::ConnectionReset, "connection reset"), + libc::ERROR_ACCESS_DENIED | libc::WSAEACCES => + (io::PermissionDenied, "permission denied"), + libc::WSAEWOULDBLOCK => { + (io::ResourceUnavailable, "resource temporarily unavailable") + } + libc::WSAENOTCONN => (io::NotConnected, "not connected"), + libc::WSAECONNABORTED => (io::ConnectionAborted, "connection aborted"), + libc::WSAEADDRNOTAVAIL => (io::ConnectionRefused, "address not available"), + libc::WSAEADDRINUSE => (io::ConnectionRefused, "address in use"), + libc::ERROR_BROKEN_PIPE => (io::EndOfFile, "the pipe has ended"), + libc::ERROR_OPERATION_ABORTED => + (io::TimedOut, "operation timed out"), + libc::WSAEINVAL => (io::InvalidInput, "invalid argument"), + libc::ERROR_CALL_NOT_IMPLEMENTED => + (io::IoUnavailable, "function not implemented"), + libc::ERROR_INVALID_HANDLE => + (io::MismatchedFileTypeForOperation, + "invalid handle provided to function"), + libc::ERROR_NOTHING_TO_TERMINATE => + (io::InvalidInput, "no process to kill"), + + // libuv maps this error code to EISDIR. we do too. if it is found + // to be incorrect, we can add in some more machinery to only + // return this message when ERROR_INVALID_FUNCTION after certain + // Windows calls. + libc::ERROR_INVALID_FUNCTION => (io::InvalidInput, + "illegal operation on a directory"), + + _ => (io::OtherIoError, "unknown error") + }; + IoError { kind: kind, desc: desc, detail: None } +} + +pub fn decode_error_detailed(errno: i32) -> IoError { + let mut err = decode_error(errno); + err.detail = Some(os::error_string(errno)); + err +} + +#[inline] +pub fn retry<I> (f: || -> I) -> I { f() } // PR rust-lang/rust/#17020 + +pub fn ms_to_timeval(ms: u64) -> libc::timeval { + libc::timeval { + tv_sec: (ms / 1000) as libc::c_long, + tv_usec: ((ms % 1000) * 1000) as libc::c_long, + } +} + +pub fn wouldblock() -> bool { + let err = os::errno(); + err == libc::WSAEWOULDBLOCK as uint +} + +pub fn set_nonblocking(fd: sock_t, nb: bool) -> IoResult<()> { + let mut set = nb as libc::c_ulong; + if unsafe { c::ioctlsocket(fd, c::FIONBIO, &mut set) != 0 } { + Err(last_error()) + } else { + Ok(()) + } +} + +pub fn init_net() { + unsafe { + static START: Once = ONCE_INIT; + + START.doit(|| { + let mut data: c::WSADATA = mem::zeroed(); + let ret = c::WSAStartup(0x202, // version 2.2 + &mut data); + assert_eq!(ret, 0); + }); + } +} + +pub fn unimpl() -> IoError { + IoError { + kind: io::IoUnavailable, + desc: "operation is not implemented", + detail: None, + } +} + +pub fn to_utf16(s: Option<&str>) -> IoResult<Vec<u16>> { + match s { + Some(s) => Ok({ + let mut s = s.utf16_units().collect::<Vec<u16>>(); + s.push(0); + s + }), + None => Err(IoError { + kind: io::InvalidInput, + desc: "valid unicode input required", + detail: None + }) + } +} diff --git a/src/libstd/sys/windows/os.rs b/src/libstd/sys/windows/os.rs new file mode 100644 index 00000000000..aaa1aaf6327 --- /dev/null +++ b/src/libstd/sys/windows/os.rs @@ -0,0 +1,103 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// FIXME: move various extern bindings from here into liblibc or +// something similar + +use libc; +use libc::{c_int, c_char, c_void}; +use prelude::*; +use io::{IoResult, IoError}; +use sys::fs::FileDesc; +use ptr; + +use os::TMPBUF_SZ; + +pub fn errno() -> uint { + use libc::types::os::arch::extra::DWORD; + + #[link_name = "kernel32"] + extern "system" { + fn GetLastError() -> DWORD; + } + + unsafe { + GetLastError() as uint + } +} + +/// Get a detailed string description for the given error number +pub fn error_string(errnum: i32) -> String { + use libc::types::os::arch::extra::DWORD; + use libc::types::os::arch::extra::LPWSTR; + use libc::types::os::arch::extra::LPVOID; + use libc::types::os::arch::extra::WCHAR; + + #[link_name = "kernel32"] + extern "system" { + fn FormatMessageW(flags: DWORD, + lpSrc: LPVOID, + msgId: DWORD, + langId: DWORD, + buf: LPWSTR, + nsize: DWORD, + args: *const c_void) + -> DWORD; + } + + static FORMAT_MESSAGE_FROM_SYSTEM: DWORD = 0x00001000; + static FORMAT_MESSAGE_IGNORE_INSERTS: DWORD = 0x00000200; + + // This value is calculated from the macro + // MAKELANGID(LANG_SYSTEM_DEFAULT, SUBLANG_SYS_DEFAULT) + let langId = 0x0800 as DWORD; + + let mut buf = [0 as WCHAR, ..TMPBUF_SZ]; + + unsafe { + let res = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + ptr::null_mut(), + errnum as DWORD, + langId, + buf.as_mut_ptr(), + buf.len() as DWORD, + ptr::null()); + if res == 0 { + // Sometimes FormatMessageW can fail e.g. system doesn't like langId, + let fm_err = errno(); + return format!("OS Error {} (FormatMessageW() returned error {})", errnum, fm_err); + } + + let msg = String::from_utf16(::str::truncate_utf16_at_nul(buf)); + match msg { + Some(msg) => format!("OS Error {}: {}", errnum, msg), + None => format!("OS Error {} (FormatMessageW() returned invalid UTF-16)", errnum), + } + } +} + +pub unsafe fn pipe() -> IoResult<(FileDesc, FileDesc)> { + // Windows pipes work subtly differently than unix pipes, and their + // inheritance has to be handled in a different way that I do not + // fully understand. Here we explicitly make the pipe non-inheritable, + // which means to pass it to a subprocess they need to be duplicated + // first, as in std::run. + let mut fds = [0, ..2]; + match libc::pipe(fds.as_mut_ptr(), 1024 as ::libc::c_uint, + (libc::O_BINARY | libc::O_NOINHERIT) as c_int) { + 0 => { + assert!(fds[0] != -1 && fds[0] != 0); + assert!(fds[1] != -1 && fds[1] != 0); + Ok((FileDesc::new(fds[0], true), FileDesc::new(fds[1], true))) + } + _ => Err(IoError::last_error()), + } +} diff --git a/src/libstd/sys/windows/pipe.rs b/src/libstd/sys/windows/pipe.rs new file mode 100644 index 00000000000..f2f7994a005 --- /dev/null +++ b/src/libstd/sys/windows/pipe.rs @@ -0,0 +1,751 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Named pipes implementation for windows +//! +//! If are unfortunate enough to be reading this code, I would like to first +//! apologize. This was my first encounter with windows named pipes, and it +//! didn't exactly turn out very cleanly. If you, too, are new to named pipes, +//! read on as I'll try to explain some fun things that I ran into. +//! +//! # Unix pipes vs Named pipes +//! +//! As with everything else, named pipes on windows are pretty different from +//! unix pipes on unix. On unix, you use one "server pipe" to accept new client +//! pipes. So long as this server pipe is active, new children pipes can +//! connect. On windows, you instead have a number of "server pipes", and each +//! of these server pipes can throughout their lifetime be attached to a client +//! or not. Once attached to a client, a server pipe may then disconnect at a +//! later date. +//! +//! # Accepting clients +//! +//! As with most other I/O interfaces, our Listener/Acceptor/Stream interfaces +//! are built around the unix flavors. This means that we have one "server +//! pipe" to which many clients can connect. In order to make this compatible +//! with the windows model, each connected client consumes ownership of a server +//! pipe, and then a new server pipe is created for the next client. +//! +//! Note that the server pipes attached to clients are never given back to the +//! listener for recycling. This could possibly be implemented with a channel so +//! the listener half can re-use server pipes, but for now I err'd on the simple +//! side of things. Each stream accepted by a listener will destroy the server +//! pipe after the stream is dropped. +//! +//! This model ends up having a small race or two, and you can find more details +//! on the `native_accept` method. +//! +//! # Simultaneous reads and writes +//! +//! In testing, I found that two simultaneous writes and two simultaneous reads +//! on a pipe ended up working out just fine, but problems were encountered when +//! a read was executed simultaneously with a write. After some googling around, +//! it sounded like named pipes just weren't built for this kind of interaction, +//! and the suggested solution was to use overlapped I/O. +//! +//! I don't really know what overlapped I/O is, but my basic understanding after +//! reading about it is that you have an external Event which is used to signal +//! I/O completion, passed around in some OVERLAPPED structures. As to what this +//! is, I'm not exactly sure. +//! +//! This problem implies that all named pipes are created with the +//! FILE_FLAG_OVERLAPPED option. This means that all of their I/O is +//! asynchronous. Each I/O operation has an associated OVERLAPPED structure, and +//! inside of this structure is a HANDLE from CreateEvent. After the I/O is +//! determined to be pending (may complete in the future), the +//! GetOverlappedResult function is used to block on the event, waiting for the +//! I/O to finish. +//! +//! This scheme ended up working well enough. There were two snags that I ran +//! into, however: +//! +//! * Each UnixStream instance needs its own read/write events to wait on. These +//! can't be shared among clones of the same stream because the documentation +//! states that it unsets the event when the I/O is started (would possibly +//! corrupt other events simultaneously waiting). For convenience's sake, +//! these events are lazily initialized. +//! +//! * Each server pipe needs to be created with FILE_FLAG_OVERLAPPED in addition +//! to all pipes created through `connect`. Notably this means that the +//! ConnectNamedPipe function is nonblocking, implying that the Listener needs +//! to have yet another event to do the actual blocking. +//! +//! # Conclusion +//! +//! The conclusion here is that I probably don't know the best way to work with +//! windows named pipes, but the solution here seems to work well enough to get +//! the test suite passing (the suite is in libstd), and that's good enough for +//! me! + +use alloc::arc::Arc; +use libc; +use c_str::CString; +use mem; +use ptr; +use sync::atomic; +use rt::mutex; +use io::{mod, IoError, IoResult}; +use prelude::*; + +use sys_common::{mod, eof}; + +use super::{c, os, timer, to_utf16, decode_error_detailed}; + +struct Event(libc::HANDLE); + +impl Event { + fn new(manual_reset: bool, initial_state: bool) -> IoResult<Event> { + let event = unsafe { + libc::CreateEventW(ptr::null_mut(), + manual_reset as libc::BOOL, + initial_state as libc::BOOL, + ptr::null()) + }; + if event as uint == 0 { + Err(super::last_error()) + } else { + Ok(Event(event)) + } + } + + fn handle(&self) -> libc::HANDLE { let Event(handle) = *self; handle } +} + +impl Drop for Event { + fn drop(&mut self) { + unsafe { let _ = libc::CloseHandle(self.handle()); } + } +} + +struct Inner { + handle: libc::HANDLE, + lock: mutex::NativeMutex, + read_closed: atomic::AtomicBool, + write_closed: atomic::AtomicBool, +} + +impl Inner { + fn new(handle: libc::HANDLE) -> Inner { + Inner { + handle: handle, + lock: unsafe { mutex::NativeMutex::new() }, + read_closed: atomic::AtomicBool::new(false), + write_closed: atomic::AtomicBool::new(false), + } + } +} + +impl Drop for Inner { + fn drop(&mut self) { + unsafe { + let _ = libc::FlushFileBuffers(self.handle); + let _ = libc::CloseHandle(self.handle); + } + } +} + +unsafe fn pipe(name: *const u16, init: bool) -> libc::HANDLE { + libc::CreateNamedPipeW( + name, + libc::PIPE_ACCESS_DUPLEX | + if init {libc::FILE_FLAG_FIRST_PIPE_INSTANCE} else {0} | + libc::FILE_FLAG_OVERLAPPED, + libc::PIPE_TYPE_BYTE | libc::PIPE_READMODE_BYTE | + libc::PIPE_WAIT, + libc::PIPE_UNLIMITED_INSTANCES, + 65536, + 65536, + 0, + ptr::null_mut() + ) +} + +pub fn await(handle: libc::HANDLE, deadline: u64, + events: &[libc::HANDLE]) -> IoResult<uint> { + use libc::consts::os::extra::{WAIT_FAILED, WAIT_TIMEOUT, WAIT_OBJECT_0}; + + // If we've got a timeout, use WaitForSingleObject in tandem with CancelIo + // to figure out if we should indeed get the result. + let ms = if deadline == 0 { + libc::INFINITE as u64 + } else { + let now = timer::now(); + if deadline < now {0} else {deadline - now} + }; + let ret = unsafe { + c::WaitForMultipleObjects(events.len() as libc::DWORD, + events.as_ptr(), + libc::FALSE, + ms as libc::DWORD) + }; + match ret { + WAIT_FAILED => Err(super::last_error()), + WAIT_TIMEOUT => unsafe { + let _ = c::CancelIo(handle); + Err(sys_common::timeout("operation timed out")) + }, + n => Ok((n - WAIT_OBJECT_0) as uint) + } +} + +fn epipe() -> IoError { + IoError { + kind: io::EndOfFile, + desc: "the pipe has ended", + detail: None, + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Unix Streams +//////////////////////////////////////////////////////////////////////////////// + +pub struct UnixStream { + inner: Arc<Inner>, + write: Option<Event>, + read: Option<Event>, + read_deadline: u64, + write_deadline: u64, +} + +impl UnixStream { + fn try_connect(p: *const u16) -> Option<libc::HANDLE> { + // Note that most of this is lifted from the libuv implementation. + // The idea is that if we fail to open a pipe in read/write mode + // that we try afterwards in just read or just write + let mut result = unsafe { + libc::CreateFileW(p, + libc::GENERIC_READ | libc::GENERIC_WRITE, + 0, + ptr::null_mut(), + libc::OPEN_EXISTING, + libc::FILE_FLAG_OVERLAPPED, + ptr::null_mut()) + }; + if result != libc::INVALID_HANDLE_VALUE { + return Some(result) + } + + let err = unsafe { libc::GetLastError() }; + if err == libc::ERROR_ACCESS_DENIED as libc::DWORD { + result = unsafe { + libc::CreateFileW(p, + libc::GENERIC_READ | libc::FILE_WRITE_ATTRIBUTES, + 0, + ptr::null_mut(), + libc::OPEN_EXISTING, + libc::FILE_FLAG_OVERLAPPED, + ptr::null_mut()) + }; + if result != libc::INVALID_HANDLE_VALUE { + return Some(result) + } + } + let err = unsafe { libc::GetLastError() }; + if err == libc::ERROR_ACCESS_DENIED as libc::DWORD { + result = unsafe { + libc::CreateFileW(p, + libc::GENERIC_WRITE | libc::FILE_READ_ATTRIBUTES, + 0, + ptr::null_mut(), + libc::OPEN_EXISTING, + libc::FILE_FLAG_OVERLAPPED, + ptr::null_mut()) + }; + if result != libc::INVALID_HANDLE_VALUE { + return Some(result) + } + } + None + } + + pub fn connect(addr: &CString, timeout: Option<u64>) -> IoResult<UnixStream> { + let addr = try!(to_utf16(addr.as_str())); + let start = timer::now(); + loop { + match UnixStream::try_connect(addr.as_ptr()) { + Some(handle) => { + let inner = Inner::new(handle); + let mut mode = libc::PIPE_TYPE_BYTE | + libc::PIPE_READMODE_BYTE | + libc::PIPE_WAIT; + let ret = unsafe { + libc::SetNamedPipeHandleState(inner.handle, + &mut mode, + ptr::null_mut(), + ptr::null_mut()) + }; + return if ret == 0 { + Err(super::last_error()) + } else { + Ok(UnixStream { + inner: Arc::new(inner), + read: None, + write: None, + read_deadline: 0, + write_deadline: 0, + }) + } + } + None => {} + } + + // On windows, if you fail to connect, you may need to call the + // `WaitNamedPipe` function, and this is indicated with an error + // code of ERROR_PIPE_BUSY. + let code = unsafe { libc::GetLastError() }; + if code as int != libc::ERROR_PIPE_BUSY as int { + return Err(super::last_error()) + } + + match timeout { + Some(timeout) => { + let now = timer::now(); + let timed_out = (now - start) >= timeout || unsafe { + let ms = (timeout - (now - start)) as libc::DWORD; + libc::WaitNamedPipeW(addr.as_ptr(), ms) == 0 + }; + if timed_out { + return Err(sys_common::timeout("connect timed out")) + } + } + + // An example I found on Microsoft's website used 20 + // seconds, libuv uses 30 seconds, hence we make the + // obvious choice of waiting for 25 seconds. + None => { + if unsafe { libc::WaitNamedPipeW(addr.as_ptr(), 25000) } == 0 { + return Err(super::last_error()) + } + } + } + } + } + + fn handle(&self) -> libc::HANDLE { self.inner.handle } + + fn read_closed(&self) -> bool { + self.inner.read_closed.load(atomic::SeqCst) + } + + fn write_closed(&self) -> bool { + self.inner.write_closed.load(atomic::SeqCst) + } + + fn cancel_io(&self) -> IoResult<()> { + match unsafe { c::CancelIoEx(self.handle(), ptr::null_mut()) } { + 0 if os::errno() == libc::ERROR_NOT_FOUND as uint => { + Ok(()) + } + 0 => Err(super::last_error()), + _ => Ok(()) + } + } + + pub fn read(&mut self, buf: &mut [u8]) -> IoResult<uint> { + if self.read.is_none() { + self.read = Some(try!(Event::new(true, false))); + } + + let mut bytes_read = 0; + let mut overlapped: libc::OVERLAPPED = unsafe { mem::zeroed() }; + overlapped.hEvent = self.read.as_ref().unwrap().handle(); + + // Pre-flight check to see if the reading half has been closed. This + // must be done before issuing the ReadFile request, but after we + // acquire the lock. + // + // See comments in close_read() about why this lock is necessary. + let guard = unsafe { self.inner.lock.lock() }; + if self.read_closed() { + return Err(eof()) + } + + // Issue a nonblocking requests, succeeding quickly if it happened to + // succeed. + let ret = unsafe { + libc::ReadFile(self.handle(), + buf.as_ptr() as libc::LPVOID, + buf.len() as libc::DWORD, + &mut bytes_read, + &mut overlapped) + }; + if ret != 0 { return Ok(bytes_read as uint) } + + // If our errno doesn't say that the I/O is pending, then we hit some + // legitimate error and return immediately. + if os::errno() != libc::ERROR_IO_PENDING as uint { + return Err(super::last_error()) + } + + // Now that we've issued a successful nonblocking request, we need to + // wait for it to finish. This can all be done outside the lock because + // we'll see any invocation of CancelIoEx. We also call this in a loop + // because we're woken up if the writing half is closed, we just need to + // realize that the reading half wasn't closed and we go right back to + // sleep. + drop(guard); + loop { + // Process a timeout if one is pending + let wait_succeeded = await(self.handle(), self.read_deadline, + [overlapped.hEvent]); + + let ret = unsafe { + libc::GetOverlappedResult(self.handle(), + &mut overlapped, + &mut bytes_read, + libc::TRUE) + }; + // If we succeeded, or we failed for some reason other than + // CancelIoEx, return immediately + if ret != 0 { return Ok(bytes_read as uint) } + if os::errno() != libc::ERROR_OPERATION_ABORTED as uint { + return Err(super::last_error()) + } + + // If the reading half is now closed, then we're done. If we woke up + // because the writing half was closed, keep trying. + if wait_succeeded.is_err() { + return Err(sys_common::timeout("read timed out")) + } + if self.read_closed() { + return Err(eof()) + } + } + } + + pub fn write(&mut self, buf: &[u8]) -> IoResult<()> { + if self.write.is_none() { + self.write = Some(try!(Event::new(true, false))); + } + + let mut offset = 0; + let mut overlapped: libc::OVERLAPPED = unsafe { mem::zeroed() }; + overlapped.hEvent = self.write.as_ref().unwrap().handle(); + + while offset < buf.len() { + let mut bytes_written = 0; + + // This sequence below is quite similar to the one found in read(). + // Some careful looping is done to ensure that if close_write() is + // invoked we bail out early, and if close_read() is invoked we keep + // going after we woke up. + // + // See comments in close_read() about why this lock is necessary. + let guard = unsafe { self.inner.lock.lock() }; + if self.write_closed() { + return Err(epipe()) + } + let ret = unsafe { + libc::WriteFile(self.handle(), + buf[offset..].as_ptr() as libc::LPVOID, + (buf.len() - offset) as libc::DWORD, + &mut bytes_written, + &mut overlapped) + }; + let err = os::errno(); + drop(guard); + + if ret == 0 { + if err != libc::ERROR_IO_PENDING as uint { + return Err(decode_error_detailed(err as i32)) + } + // Process a timeout if one is pending + let wait_succeeded = await(self.handle(), self.write_deadline, + [overlapped.hEvent]); + let ret = unsafe { + libc::GetOverlappedResult(self.handle(), + &mut overlapped, + &mut bytes_written, + libc::TRUE) + }; + // If we weren't aborted, this was a legit error, if we were + // aborted, then check to see if the write half was actually + // closed or whether we woke up from the read half closing. + if ret == 0 { + if os::errno() != libc::ERROR_OPERATION_ABORTED as uint { + return Err(super::last_error()) + } + if !wait_succeeded.is_ok() { + let amt = offset + bytes_written as uint; + return if amt > 0 { + Err(IoError { + kind: io::ShortWrite(amt), + desc: "short write during write", + detail: None, + }) + } else { + Err(sys_common::timeout("write timed out")) + } + } + if self.write_closed() { + return Err(epipe()) + } + continue // retry + } + } + offset += bytes_written as uint; + } + Ok(()) + } + + pub fn close_read(&mut self) -> IoResult<()> { + // On windows, there's no actual shutdown() method for pipes, so we're + // forced to emulate the behavior manually at the application level. To + // do this, we need to both cancel any pending requests, as well as + // prevent all future requests from succeeding. These two operations are + // not atomic with respect to one another, so we must use a lock to do + // so. + // + // The read() code looks like: + // + // 1. Make sure the pipe is still open + // 2. Submit a read request + // 3. Wait for the read request to finish + // + // The race this lock is preventing is if another thread invokes + // close_read() between steps 1 and 2. By atomically executing steps 1 + // and 2 with a lock with respect to close_read(), we're guaranteed that + // no thread will erroneously sit in a read forever. + let _guard = unsafe { self.inner.lock.lock() }; + self.inner.read_closed.store(true, atomic::SeqCst); + self.cancel_io() + } + + pub fn close_write(&mut self) -> IoResult<()> { + // see comments in close_read() for why this lock is necessary + let _guard = unsafe { self.inner.lock.lock() }; + self.inner.write_closed.store(true, atomic::SeqCst); + self.cancel_io() + } + + pub fn set_timeout(&mut self, timeout: Option<u64>) { + let deadline = timeout.map(|a| timer::now() + a).unwrap_or(0); + self.read_deadline = deadline; + self.write_deadline = deadline; + } + pub fn set_read_timeout(&mut self, timeout: Option<u64>) { + self.read_deadline = timeout.map(|a| timer::now() + a).unwrap_or(0); + } + pub fn set_write_timeout(&mut self, timeout: Option<u64>) { + self.write_deadline = timeout.map(|a| timer::now() + a).unwrap_or(0); + } +} + +impl Clone for UnixStream { + fn clone(&self) -> UnixStream { + UnixStream { + inner: self.inner.clone(), + read: None, + write: None, + read_deadline: 0, + write_deadline: 0, + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Unix Listener +//////////////////////////////////////////////////////////////////////////////// + +pub struct UnixListener { + handle: libc::HANDLE, + name: CString, +} + +impl UnixListener { + pub fn bind(addr: &CString) -> IoResult<UnixListener> { + // Although we technically don't need the pipe until much later, we + // create the initial handle up front to test the validity of the name + // and such. + let addr_v = try!(to_utf16(addr.as_str())); + let ret = unsafe { pipe(addr_v.as_ptr(), true) }; + if ret == libc::INVALID_HANDLE_VALUE { + Err(super::last_error()) + } else { + Ok(UnixListener { handle: ret, name: addr.clone() }) + } + } + + pub fn listen(self) -> IoResult<UnixAcceptor> { + Ok(UnixAcceptor { + listener: self, + event: try!(Event::new(true, false)), + deadline: 0, + inner: Arc::new(AcceptorState { + abort: try!(Event::new(true, false)), + closed: atomic::AtomicBool::new(false), + }), + }) + } +} + +impl Drop for UnixListener { + fn drop(&mut self) { + unsafe { let _ = libc::CloseHandle(self.handle); } + } +} + +pub struct UnixAcceptor { + inner: Arc<AcceptorState>, + listener: UnixListener, + event: Event, + deadline: u64, +} + +struct AcceptorState { + abort: Event, + closed: atomic::AtomicBool, +} + +impl UnixAcceptor { + pub fn accept(&mut self) -> IoResult<UnixStream> { + // This function has some funky implementation details when working with + // unix pipes. On windows, each server named pipe handle can be + // connected to a one or zero clients. To the best of my knowledge, a + // named server is considered active and present if there exists at + // least one server named pipe for it. + // + // The model of this function is to take the current known server + // handle, connect a client to it, and then transfer ownership to the + // UnixStream instance. The next time accept() is invoked, it'll need a + // different server handle to connect a client to. + // + // Note that there is a possible race here. Once our server pipe is + // handed off to a `UnixStream` object, the stream could be closed, + // meaning that there would be no active server pipes, hence even though + // we have a valid `UnixAcceptor`, no one can connect to it. For this + // reason, we generate the next accept call's server pipe at the end of + // this function call. + // + // This provides us an invariant that we always have at least one server + // connection open at a time, meaning that all connects to this acceptor + // should succeed while this is active. + // + // The actual implementation of doing this is a little tricky. Once a + // server pipe is created, a client can connect to it at any time. I + // assume that which server a client connects to is nondeterministic, so + // we also need to guarantee that the only server able to be connected + // to is the one that we're calling ConnectNamedPipe on. This means that + // we have to create the second server pipe *after* we've already + // accepted a connection. In order to at least somewhat gracefully + // handle errors, this means that if the second server pipe creation + // fails that we disconnect the connected client and then just keep + // using the original server pipe. + let handle = self.listener.handle; + + // If we've had an artificial call to close_accept, be sure to never + // proceed in accepting new clients in the future + if self.inner.closed.load(atomic::SeqCst) { return Err(eof()) } + + let name = try!(to_utf16(self.listener.name.as_str())); + + // Once we've got a "server handle", we need to wait for a client to + // connect. The ConnectNamedPipe function will block this thread until + // someone on the other end connects. This function can "fail" if a + // client connects after we created the pipe but before we got down + // here. Thanks windows. + let mut overlapped: libc::OVERLAPPED = unsafe { mem::zeroed() }; + overlapped.hEvent = self.event.handle(); + if unsafe { libc::ConnectNamedPipe(handle, &mut overlapped) == 0 } { + let mut err = unsafe { libc::GetLastError() }; + + if err == libc::ERROR_IO_PENDING as libc::DWORD { + // Process a timeout if one is pending + let wait_succeeded = await(handle, self.deadline, + [self.inner.abort.handle(), + overlapped.hEvent]); + + // This will block until the overlapped I/O is completed. The + // timeout was previously handled, so this will either block in + // the normal case or succeed very quickly in the timeout case. + let ret = unsafe { + let mut transfer = 0; + libc::GetOverlappedResult(handle, + &mut overlapped, + &mut transfer, + libc::TRUE) + }; + if ret == 0 { + if wait_succeeded.is_ok() { + err = unsafe { libc::GetLastError() }; + } else { + return Err(sys_common::timeout("accept timed out")) + } + } else { + // we succeeded, bypass the check below + err = libc::ERROR_PIPE_CONNECTED as libc::DWORD; + } + } + if err != libc::ERROR_PIPE_CONNECTED as libc::DWORD { + return Err(super::last_error()) + } + } + + // Now that we've got a connected client to our handle, we need to + // create a second server pipe. If this fails, we disconnect the + // connected client and return an error (see comments above). + let new_handle = unsafe { pipe(name.as_ptr(), false) }; + if new_handle == libc::INVALID_HANDLE_VALUE { + let ret = Err(super::last_error()); + // If our disconnection fails, then there's not really a whole lot + // that we can do, so panic + let err = unsafe { libc::DisconnectNamedPipe(handle) }; + assert!(err != 0); + return ret; + } else { + self.listener.handle = new_handle; + } + + // Transfer ownership of our handle into this stream + Ok(UnixStream { + inner: Arc::new(Inner::new(handle)), + read: None, + write: None, + read_deadline: 0, + write_deadline: 0, + }) + } + + pub fn set_timeout(&mut self, timeout: Option<u64>) { + self.deadline = timeout.map(|i| i + timer::now()).unwrap_or(0); + } + + pub fn close_accept(&mut self) -> IoResult<()> { + self.inner.closed.store(true, atomic::SeqCst); + let ret = unsafe { + c::SetEvent(self.inner.abort.handle()) + }; + if ret == 0 { + Err(super::last_error()) + } else { + Ok(()) + } + } +} + +impl Clone for UnixAcceptor { + fn clone(&self) -> UnixAcceptor { + let name = to_utf16(self.listener.name.as_str()).ok().unwrap(); + UnixAcceptor { + inner: self.inner.clone(), + event: Event::new(true, false).ok().unwrap(), + deadline: 0, + listener: UnixListener { + name: self.listener.name.clone(), + handle: unsafe { + let p = pipe(name.as_ptr(), false) ; + assert!(p != libc::INVALID_HANDLE_VALUE as libc::HANDLE); + p + }, + }, + } + } +} diff --git a/src/libstd/sys/windows/process.rs b/src/libstd/sys/windows/process.rs new file mode 100644 index 00000000000..67e87841ed2 --- /dev/null +++ b/src/libstd/sys/windows/process.rs @@ -0,0 +1,511 @@ +// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use libc::{pid_t, c_void, c_int}; +use libc; +use c_str::CString; +use io; +use mem; +use os; +use ptr; +use prelude::*; +use io::process::{ProcessExit, ExitStatus, ExitSignal}; +use collections; +use path::BytesContainer; +use hash::Hash; +use io::{IoResult, IoError}; + +use sys::fs; +use sys::{mod, retry, c, wouldblock, set_nonblocking, ms_to_timeval, timer}; +use sys::fs::FileDesc; +use sys_common::helper_thread::Helper; +use sys_common::{AsFileDesc, mkerr_libc, timeout}; + +use io::fs::PathExtensions; +use string::String; + +pub use sys_common::ProcessConfig; + +/** + * A value representing a child process. + * + * The lifetime of this value is linked to the lifetime of the actual + * process - the Process destructor calls self.finish() which waits + * for the process to terminate. + */ +pub struct Process { + /// The unique id of the process (this should never be negative). + pid: pid_t, + + /// A HANDLE to the process, which will prevent the pid being + /// re-used until the handle is closed. + handle: *mut (), +} + +impl Drop for Process { + fn drop(&mut self) { + free_handle(self.handle); + } +} + +impl Process { + pub fn id(&self) -> pid_t { + self.pid + } + + pub unsafe fn kill(&self, signal: int) -> IoResult<()> { + Process::killpid(self.pid, signal) + } + + pub unsafe fn killpid(pid: pid_t, signal: int) -> IoResult<()> { + let handle = libc::OpenProcess(libc::PROCESS_TERMINATE | + libc::PROCESS_QUERY_INFORMATION, + libc::FALSE, pid as libc::DWORD); + if handle.is_null() { + return Err(super::last_error()) + } + let ret = match signal { + // test for existence on signal 0 + 0 => { + let mut status = 0; + let ret = libc::GetExitCodeProcess(handle, &mut status); + if ret == 0 { + Err(super::last_error()) + } else if status != libc::STILL_ACTIVE { + Err(IoError { + kind: io::InvalidInput, + desc: "no process to kill", + detail: None, + }) + } else { + Ok(()) + } + } + 15 | 9 => { // sigterm or sigkill + let ret = libc::TerminateProcess(handle, 1); + super::mkerr_winbool(ret) + } + _ => Err(IoError { + kind: io::IoUnavailable, + desc: "unsupported signal on windows", + detail: None, + }) + }; + let _ = libc::CloseHandle(handle); + return ret; + } + + pub fn spawn<K, V, C, P>(cfg: &C, in_fd: Option<P>, + out_fd: Option<P>, err_fd: Option<P>) + -> IoResult<Process> + where C: ProcessConfig<K, V>, P: AsFileDesc, + K: BytesContainer + Eq + Hash, V: BytesContainer + { + use libc::types::os::arch::extra::{DWORD, HANDLE, STARTUPINFO}; + use libc::consts::os::extra::{ + TRUE, FALSE, + STARTF_USESTDHANDLES, + INVALID_HANDLE_VALUE, + DUPLICATE_SAME_ACCESS + }; + use libc::funcs::extra::kernel32::{ + GetCurrentProcess, + DuplicateHandle, + CloseHandle, + CreateProcessW + }; + use libc::funcs::extra::msvcrt::get_osfhandle; + + use mem; + use iter::Iterator; + use str::StrPrelude; + + if cfg.gid().is_some() || cfg.uid().is_some() { + return Err(IoError { + kind: io::IoUnavailable, + desc: "unsupported gid/uid requested on windows", + detail: None, + }) + } + + // To have the spawning semantics of unix/windows stay the same, we need to + // read the *child's* PATH if one is provided. See #15149 for more details. + let program = cfg.env().and_then(|env| { + for (key, v) in env.iter() { + if b"PATH" != key.container_as_bytes() { continue } + + // Split the value and test each path to see if the + // program exists. + for path in os::split_paths(v.container_as_bytes()).into_iter() { + let path = path.join(cfg.program().as_bytes_no_nul()) + .with_extension(os::consts::EXE_EXTENSION); + if path.exists() { + return Some(path.to_c_str()) + } + } + break + } + None + }); + + unsafe { + let mut si = zeroed_startupinfo(); + si.cb = mem::size_of::<STARTUPINFO>() as DWORD; + si.dwFlags = STARTF_USESTDHANDLES; + + let cur_proc = GetCurrentProcess(); + + // Similarly to unix, we don't actually leave holes for the stdio file + // descriptors, but rather open up /dev/null equivalents. These + // equivalents are drawn from libuv's windows process spawning. + let set_fd = |fd: &Option<P>, slot: &mut HANDLE, + is_stdin: bool| { + match *fd { + None => { + let access = if is_stdin { + libc::FILE_GENERIC_READ + } else { + libc::FILE_GENERIC_WRITE | libc::FILE_READ_ATTRIBUTES + }; + let size = mem::size_of::<libc::SECURITY_ATTRIBUTES>(); + let mut sa = libc::SECURITY_ATTRIBUTES { + nLength: size as libc::DWORD, + lpSecurityDescriptor: ptr::null_mut(), + bInheritHandle: 1, + }; + let mut filename: Vec<u16> = "NUL".utf16_units().collect(); + filename.push(0); + *slot = libc::CreateFileW(filename.as_ptr(), + access, + libc::FILE_SHARE_READ | + libc::FILE_SHARE_WRITE, + &mut sa, + libc::OPEN_EXISTING, + 0, + ptr::null_mut()); + if *slot == INVALID_HANDLE_VALUE { + return Err(super::last_error()) + } + } + Some(ref fd) => { + let orig = get_osfhandle(fd.as_fd().fd()) as HANDLE; + if orig == INVALID_HANDLE_VALUE { + return Err(super::last_error()) + } + if DuplicateHandle(cur_proc, orig, cur_proc, slot, + 0, TRUE, DUPLICATE_SAME_ACCESS) == FALSE { + return Err(super::last_error()) + } + } + } + Ok(()) + }; + + try!(set_fd(&in_fd, &mut si.hStdInput, true)); + try!(set_fd(&out_fd, &mut si.hStdOutput, false)); + try!(set_fd(&err_fd, &mut si.hStdError, false)); + + let cmd_str = make_command_line(program.as_ref().unwrap_or(cfg.program()), + cfg.args()); + let mut pi = zeroed_process_information(); + let mut create_err = None; + + // stolen from the libuv code. + let mut flags = libc::CREATE_UNICODE_ENVIRONMENT; + if cfg.detach() { + flags |= libc::DETACHED_PROCESS | libc::CREATE_NEW_PROCESS_GROUP; + } + + with_envp(cfg.env(), |envp| { + with_dirp(cfg.cwd(), |dirp| { + let mut cmd_str: Vec<u16> = cmd_str.as_slice().utf16_units().collect(); + cmd_str.push(0); + let created = CreateProcessW(ptr::null(), + cmd_str.as_mut_ptr(), + ptr::null_mut(), + ptr::null_mut(), + TRUE, + flags, envp, dirp, + &mut si, &mut pi); + if created == FALSE { + create_err = Some(super::last_error()); + } + }) + }); + + assert!(CloseHandle(si.hStdInput) != 0); + assert!(CloseHandle(si.hStdOutput) != 0); + assert!(CloseHandle(si.hStdError) != 0); + + match create_err { + Some(err) => return Err(err), + None => {} + } + + // We close the thread handle because we don't care about keeping the + // thread id valid, and we aren't keeping the thread handle around to be + // able to close it later. We don't close the process handle however + // because std::we want the process id to stay valid at least until the + // calling code closes the process handle. + assert!(CloseHandle(pi.hThread) != 0); + + Ok(Process { + pid: pi.dwProcessId as pid_t, + handle: pi.hProcess as *mut () + }) + } + } + + /** + * Waits for a process to exit and returns the exit code, failing + * if there is no process with the specified id. + * + * Note that this is private to avoid race conditions on unix where if + * a user calls waitpid(some_process.get_id()) then some_process.finish() + * and some_process.destroy() and some_process.finalize() will then either + * operate on a none-existent process or, even worse, on a newer process + * with the same id. + */ + pub fn wait(&self, deadline: u64) -> IoResult<ProcessExit> { + use libc::types::os::arch::extra::DWORD; + use libc::consts::os::extra::{ + SYNCHRONIZE, + PROCESS_QUERY_INFORMATION, + FALSE, + STILL_ACTIVE, + INFINITE, + WAIT_TIMEOUT, + WAIT_OBJECT_0, + }; + use libc::funcs::extra::kernel32::{ + OpenProcess, + GetExitCodeProcess, + CloseHandle, + WaitForSingleObject, + }; + + unsafe { + let process = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION, + FALSE, + self.pid as DWORD); + if process.is_null() { + return Err(super::last_error()) + } + + loop { + let mut status = 0; + if GetExitCodeProcess(process, &mut status) == FALSE { + let err = Err(super::last_error()); + assert!(CloseHandle(process) != 0); + return err; + } + if status != STILL_ACTIVE { + assert!(CloseHandle(process) != 0); + return Ok(ExitStatus(status as int)); + } + let interval = if deadline == 0 { + INFINITE + } else { + let now = timer::now(); + if deadline < now {0} else {(deadline - now) as u32} + }; + match WaitForSingleObject(process, interval) { + WAIT_OBJECT_0 => {} + WAIT_TIMEOUT => { + assert!(CloseHandle(process) != 0); + return Err(timeout("process wait timed out")) + } + _ => { + let err = Err(super::last_error()); + assert!(CloseHandle(process) != 0); + return err + } + } + } + } + } +} + +fn zeroed_startupinfo() -> libc::types::os::arch::extra::STARTUPINFO { + libc::types::os::arch::extra::STARTUPINFO { + cb: 0, + lpReserved: ptr::null_mut(), + lpDesktop: ptr::null_mut(), + lpTitle: ptr::null_mut(), + dwX: 0, + dwY: 0, + dwXSize: 0, + dwYSize: 0, + dwXCountChars: 0, + dwYCountCharts: 0, + dwFillAttribute: 0, + dwFlags: 0, + wShowWindow: 0, + cbReserved2: 0, + lpReserved2: ptr::null_mut(), + hStdInput: libc::INVALID_HANDLE_VALUE, + hStdOutput: libc::INVALID_HANDLE_VALUE, + hStdError: libc::INVALID_HANDLE_VALUE, + } +} + +fn zeroed_process_information() -> libc::types::os::arch::extra::PROCESS_INFORMATION { + libc::types::os::arch::extra::PROCESS_INFORMATION { + hProcess: ptr::null_mut(), + hThread: ptr::null_mut(), + dwProcessId: 0, + dwThreadId: 0 + } +} + +fn make_command_line(prog: &CString, args: &[CString]) -> String { + let mut cmd = String::new(); + append_arg(&mut cmd, prog.as_str() + .expect("expected program name to be utf-8 encoded")); + for arg in args.iter() { + cmd.push(' '); + append_arg(&mut cmd, arg.as_str() + .expect("expected argument to be utf-8 encoded")); + } + return cmd; + + fn append_arg(cmd: &mut String, arg: &str) { + // If an argument has 0 characters then we need to quote it to ensure + // that it actually gets passed through on the command line or otherwise + // it will be dropped entirely when parsed on the other end. + let quote = arg.chars().any(|c| c == ' ' || c == '\t') || arg.len() == 0; + if quote { + cmd.push('"'); + } + let argvec: Vec<char> = arg.chars().collect(); + for i in range(0u, argvec.len()) { + append_char_at(cmd, argvec.as_slice(), i); + } + if quote { + cmd.push('"'); + } + } + + fn append_char_at(cmd: &mut String, arg: &[char], i: uint) { + match arg[i] { + '"' => { + // Escape quotes. + cmd.push_str("\\\""); + } + '\\' => { + if backslash_run_ends_in_quote(arg, i) { + // Double all backslashes that are in runs before quotes. + cmd.push_str("\\\\"); + } else { + // Pass other backslashes through unescaped. + cmd.push('\\'); + } + } + c => { + cmd.push(c); + } + } + } + + fn backslash_run_ends_in_quote(s: &[char], mut i: uint) -> bool { + while i < s.len() && s[i] == '\\' { + i += 1; + } + return i < s.len() && s[i] == '"'; + } +} + +fn with_envp<K, V, T>(env: Option<&collections::HashMap<K, V>>, + cb: |*mut c_void| -> T) -> T + where K: BytesContainer + Eq + Hash, V: BytesContainer +{ + // On Windows we pass an "environment block" which is not a char**, but + // rather a concatenation of null-terminated k=v\0 sequences, with a final + // \0 to terminate. + match env { + Some(env) => { + let mut blk = Vec::new(); + + for pair in env.iter() { + let kv = format!("{}={}", + pair.ref0().container_as_str().unwrap(), + pair.ref1().container_as_str().unwrap()); + blk.extend(kv.as_slice().utf16_units()); + blk.push(0); + } + + blk.push(0); + + cb(blk.as_mut_ptr() as *mut c_void) + } + _ => cb(ptr::null_mut()) + } +} + +fn with_dirp<T>(d: Option<&CString>, cb: |*const u16| -> T) -> T { + match d { + Some(dir) => { + let dir_str = dir.as_str() + .expect("expected workingdirectory to be utf-8 encoded"); + let mut dir_str: Vec<u16> = dir_str.utf16_units().collect(); + dir_str.push(0); + cb(dir_str.as_ptr()) + }, + None => cb(ptr::null()) + } +} + +fn free_handle(handle: *mut ()) { + assert!(unsafe { + libc::CloseHandle(mem::transmute(handle)) != 0 + }) +} + +#[cfg(test)] +mod tests { + + #[test] + fn test_make_command_line() { + use prelude::*; + use str; + use c_str::CString; + use super::make_command_line; + + fn test_wrapper(prog: &str, args: &[&str]) -> String { + make_command_line(&prog.to_c_str(), + args.iter() + .map(|a| a.to_c_str()) + .collect::<Vec<CString>>() + .as_slice()) + } + + assert_eq!( + test_wrapper("prog", ["aaa", "bbb", "ccc"]), + "prog aaa bbb ccc".to_string() + ); + + assert_eq!( + test_wrapper("C:\\Program Files\\blah\\blah.exe", ["aaa"]), + "\"C:\\Program Files\\blah\\blah.exe\" aaa".to_string() + ); + assert_eq!( + test_wrapper("C:\\Program Files\\test", ["aa\"bb"]), + "\"C:\\Program Files\\test\" aa\\\"bb".to_string() + ); + assert_eq!( + test_wrapper("echo", ["a b c"]), + "echo \"a b c\"".to_string() + ); + assert_eq!( + test_wrapper("\u03c0\u042f\u97f3\u00e6\u221e", []), + "\u03c0\u042f\u97f3\u00e6\u221e".to_string() + ); + } +} diff --git a/src/libstd/sys/windows/tcp.rs b/src/libstd/sys/windows/tcp.rs new file mode 100644 index 00000000000..3baf2be08d2 --- /dev/null +++ b/src/libstd/sys/windows/tcp.rs @@ -0,0 +1,219 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use io::net::ip; +use io::IoResult; +use libc; +use mem; +use ptr; +use prelude::*; +use super::{last_error, last_net_error, retry, sock_t}; +use sync::{Arc, atomic}; +use sys::fs::FileDesc; +use sys::{mod, c, set_nonblocking, wouldblock, timer}; +use sys_common::{mod, timeout, eof}; +use sys_common::net::*; + +pub use sys_common::net::TcpStream; + +pub struct Event(c::WSAEVENT); + +impl Event { + pub fn new() -> IoResult<Event> { + let event = unsafe { c::WSACreateEvent() }; + if event == c::WSA_INVALID_EVENT { + Err(super::last_error()) + } else { + Ok(Event(event)) + } + } + + pub fn handle(&self) -> c::WSAEVENT { let Event(handle) = *self; handle } +} + +impl Drop for Event { + fn drop(&mut self) { + unsafe { let _ = c::WSACloseEvent(self.handle()); } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// TCP listeners +//////////////////////////////////////////////////////////////////////////////// + +pub struct TcpListener { + inner: FileDesc, +} + +impl TcpListener { + pub fn bind(addr: ip::SocketAddr) -> IoResult<TcpListener> { + sys::init_net(); + + let fd = try!(socket(addr, libc::SOCK_STREAM)); + let ret = TcpListener { inner: FileDesc::new(fd as libc::c_int, true) }; + + let mut storage = unsafe { mem::zeroed() }; + let len = addr_to_sockaddr(addr, &mut storage); + let addrp = &storage as *const _ as *const libc::sockaddr; + + match unsafe { libc::bind(fd, addrp, len) } { + -1 => Err(last_net_error()), + _ => Ok(ret), + } + } + + pub fn fd(&self) -> sock_t { self.inner.fd as sock_t } + + pub fn listen(self, backlog: int) -> IoResult<TcpAcceptor> { + match unsafe { libc::listen(self.fd(), backlog as libc::c_int) } { + -1 => Err(last_net_error()), + + _ => { + let accept = try!(Event::new()); + let ret = unsafe { + c::WSAEventSelect(self.fd(), accept.handle(), c::FD_ACCEPT) + }; + if ret != 0 { + return Err(last_net_error()) + } + Ok(TcpAcceptor { + inner: Arc::new(AcceptorInner { + listener: self, + abort: try!(Event::new()), + accept: accept, + closed: atomic::AtomicBool::new(false), + }), + deadline: 0, + }) + } + } + } + + pub fn socket_name(&mut self) -> IoResult<ip::SocketAddr> { + sockname(self.fd(), libc::getsockname) + } +} + +pub struct TcpAcceptor { + inner: Arc<AcceptorInner>, + deadline: u64, +} + +struct AcceptorInner { + listener: TcpListener, + abort: Event, + accept: Event, + closed: atomic::AtomicBool, +} + +impl TcpAcceptor { + pub fn fd(&self) -> sock_t { self.inner.listener.fd() } + + pub fn accept(&mut self) -> IoResult<TcpStream> { + // Unlink unix, windows cannot invoke `select` on arbitrary file + // descriptors like pipes, only sockets. Consequently, windows cannot + // use the same implementation as unix for accept() when close_accept() + // is considered. + // + // In order to implement close_accept() and timeouts, windows uses + // event handles. An acceptor-specific abort event is created which + // will only get set in close_accept(), and it will never be un-set. + // Additionally, another acceptor-specific event is associated with the + // FD_ACCEPT network event. + // + // These two events are then passed to WaitForMultipleEvents to see + // which one triggers first, and the timeout passed to this function is + // the local timeout for the acceptor. + // + // If the wait times out, then the accept timed out. If the wait + // succeeds with the abort event, then we were closed, and if the wait + // succeeds otherwise, then we do a nonblocking poll via `accept` to + // see if we can accept a connection. The connection is candidate to be + // stolen, so we do all of this in a loop as well. + let events = [self.inner.abort.handle(), self.inner.accept.handle()]; + + while !self.inner.closed.load(atomic::SeqCst) { + let ms = if self.deadline == 0 { + c::WSA_INFINITE as u64 + } else { + let now = timer::now(); + if self.deadline < now {0} else {self.deadline - now} + }; + let ret = unsafe { + c::WSAWaitForMultipleEvents(2, events.as_ptr(), libc::FALSE, + ms as libc::DWORD, libc::FALSE) + }; + match ret { + c::WSA_WAIT_TIMEOUT => { + return Err(timeout("accept timed out")) + } + c::WSA_WAIT_FAILED => return Err(last_net_error()), + c::WSA_WAIT_EVENT_0 => break, + n => assert_eq!(n, c::WSA_WAIT_EVENT_0 + 1), + } + + let mut wsaevents: c::WSANETWORKEVENTS = unsafe { mem::zeroed() }; + let ret = unsafe { + c::WSAEnumNetworkEvents(self.fd(), events[1], &mut wsaevents) + }; + if ret != 0 { return Err(last_net_error()) } + + if wsaevents.lNetworkEvents & c::FD_ACCEPT == 0 { continue } + match unsafe { + libc::accept(self.fd(), ptr::null_mut(), ptr::null_mut()) + } { + -1 if wouldblock() => {} + -1 => return Err(last_net_error()), + + // Accepted sockets inherit the same properties as the caller, + // so we need to deregister our event and switch the socket back + // to blocking mode + fd => { + let stream = TcpStream::new(fd); + let ret = unsafe { + c::WSAEventSelect(fd, events[1], 0) + }; + if ret != 0 { return Err(last_net_error()) } + try!(set_nonblocking(fd, false)); + return Ok(stream) + } + } + } + + Err(eof()) + } + + pub fn socket_name(&mut self) -> IoResult<ip::SocketAddr> { + sockname(self.fd(), libc::getsockname) + } + + pub fn set_timeout(&mut self, timeout: Option<u64>) { + self.deadline = timeout.map(|a| timer::now() + a).unwrap_or(0); + } + + pub fn close_accept(&mut self) -> IoResult<()> { + self.inner.closed.store(true, atomic::SeqCst); + let ret = unsafe { c::WSASetEvent(self.inner.abort.handle()) }; + if ret == libc::TRUE { + Ok(()) + } else { + Err(last_net_error()) + } + } +} + +impl Clone for TcpAcceptor { + fn clone(&self) -> TcpAcceptor { + TcpAcceptor { + inner: self.inner.clone(), + deadline: 0, + } + } +} diff --git a/src/libstd/sys/windows/timer.rs b/src/libstd/sys/windows/timer.rs new file mode 100644 index 00000000000..f507be2a985 --- /dev/null +++ b/src/libstd/sys/windows/timer.rs @@ -0,0 +1,208 @@ +// 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. + +//! Timers based on Windows WaitableTimers +//! +//! This implementation is meant to be used solely on windows. As with other +//! implementations, there is a worker thread which is doing all the waiting on +//! a large number of timers for all active timers in the system. This worker +//! thread uses the select() equivalent, WaitForMultipleObjects. One of the +//! objects being waited on is a signal into the worker thread to notify that +//! the incoming channel should be looked at. +//! +//! Other than that, the implementation is pretty straightforward in terms of +//! the other two implementations of timers with nothing *that* new showing up. + +use libc; +use ptr; +use comm; + +use sys::c; +use sys::fs::FileDesc; +use sys_common::helper_thread::Helper; +use prelude::*; +use io::IoResult; + +helper_init!(static HELPER: Helper<Req>) + +pub trait Callback { + fn call(&mut self); +} + +pub struct Timer { + obj: libc::HANDLE, + on_worker: bool, +} + +pub enum Req { + NewTimer(libc::HANDLE, Box<Callback + Send>, bool), + RemoveTimer(libc::HANDLE, Sender<()>), +} + +fn helper(input: libc::HANDLE, messages: Receiver<Req>, _: ()) { + let mut objs = vec![input]; + let mut chans = vec![]; + + 'outer: loop { + let idx = unsafe { + imp::WaitForMultipleObjects(objs.len() as libc::DWORD, + objs.as_ptr(), + 0 as libc::BOOL, + libc::INFINITE) + }; + + if idx == 0 { + loop { + match messages.try_recv() { + Ok(NewTimer(obj, c, one)) => { + objs.push(obj); + chans.push((c, one)); + } + Ok(RemoveTimer(obj, c)) => { + c.send(()); + match objs.iter().position(|&o| o == obj) { + Some(i) => { + drop(objs.remove(i)); + drop(chans.remove(i - 1)); + } + None => {} + } + } + Err(comm::Disconnected) => { + assert_eq!(objs.len(), 1); + assert_eq!(chans.len(), 0); + break 'outer; + } + Err(..) => break + } + } + } else { + let remove = { + match &mut chans[idx as uint - 1] { + &(ref mut c, oneshot) => { c.call(); oneshot } + } + }; + if remove { + drop(objs.remove(idx as uint)); + drop(chans.remove(idx as uint - 1)); + } + } + } +} + +// returns the current time (in milliseconds) +pub fn now() -> u64 { + let mut ticks_per_s = 0; + assert_eq!(unsafe { libc::QueryPerformanceFrequency(&mut ticks_per_s) }, 1); + let ticks_per_s = if ticks_per_s == 0 {1} else {ticks_per_s}; + let mut ticks = 0; + assert_eq!(unsafe { libc::QueryPerformanceCounter(&mut ticks) }, 1); + + return (ticks as u64 * 1000) / (ticks_per_s as u64); +} + +impl Timer { + pub fn new() -> IoResult<Timer> { + HELPER.boot(|| {}, helper); + + let obj = unsafe { + imp::CreateWaitableTimerA(ptr::null_mut(), 0, ptr::null()) + }; + if obj.is_null() { + Err(super::last_error()) + } else { + Ok(Timer { obj: obj, on_worker: false, }) + } + } + + fn remove(&mut self) { + if !self.on_worker { return } + + let (tx, rx) = channel(); + HELPER.send(RemoveTimer(self.obj, tx)); + rx.recv(); + + self.on_worker = false; + } + + pub fn sleep(&mut self, msecs: u64) { + self.remove(); + + // there are 10^6 nanoseconds in a millisecond, and the parameter is in + // 100ns intervals, so we multiply by 10^4. + let due = -(msecs as i64 * 10000) as libc::LARGE_INTEGER; + assert_eq!(unsafe { + imp::SetWaitableTimer(self.obj, &due, 0, ptr::null_mut(), + ptr::null_mut(), 0) + }, 1); + + let _ = unsafe { imp::WaitForSingleObject(self.obj, libc::INFINITE) }; + } + + pub fn oneshot(&mut self, msecs: u64, cb: Box<Callback + Send>) { + self.remove(); + + // see above for the calculation + let due = -(msecs as i64 * 10000) as libc::LARGE_INTEGER; + assert_eq!(unsafe { + imp::SetWaitableTimer(self.obj, &due, 0, ptr::null_mut(), + ptr::null_mut(), 0) + }, 1); + + HELPER.send(NewTimer(self.obj, cb, true)); + self.on_worker = true; + } + + pub fn period(&mut self, msecs: u64, cb: Box<Callback + Send>) { + self.remove(); + + // see above for the calculation + let due = -(msecs as i64 * 10000) as libc::LARGE_INTEGER; + assert_eq!(unsafe { + imp::SetWaitableTimer(self.obj, &due, msecs as libc::LONG, + ptr::null_mut(), ptr::null_mut(), 0) + }, 1); + + HELPER.send(NewTimer(self.obj, cb, false)); + self.on_worker = true; + } +} + +impl Drop for Timer { + fn drop(&mut self) { + self.remove(); + assert!(unsafe { libc::CloseHandle(self.obj) != 0 }); + } +} + +mod imp { + use libc::{LPSECURITY_ATTRIBUTES, BOOL, LPCSTR, HANDLE, LARGE_INTEGER, + LONG, LPVOID, DWORD, c_void}; + + pub type PTIMERAPCROUTINE = *mut c_void; + + extern "system" { + pub fn CreateWaitableTimerA(lpTimerAttributes: LPSECURITY_ATTRIBUTES, + bManualReset: BOOL, + lpTimerName: LPCSTR) -> HANDLE; + pub fn SetWaitableTimer(hTimer: HANDLE, + pDueTime: *const LARGE_INTEGER, + lPeriod: LONG, + pfnCompletionRoutine: PTIMERAPCROUTINE, + lpArgToCompletionRoutine: LPVOID, + fResume: BOOL) -> BOOL; + pub fn WaitForMultipleObjects(nCount: DWORD, + lpHandles: *const HANDLE, + bWaitAll: BOOL, + dwMilliseconds: DWORD) -> DWORD; + pub fn WaitForSingleObject(hHandle: HANDLE, + dwMilliseconds: DWORD) -> DWORD; + } +} diff --git a/src/libstd/sys/windows/tty.rs b/src/libstd/sys/windows/tty.rs new file mode 100644 index 00000000000..7d001e6394c --- /dev/null +++ b/src/libstd/sys/windows/tty.rs @@ -0,0 +1,166 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// ignore-lexer-test FIXME #15877 + +//! Windows specific console TTY implementation +//! +//! This module contains the implementation of a Windows specific console TTY. +//! Also converts between UTF-16 and UTF-8. Windows has very poor support for +//! UTF-8 and some functions will fail. In particular ReadFile and ReadConsole +//! will fail when the codepage is set to UTF-8 and a Unicode character is +//! entered. +//! +//! FIXME +//! This implementation does not account for codepoints that are split across +//! multiple reads and writes. Also, this implementation does not expose a way +//! to read/write UTF-16 directly. When/if Rust receives a Reader/Writer +//! wrapper that performs encoding/decoding, this implementation should switch +//! to working in raw UTF-16, with such a wrapper around it. + +use super::c::{ReadConsoleW, WriteConsoleW, GetConsoleMode, SetConsoleMode}; +use super::c::{ERROR_ILLEGAL_CHARACTER}; +use super::c::{ENABLE_ECHO_INPUT, ENABLE_EXTENDED_FLAGS}; +use super::c::{ENABLE_INSERT_MODE, ENABLE_LINE_INPUT}; +use super::c::{ENABLE_PROCESSED_INPUT, ENABLE_QUICK_EDIT_MODE}; +use libc::{c_int, HANDLE, LPDWORD, DWORD, LPVOID}; +use libc::{get_osfhandle, CloseHandle}; +use libc::types::os::arch::extra::LPCVOID; +use io::{mod, IoError, IoResult, MemReader}; +use prelude::*; +use ptr; +use str::from_utf8; + +fn invalid_encoding() -> IoError { + IoError { + kind: io::InvalidInput, + desc: "text was not valid unicode", + detail: None, + } +} + +pub fn is_tty(fd: c_int) -> bool { + let mut out: DWORD = 0; + // If this function doesn't fail then fd is a TTY + match unsafe { GetConsoleMode(get_osfhandle(fd) as HANDLE, + &mut out as LPDWORD) } { + 0 => false, + _ => true, + } +} + +pub struct TTY { + closeme: bool, + handle: HANDLE, + utf8: MemReader, +} + +impl TTY { + pub fn new(fd: c_int) -> IoResult<TTY> { + if is_tty(fd) { + // If the file descriptor is one of stdin, stderr, or stdout + // then it should not be closed by us + let closeme = match fd { + 0...2 => false, + _ => true, + }; + let handle = unsafe { get_osfhandle(fd) as HANDLE }; + Ok(TTY { + handle: handle, + utf8: MemReader::new(Vec::new()), + closeme: closeme, + }) + } else { + Err(IoError { + kind: io::MismatchedFileTypeForOperation, + desc: "invalid handle provided to function", + detail: None, + }) + } + } + + pub fn read(&mut self, buf: &mut [u8]) -> IoResult<uint> { + // Read more if the buffer is empty + if self.utf8.eof() { + let mut utf16 = Vec::from_elem(0x1000, 0u16); + let mut num: DWORD = 0; + match unsafe { ReadConsoleW(self.handle, + utf16.as_mut_ptr() as LPVOID, + utf16.len() as u32, + &mut num as LPDWORD, + ptr::null_mut()) } { + 0 => return Err(super::last_error()), + _ => (), + }; + utf16.truncate(num as uint); + let utf8 = match String::from_utf16(utf16.as_slice()) { + Some(utf8) => utf8.into_bytes(), + None => return Err(invalid_encoding()), + }; + self.utf8 = MemReader::new(utf8); + } + // MemReader shouldn't error here since we just filled it + Ok(self.utf8.read(buf).unwrap()) + } + + pub fn write(&mut self, buf: &[u8]) -> IoResult<()> { + let utf16 = match from_utf8(buf) { + Some(utf8) => { + utf8.as_slice().utf16_units().collect::<Vec<u16>>() + } + None => return Err(invalid_encoding()), + }; + let mut num: DWORD = 0; + match unsafe { WriteConsoleW(self.handle, + utf16.as_ptr() as LPCVOID, + utf16.len() as u32, + &mut num as LPDWORD, + ptr::null_mut()) } { + 0 => Err(super::last_error()), + _ => Ok(()), + } + } + + pub fn set_raw(&mut self, raw: bool) -> IoResult<()> { + // FIXME + // Somebody needs to decide on which of these flags we want + match unsafe { SetConsoleMode(self.handle, + match raw { + true => 0, + false => ENABLE_ECHO_INPUT | ENABLE_EXTENDED_FLAGS | + ENABLE_INSERT_MODE | ENABLE_LINE_INPUT | + ENABLE_PROCESSED_INPUT | ENABLE_QUICK_EDIT_MODE, + }) } { + 0 => Err(super::last_error()), + _ => Ok(()), + } + } + + pub fn get_winsize(&mut self) -> IoResult<(int, int)> { + // FIXME + // Get console buffer via CreateFile with CONOUT$ + // Make a CONSOLE_SCREEN_BUFFER_INFO + // Call GetConsoleScreenBufferInfo + // Maybe call GetLargestConsoleWindowSize instead? + Err(super::unimpl()) + } + + // Let us magically declare this as a TTY + pub fn isatty(&self) -> bool { true } +} + +impl Drop for TTY { + fn drop(&mut self) { + if self.closeme { + // Nobody cares about the return value + let _ = unsafe { CloseHandle(self.handle) }; + } + } +} diff --git a/src/libstd/sys/windows/udp.rs b/src/libstd/sys/windows/udp.rs new file mode 100644 index 00000000000..50f8fb828ad --- /dev/null +++ b/src/libstd/sys/windows/udp.rs @@ -0,0 +1,11 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +pub use sys_common::net::UdpSocket; |
