From 3a527f2b3311d5b1c6dd7c72db71c45596e6db49 Mon Sep 17 00:00:00 2001 From: Aaron Turon Date: Tue, 30 Sep 2014 17:03:56 -0700 Subject: Runtime removal: add private sys, sys_common modules These modules will house the code that used to be part of the runtime system in libnative. The `sys_common` module contains a few low-level but cross-platform details. The `sys` module is set up using `#[cfg()]` to include either a unix or windows implementation of a common API surface. This API surface is *not* exported directly in `libstd`, but is instead used to bulid `std::os` and `std::io`. Ultimately, the low-level details in `sys` will be exposed in a controlled way through a separate platform-specific surface, but that setup is not part of this patch. --- src/libstd/io/mod.rs | 92 +-------------- src/libstd/lib.rs | 7 ++ src/libstd/os.rs | 155 +------------------------ src/libstd/sys/common/mod.rs | 91 +++++++++++++++ src/libstd/sys/unix/c.rs | 262 ++++++++++++++++++++++++++++++++++++++++++ src/libstd/sys/unix/mod.rs | 92 +++++++++++++++ src/libstd/sys/unix/os.rs | 101 ++++++++++++++++ src/libstd/sys/windows/c.rs | 250 ++++++++++++++++++++++++++++++++++++++++ src/libstd/sys/windows/mod.rs | 178 ++++++++++++++++++++++++++++ src/libstd/sys/windows/os.rs | 103 +++++++++++++++++ 10 files changed, 1093 insertions(+), 238 deletions(-) create mode 100644 src/libstd/sys/common/mod.rs create mode 100644 src/libstd/sys/unix/c.rs create mode 100644 src/libstd/sys/unix/mod.rs create mode 100644 src/libstd/sys/unix/os.rs create mode 100644 src/libstd/sys/windows/c.rs create mode 100644 src/libstd/sys/windows/mod.rs create mode 100644 src/libstd/sys/windows/os.rs (limited to 'src/libstd') diff --git a/src/libstd/io/mod.rs b/src/libstd/io/mod.rs index c404741b7c3..78abbb9f80d 100644 --- a/src/libstd/io/mod.rs +++ b/src/libstd/io/mod.rs @@ -236,8 +236,7 @@ use os; use boxed::Box; use result::{Ok, Err, Result}; use rt::rtio; -use slice::{AsSlice, SlicePrelude}; -use str::{Str, StrPrelude}; +use sys; use str; use string::String; use uint; @@ -312,91 +311,10 @@ 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()) } } 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..175e23bf819 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,7 @@ use ops::Drop; use option::{Some, None, Option}; use os; use path::{Path, GenericPath, BytesContainer}; +use sys::os as os_imp; use ptr::RawPtr; use ptr; use result::{Err, Ok, Result}; @@ -905,59 +906,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 +920,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/mod.rs b/src/libstd/sys/common/mod.rs new file mode 100644 index 00000000000..402c62bb35e --- /dev/null +++ b/src/libstd/sys/common/mod.rs @@ -0,0 +1,91 @@ +// 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 or the MIT license +// , 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; + +// 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, + } +} + +// unix has nonzero values as errors +pub fn mkerr_libc(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 { + fn program(&self) -> &CString; + fn args(&self) -> &[CString]; + fn env(&self) -> Option<&collections::HashMap>; + fn cwd(&self) -> Option<&CString>; + fn uid(&self) -> Option; + fn gid(&self) -> Option; + fn detach(&self) -> bool; +} 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 or the MIT license +// , 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/mod.rs b/src/libstd/sys/unix/mod.rs new file mode 100644 index 00000000000..ad5de2dad48 --- /dev/null +++ b/src/libstd/sys/unix/mod.rs @@ -0,0 +1,92 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +extern crate libc; + +use num; +use prelude::*; +use io::{mod, IoResult, IoError}; + +pub mod fs; +pub mod os; +pub mod c; + +pub type sock_t = io::file::fd_t; +pub type wrlen = libc::size_t; +pub unsafe fn close_sock(sock: sock_t) { let _ = libc::close(sock); } + +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 +} + +/// 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 } +} + +#[inline] +pub fn retry> (f: || -> I) -> I { + let minus_one = -num::one::(); + 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: net::sock_t, nb: bool) -> IoResult<()> { + let set = nb as libc::c_int; + super::mkerr_libc(retry(|| unsafe { c::ioctl(fd, c::FIONBIO, &set) })) +} diff --git a/src/libstd/sys/unix/os.rs b/src/libstd/sys/unix/os.rs new file mode 100644 index 00000000000..34699eb27c1 --- /dev/null +++ b/src/libstd/sys/unix/os.rs @@ -0,0 +1,101 @@ +// 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 or the MIT license +// , 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 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) + } +} 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 or the MIT license +// , 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 = 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/mod.rs b/src/libstd/sys/windows/mod.rs new file mode 100644 index 00000000000..5f4129c1484 --- /dev/null +++ b/src/libstd/sys/windows/mod.rs @@ -0,0 +1,178 @@ +// 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 or the MIT license +// , 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 fs; +pub mod os; +pub mod c; + +pub type sock_t = libc::SOCKET; +pub type wrlen = 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 (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(()) + } +} + +// FIXME: call this +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> { + match s { + Some(s) => Ok({ + let mut s = s.utf16_units().collect::>(); + 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 or the MIT license +// , 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()), + } +} -- cgit 1.4.1-3-g733a5 From 0c1e1ff1e300868a29405a334e65eae690df971d Mon Sep 17 00:00:00 2001 From: Aaron Turon Date: Tue, 30 Sep 2014 17:34:14 -0700 Subject: Runtime removal: refactor fs This moves the filesystem implementation from libnative into the new `sys` modules, refactoring along the way and hooking into `std::io::fs`. Because this eliminates APIs in `libnative` and `librustrt`, it is a: [breaking-change] This functionality is likely to be available publicly, in some form, from `std` in the future. --- src/libnative/io/file_unix.rs | 554 ---------------------------------- src/libnative/io/file_windows.rs | 523 -------------------------------- src/libnative/io/mod.rs | 83 ----- src/libnative/io/timer_unix.rs | 2 +- src/librustrt/rtio.rs | 86 ------ src/libstd/io/fs.rs | 304 ++++++------------- src/libstd/io/mod.rs | 1 + src/libstd/io/stdio.rs | 14 +- src/libstd/platform_imp/unix/fs.rs | 411 +++++++++++++++++++++++++ src/libstd/platform_imp/windows/fs.rs | 460 ++++++++++++++++++++++++++++ 10 files changed, 967 insertions(+), 1471 deletions(-) delete mode 100644 src/libnative/io/file_unix.rs delete mode 100644 src/libnative/io/file_windows.rs create mode 100644 src/libstd/platform_imp/unix/fs.rs create mode 100644 src/libstd/platform_imp/windows/fs.rs (limited to 'src/libstd') diff --git a/src/libnative/io/file_unix.rs b/src/libnative/io/file_unix.rs deleted file mode 100644 index f616295c73d..00000000000 --- a/src/libnative/io/file_unix.rs +++ /dev/null @@ -1,554 +0,0 @@ -// 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 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -//! Blocking posix-based file I/O - -use alloc::arc::Arc; -use libc::{mod, c_int, c_void}; -use std::c_str::CString; -use std::mem; -use std::rt::rtio::{mod, IoResult}; - -use io::{retry, keep_going}; -use io::util; - -pub type fd_t = libc::c_int; - -struct Inner { - fd: fd_t, - close_on_drop: bool, -} - -pub struct FileDesc { - inner: Arc -} - -impl FileDesc { - /// Create a `FileDesc` from an open C file descriptor. - /// - /// The `FileDesc` will take ownership of the specified file descriptor and - /// close it upon destruction if the `close_on_drop` flag is true, otherwise - /// it will not close the file descriptor when this `FileDesc` is dropped. - /// - /// Note that all I/O operations done on this object will be *blocking*, but - /// they do not require the runtime to be active. - pub fn new(fd: fd_t, close_on_drop: bool) -> FileDesc { - FileDesc { inner: Arc::new(Inner { - fd: fd, - close_on_drop: close_on_drop - }) } - } - - // FIXME(#10465) these functions should not be public, but anything in - // native::io wanting to use them is forced to have all the - // rtio traits in scope - pub fn inner_read(&mut self, buf: &mut [u8]) -> IoResult { - 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(util::eof()) - } else if ret < 0 { - Err(super::last_error()) - } else { - Ok(ret as uint) - } - } - pub fn inner_write(&mut 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.inner.fd } -} - -impl rtio::RtioFileStream for FileDesc { - fn read(&mut self, buf: &mut [u8]) -> IoResult { - self.inner_read(buf).map(|i| i as int) - } - fn write(&mut self, buf: &[u8]) -> IoResult<()> { - self.inner_write(buf) - } - fn pread(&mut self, buf: &mut [u8], offset: u64) -> IoResult { - match retry(|| unsafe { - libc::pread(self.fd(), buf.as_ptr() as *mut _, - buf.len() as libc::size_t, - offset as libc::off_t) - }) { - -1 => Err(super::last_error()), - n => Ok(n as int) - } - } - fn pwrite(&mut self, buf: &[u8], offset: u64) -> IoResult<()> { - super::mkerr_libc(retry(|| unsafe { - libc::pwrite(self.fd(), buf.as_ptr() as *const _, - buf.len() as libc::size_t, offset as libc::off_t) - })) - } - fn seek(&mut self, pos: i64, whence: rtio::SeekStyle) -> IoResult { - let whence = match whence { - rtio::SeekSet => libc::SEEK_SET, - rtio::SeekEnd => libc::SEEK_END, - rtio::SeekCur => libc::SEEK_CUR, - }; - let n = unsafe { libc::lseek(self.fd(), pos as libc::off_t, whence) }; - if n < 0 { - Err(super::last_error()) - } else { - Ok(n as u64) - } - } - fn tell(&self) -> IoResult { - let n = unsafe { libc::lseek(self.fd(), 0, libc::SEEK_CUR) }; - if n < 0 { - Err(super::last_error()) - } else { - Ok(n as u64) - } - } - fn fsync(&mut self) -> IoResult<()> { - super::mkerr_libc(retry(|| unsafe { libc::fsync(self.fd()) })) - } - fn datasync(&mut self) -> IoResult<()> { - return super::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) }) - } - } - fn truncate(&mut self, offset: i64) -> IoResult<()> { - super::mkerr_libc(retry(|| unsafe { - libc::ftruncate(self.fd(), offset as libc::off_t) - })) - } - - fn fstat(&mut self) -> IoResult { - let mut stat: libc::stat = unsafe { mem::zeroed() }; - match unsafe { libc::fstat(self.fd(), &mut stat) } { - 0 => Ok(mkstat(&stat)), - _ => Err(super::last_error()), - } - } -} - -impl rtio::RtioPipe for FileDesc { - fn read(&mut self, buf: &mut [u8]) -> IoResult { - self.inner_read(buf) - } - fn write(&mut self, buf: &[u8]) -> IoResult<()> { - self.inner_write(buf) - } - fn clone(&self) -> Box { - box FileDesc { inner: self.inner.clone() } as Box - } - - // Only supported on named pipes currently. Note that this doesn't have an - // impact on the std::io primitives, this is never called via - // std::io::PipeStream. If the functionality is exposed in the future, then - // these methods will need to be implemented. - fn close_read(&mut self) -> IoResult<()> { - Err(super::unimpl()) - } - fn close_write(&mut self) -> IoResult<()> { - Err(super::unimpl()) - } - fn set_timeout(&mut self, _t: Option) {} - fn set_read_timeout(&mut self, _t: Option) {} - fn set_write_timeout(&mut self, _t: Option) {} -} - -impl rtio::RtioTTY for FileDesc { - fn read(&mut self, buf: &mut [u8]) -> IoResult { - self.inner_read(buf) - } - fn write(&mut self, buf: &[u8]) -> IoResult<()> { - self.inner_write(buf) - } - fn set_raw(&mut self, _raw: bool) -> IoResult<()> { - Err(super::unimpl()) - } - fn get_winsize(&mut self) -> IoResult<(int, int)> { - Err(super::unimpl()) - } - fn isatty(&self) -> bool { false } -} - -impl Drop for Inner { - 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 struct CFile { - file: *mut libc::FILE, - fd: FileDesc, -} - -impl CFile { - /// Create a `CFile` from an open `FILE` pointer. - /// - /// The `CFile` takes ownership of the `FILE` pointer and will close it upon - /// destruction. - pub fn new(file: *mut libc::FILE) -> CFile { - CFile { - file: file, - fd: FileDesc::new(unsafe { libc::fileno(file) }, false) - } - } - - pub fn flush(&mut self) -> IoResult<()> { - super::mkerr_libc(retry(|| unsafe { libc::fflush(self.file) })) - } -} - -impl rtio::RtioFileStream for CFile { - fn read(&mut self, buf: &mut [u8]) -> IoResult { - let ret = keep_going(buf, |buf, len| { - unsafe { - libc::fread(buf as *mut libc::c_void, 1, len as libc::size_t, - self.file) as i64 - } - }); - if ret == 0 { - Err(util::eof()) - } else if ret < 0 { - Err(super::last_error()) - } else { - Ok(ret as int) - } - } - - fn write(&mut self, buf: &[u8]) -> IoResult<()> { - let ret = keep_going(buf, |buf, len| { - unsafe { - libc::fwrite(buf as *const libc::c_void, 1, len as libc::size_t, - self.file) as i64 - } - }); - if ret < 0 { - Err(super::last_error()) - } else { - Ok(()) - } - } - - fn pread(&mut self, buf: &mut [u8], offset: u64) -> IoResult { - self.flush().and_then(|()| self.fd.pread(buf, offset)) - } - fn pwrite(&mut self, buf: &[u8], offset: u64) -> IoResult<()> { - self.flush().and_then(|()| self.fd.pwrite(buf, offset)) - } - fn seek(&mut self, pos: i64, style: rtio::SeekStyle) -> IoResult { - let whence = match style { - rtio::SeekSet => libc::SEEK_SET, - rtio::SeekEnd => libc::SEEK_END, - rtio::SeekCur => libc::SEEK_CUR, - }; - let n = unsafe { libc::fseek(self.file, pos as libc::c_long, whence) }; - if n < 0 { - Err(super::last_error()) - } else { - Ok(n as u64) - } - } - fn tell(&self) -> IoResult { - let ret = unsafe { libc::ftell(self.file) }; - if ret < 0 { - Err(super::last_error()) - } else { - Ok(ret as u64) - } - } - fn fsync(&mut self) -> IoResult<()> { - self.flush().and_then(|()| self.fd.fsync()) - } - fn datasync(&mut self) -> IoResult<()> { - self.flush().and_then(|()| self.fd.datasync()) - } - fn truncate(&mut self, offset: i64) -> IoResult<()> { - self.flush().and_then(|()| self.fd.truncate(offset)) - } - - fn fstat(&mut self) -> IoResult { - self.flush().and_then(|()| self.fd.fstat()) - } -} - -impl Drop for CFile { - fn drop(&mut self) { - unsafe { let _ = libc::fclose(self.file); } - } -} - -pub fn open(path: &CString, fm: rtio::FileMode, fa: rtio::FileAccess) - -> IoResult -{ - let flags = match fm { - rtio::Open => 0, - rtio::Append => libc::O_APPEND, - rtio::Truncate => libc::O_TRUNC, - }; - // Opening with a write permission must silently create the file. - let (flags, mode) = match fa { - rtio::Read => (flags | libc::O_RDONLY, 0), - rtio::Write => (flags | libc::O_WRONLY | libc::O_CREAT, - libc::S_IRUSR | libc::S_IWUSR), - rtio::ReadWrite => (flags | libc::O_RDWR | libc::O_CREAT, - libc::S_IRUSR | libc::S_IWUSR), - }; - - 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: &CString, mode: uint) -> IoResult<()> { - super::mkerr_libc(unsafe { libc::mkdir(p.as_ptr(), mode as libc::mode_t) }) -} - -pub fn readdir(p: &CString) -> IoResult> { - use libc::{dirent_t}; - use libc::{opendir, readdir_r, closedir}; - - fn prune(root: &CString, dirs: Vec) -> Vec { - 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).to_c_str()).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::::with_capacity(size as uint); - let ptr = buf.as_mut_slice().as_mut_ptr() as *mut dirent_t; - - 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: &CString) -> IoResult<()> { - super::mkerr_libc(unsafe { libc::unlink(p.as_ptr()) }) -} - -pub fn rename(old: &CString, new: &CString) -> IoResult<()> { - super::mkerr_libc(unsafe { libc::rename(old.as_ptr(), new.as_ptr()) }) -} - -pub fn chmod(p: &CString, mode: uint) -> IoResult<()> { - super::mkerr_libc(retry(|| unsafe { - libc::chmod(p.as_ptr(), mode as libc::mode_t) - })) -} - -pub fn rmdir(p: &CString) -> IoResult<()> { - super::mkerr_libc(unsafe { libc::rmdir(p.as_ptr()) }) -} - -pub fn chown(p: &CString, uid: int, gid: int) -> IoResult<()> { - super::mkerr_libc(retry(|| unsafe { - libc::chown(p.as_ptr(), uid as libc::uid_t, - gid as libc::gid_t) - })) -} - -pub fn readlink(p: &CString) -> IoResult { - let p = p.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 = 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(buf.as_slice().to_c_str()) - } - } -} - -pub fn symlink(src: &CString, dst: &CString) -> IoResult<()> { - super::mkerr_libc(unsafe { libc::symlink(src.as_ptr(), dst.as_ptr()) }) -} - -pub fn link(src: &CString, dst: &CString) -> IoResult<()> { - super::mkerr_libc(unsafe { libc::link(src.as_ptr(), dst.as_ptr()) }) -} - -fn mkstat(stat: &libc::stat) -> rtio::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 } - - rtio::FileStat { - size: stat.st_size as u64, - kind: stat.st_mode as u64, - perm: stat.st_mode as u64, - 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), - device: stat.st_dev as u64, - inode: stat.st_ino as u64, - rdev: stat.st_rdev as u64, - nlink: stat.st_nlink as u64, - uid: stat.st_uid as u64, - gid: stat.st_gid as u64, - blksize: stat.st_blksize as u64, - blocks: stat.st_blocks as u64, - flags: flags(stat), - gen: gen(stat), - } -} - -pub fn stat(p: &CString) -> IoResult { - 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: &CString) -> IoResult { - 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: &CString, atime: u64, mtime: u64) -> IoResult<()> { - let buf = libc::utimbuf { - actime: (atime / 1000) as libc::time_t, - modtime: (mtime / 1000) as libc::time_t, - }; - super::mkerr_libc(unsafe { libc::utime(p.as_ptr(), &buf) }) -} - -#[cfg(test)] -mod tests { - use super::{CFile, FileDesc}; - use libc; - use std::os; - use std::rt::rtio::{RtioFileStream, SeekSet}; - - #[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.inner_write(b"test").ok().unwrap(); - let mut buf = [0u8, ..4]; - match reader.inner_read(buf) { - Ok(4) => { - assert_eq!(buf[0], 't' as u8); - assert_eq!(buf[1], 'e' as u8); - assert_eq!(buf[2], 's' as u8); - assert_eq!(buf[3], 't' as u8); - } - r => panic!("invalid read: {}", r), - } - - assert!(writer.inner_read(buf).is_err()); - assert!(reader.inner_write(buf).is_err()); - } - - #[test] - fn test_cfile() { - unsafe { - let f = libc::tmpfile(); - assert!(!f.is_null()); - let mut file = CFile::new(f); - - file.write(b"test").ok().unwrap(); - let mut buf = [0u8, ..4]; - let _ = file.seek(0, SeekSet).ok().unwrap(); - match file.read(buf) { - Ok(4) => { - assert_eq!(buf[0], 't' as u8); - assert_eq!(buf[1], 'e' as u8); - assert_eq!(buf[2], 's' as u8); - assert_eq!(buf[3], 't' as u8); - } - r => panic!("invalid read: {}", r) - } - } - } -} diff --git a/src/libnative/io/file_windows.rs b/src/libnative/io/file_windows.rs deleted file mode 100644 index eb4d4f22132..00000000000 --- a/src/libnative/io/file_windows.rs +++ /dev/null @@ -1,523 +0,0 @@ -// 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 or the MIT license -// , 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 std::c_str::CString; -use std::mem; -use std::os::windows::fill_utf16_buf_and_decode; -use std::ptr; -use std::rt::rtio; -use std::rt::rtio::{IoResult, IoError}; -use std::str; - -pub type fd_t = libc::c_int; - -struct Inner { - fd: fd_t, - close_on_drop: bool, -} - -pub struct FileDesc { - inner: Arc -} - -impl FileDesc { - /// Create a `FileDesc` from an open C file descriptor. - /// - /// The `FileDesc` will take ownership of the specified file descriptor and - /// close it upon destruction if the `close_on_drop` flag is true, otherwise - /// it will not close the file descriptor when this `FileDesc` is dropped. - /// - /// Note that all I/O operations done on this object will be *blocking*, but - /// they do not require the runtime to be active. - pub fn new(fd: fd_t, close_on_drop: bool) -> FileDesc { - FileDesc { inner: Arc::new(Inner { - fd: fd, - close_on_drop: close_on_drop - }) } - } - - pub fn inner_read(&mut self, buf: &mut [u8]) -> IoResult { - 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 inner_write(&mut 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.inner.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: rtio::SeekStyle) -> IoResult { - let whence = match style { - rtio::SeekSet => libc::FILE_BEGIN, - rtio::SeekEnd => libc::FILE_END, - rtio::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), - } - } - } - -} - -impl rtio::RtioFileStream for FileDesc { - fn read(&mut self, buf: &mut [u8]) -> IoResult { - self.inner_read(buf).map(|i| i as int) - } - fn write(&mut self, buf: &[u8]) -> IoResult<()> { - self.inner_write(buf) - } - - fn pread(&mut self, buf: &mut [u8], offset: u64) -> IoResult { - let mut read = 0; - let mut overlap: libc::OVERLAPPED = unsafe { mem::zeroed() }; - overlap.Offset = offset as libc::DWORD; - overlap.OffsetHigh = (offset >> 32) as libc::DWORD; - let ret = unsafe { - libc::ReadFile(self.handle(), buf.as_ptr() as libc::LPVOID, - buf.len() as libc::DWORD, &mut read, - &mut overlap) - }; - if ret != 0 { - Ok(read as int) - } else { - Err(super::last_error()) - } - } - fn pwrite(&mut self, buf: &[u8], mut offset: u64) -> IoResult<()> { - let mut cur = buf.as_ptr(); - let mut remaining = buf.len(); - let mut overlap: libc::OVERLAPPED = unsafe { mem::zeroed() }; - while remaining > 0 { - overlap.Offset = offset as libc::DWORD; - overlap.OffsetHigh = (offset >> 32) as libc::DWORD; - let mut amt = 0; - let ret = unsafe { - libc::WriteFile(self.handle(), cur as libc::LPVOID, - remaining as libc::DWORD, &mut amt, - &mut overlap) - }; - if ret != 0 { - remaining -= amt as uint; - cur = unsafe { cur.offset(amt as int) }; - offset += amt as u64; - } else { - return Err(super::last_error()) - } - } - Ok(()) - } - - fn seek(&mut self, pos: i64, style: rtio::SeekStyle) -> IoResult { - self.seek_common(pos, style) - } - - fn tell(&self) -> IoResult { - self.seek_common(0, rtio::SeekCur) - } - - fn fsync(&mut self) -> IoResult<()> { - super::mkerr_winbool(unsafe { - libc::FlushFileBuffers(self.handle()) - }) - } - - fn datasync(&mut self) -> IoResult<()> { return self.fsync(); } - - fn truncate(&mut self, offset: i64) -> IoResult<()> { - let orig_pos = try!(self.tell()); - let _ = try!(self.seek(offset, rtio::SeekSet)); - let ret = unsafe { - match libc::SetEndOfFile(self.handle()) { - 0 => Err(super::last_error()), - _ => Ok(()) - } - }; - let _ = self.seek(orig_pos as i64, rtio::SeekSet); - return ret; - } - - fn fstat(&mut self) -> IoResult { - let mut stat: libc::stat = unsafe { mem::zeroed() }; - match unsafe { libc::fstat(self.fd(), &mut stat) } { - 0 => Ok(mkstat(&stat)), - _ => Err(super::last_error()), - } - } -} - -impl rtio::RtioPipe for FileDesc { - fn read(&mut self, buf: &mut [u8]) -> IoResult { - self.inner_read(buf) - } - fn write(&mut self, buf: &[u8]) -> IoResult<()> { - self.inner_write(buf) - } - fn clone(&self) -> Box { - box FileDesc { inner: self.inner.clone() } as Box - } - - // Only supported on named pipes currently. Note that this doesn't have an - // impact on the std::io primitives, this is never called via - // std::io::PipeStream. If the functionality is exposed in the future, then - // these methods will need to be implemented. - fn close_read(&mut self) -> IoResult<()> { - Err(super::unimpl()) - } - fn close_write(&mut self) -> IoResult<()> { - Err(super::unimpl()) - } - fn set_timeout(&mut self, _t: Option) {} - fn set_read_timeout(&mut self, _t: Option) {} - fn set_write_timeout(&mut self, _t: Option) {} -} - -impl rtio::RtioTTY for FileDesc { - fn read(&mut self, buf: &mut [u8]) -> IoResult { - self.inner_read(buf) - } - fn write(&mut self, buf: &[u8]) -> IoResult<()> { - self.inner_write(buf) - } - fn set_raw(&mut self, _raw: bool) -> IoResult<()> { - Err(super::unimpl()) - } - fn get_winsize(&mut self) -> IoResult<(int, int)> { - Err(super::unimpl()) - } - fn isatty(&self) -> bool { false } -} - -impl Drop for Inner { - 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: &CString) -> IoResult> { - match s.as_str() { - Some(s) => Ok({ - let mut s = s.utf16_units().collect::>(); - s.push(0); - s - }), - None => Err(IoError { - code: libc::ERROR_INVALID_NAME as uint, - extra: 0, - detail: Some("valid unicode input required".to_string()), - }) - } -} - -pub fn open(path: &CString, fm: rtio::FileMode, fa: rtio::FileAccess) - -> IoResult { - // Flags passed to open_osfhandle - let flags = match fm { - rtio::Open => 0, - rtio::Append => libc::O_APPEND, - rtio::Truncate => libc::O_TRUNC, - }; - let flags = match fa { - rtio::Read => flags | libc::O_RDONLY, - rtio::Write => flags | libc::O_WRONLY | libc::O_CREAT, - rtio::ReadWrite => flags | libc::O_RDWR | libc::O_CREAT, - }; - - let mut dwDesiredAccess = match fa { - rtio::Read => libc::FILE_GENERIC_READ, - rtio::Write => libc::FILE_GENERIC_WRITE, - rtio::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) { - (rtio::Truncate, rtio::Read) => libc::TRUNCATE_EXISTING, - (rtio::Truncate, _) => libc::CREATE_ALWAYS, - (rtio::Open, rtio::Read) => libc::OPEN_EXISTING, - (rtio::Open, _) => libc::OPEN_ALWAYS, - (rtio::Append, rtio::Read) => { - dwDesiredAccess |= libc::FILE_APPEND_DATA; - libc::OPEN_EXISTING - } - (rtio::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: &CString, _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: &CString) -> IoResult> { - fn prune(root: &CString, dirs: Vec) -> Vec { - 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).to_c_str()).collect() - } - - let star = Path::new(unsafe { - CString::new(p.as_ptr(), false) - }).join("*"); - let path = try!(to_utf16(&star.to_c_str())); - - 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 { - code: super::c::ERROR_ILLEGAL_CHARACTER as uint, - extra: 0, - 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: &CString) -> IoResult<()> { - let p = try!(to_utf16(p)); - super::mkerr_winbool(unsafe { - libc::DeleteFileW(p.as_ptr()) - }) -} - -pub fn rename(old: &CString, new: &CString) -> 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: &CString, mode: uint) -> IoResult<()> { - let p = try!(to_utf16(p)); - super::mkerr_libc(unsafe { - libc::wchmod(p.as_ptr(), mode as libc::c_int) - }) -} - -pub fn rmdir(p: &CString) -> IoResult<()> { - let p = try!(to_utf16(p)); - super::mkerr_libc(unsafe { libc::wrmdir(p.as_ptr()) }) -} - -pub fn chown(_p: &CString, _uid: int, _gid: int) -> IoResult<()> { - // libuv has this as a no-op, so seems like this should as well? - Ok(()) -} - -pub fn readlink(p: &CString) -> IoResult { - // FIXME: I have a feeling that this reads intermediate symlinks as well. - use io::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)).to_c_str()) - } - Some(s) => Ok(Path::new(s).to_c_str()), - None => Err(super::last_error()), - }; - assert!(unsafe { libc::CloseHandle(handle) } != 0); - return ret; -} - -pub fn symlink(src: &CString, dst: &CString) -> IoResult<()> { - use io::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: &CString, dst: &CString) -> 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) -> rtio::FileStat { - rtio::FileStat { - size: stat.st_size as u64, - kind: stat.st_mode as u64, - perm: stat.st_mode as u64, - created: stat.st_ctime as u64, - modified: stat.st_mtime as u64, - accessed: stat.st_atime as u64, - 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: &CString) -> IoResult { - 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()), - } -} - -pub fn lstat(_p: &CString) -> IoResult { - // FIXME: implementation is missing - Err(super::unimpl()) -} - -pub fn utime(p: &CString, 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)); - super::mkerr_libc(unsafe { - libc::wutime(p.as_ptr(), &mut buf) - }) -} diff --git a/src/libnative/io/mod.rs b/src/libnative/io/mod.rs index a541712e17f..baf58b83dcd 100644 --- a/src/libnative/io/mod.rs +++ b/src/libnative/io/mod.rs @@ -30,7 +30,6 @@ use std::rt::rtio::{mod, IoResult, IoError}; use std::num; // Local re-exports -pub use self::file::FileDesc; pub use self::process::Process; mod helper_thread; @@ -41,13 +40,6 @@ pub mod net; pub mod process; mod util; -#[cfg(unix)] -#[path = "file_unix.rs"] -pub mod file; -#[cfg(windows)] -#[path = "file_windows.rs"] -pub mod file; - #[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd", @@ -92,25 +84,6 @@ fn last_error() -> IoError { } } -// unix has nonzero values as errors -fn mkerr_libc (ret: Int) -> IoResult<()> { - if !ret.is_zero() { - Err(last_error()) - } else { - Ok(()) - } -} - -// windows has zero values as errors -#[cfg(windows)] -fn mkerr_winbool(ret: libc::c_int) -> IoResult<()> { - if ret == 0 { - Err(last_error()) - } else { - Ok(()) - } -} - #[cfg(windows)] #[inline] fn retry (f: || -> I) -> I { f() } // PR rust-lang/rust/#17020 @@ -199,62 +172,6 @@ impl rtio::IoFactory for IoFactory { addrinfo::GetAddrInfoRequest::run(host, servname, hint) } - // filesystem operations - fn fs_from_raw_fd(&mut self, fd: c_int, close: rtio::CloseBehavior) - -> Box { - let close = match close { - rtio::CloseSynchronously | rtio::CloseAsynchronously => true, - rtio::DontClose => false - }; - box file::FileDesc::new(fd, close) as Box - } - fn fs_open(&mut self, path: &CString, fm: rtio::FileMode, - fa: rtio::FileAccess) - -> IoResult> - { - file::open(path, fm, fa).map(|fd| box fd as Box) - } - fn fs_unlink(&mut self, path: &CString) -> IoResult<()> { - file::unlink(path) - } - fn fs_stat(&mut self, path: &CString) -> IoResult { - file::stat(path) - } - fn fs_mkdir(&mut self, path: &CString, mode: uint) -> IoResult<()> { - file::mkdir(path, mode) - } - fn fs_chmod(&mut self, path: &CString, mode: uint) -> IoResult<()> { - file::chmod(path, mode) - } - fn fs_rmdir(&mut self, path: &CString) -> IoResult<()> { - file::rmdir(path) - } - fn fs_rename(&mut self, path: &CString, to: &CString) -> IoResult<()> { - file::rename(path, to) - } - fn fs_readdir(&mut self, path: &CString, _flags: c_int) -> IoResult> { - file::readdir(path) - } - fn fs_lstat(&mut self, path: &CString) -> IoResult { - file::lstat(path) - } - fn fs_chown(&mut self, path: &CString, uid: int, gid: int) -> IoResult<()> { - file::chown(path, uid, gid) - } - fn fs_readlink(&mut self, path: &CString) -> IoResult { - file::readlink(path) - } - fn fs_symlink(&mut self, src: &CString, dst: &CString) -> IoResult<()> { - file::symlink(src, dst) - } - fn fs_link(&mut self, src: &CString, dst: &CString) -> IoResult<()> { - file::link(src, dst) - } - fn fs_utime(&mut self, src: &CString, atime: u64, - mtime: u64) -> IoResult<()> { - file::utime(src, atime, mtime) - } - // misc fn timer_init(&mut self) -> IoResult> { timer::Timer::new().map(|t| box t as Box) diff --git a/src/libnative/io/timer_unix.rs b/src/libnative/io/timer_unix.rs index 38895f2a8f9..c26e2e76cee 100644 --- a/src/libnative/io/timer_unix.rs +++ b/src/libnative/io/timer_unix.rs @@ -56,7 +56,7 @@ use std::sync::atomic; use std::comm; use io::c; -use io::file::FileDesc; +use platform_imp::fs::FileDesc; use io::helper_thread::Helper; helper_init!(static HELPER: Helper) diff --git a/src/librustrt/rtio.rs b/src/librustrt/rtio.rs index e9bee19b498..1f3ef60e6fb 100644 --- a/src/librustrt/rtio.rs +++ b/src/librustrt/rtio.rs @@ -50,20 +50,6 @@ pub trait RemoteCallback { fn fire(&mut self); } -/// Description of what to do when a file handle is closed -pub enum CloseBehavior { - /// Do not close this handle when the object is destroyed - DontClose, - /// Synchronously close the handle, meaning that the task will block when - /// the handle is destroyed until it has been fully closed. - CloseSynchronously, - /// Asynchronously closes a handle, meaning that the task will *not* block - /// when the handle is destroyed, but the handle will still get deallocated - /// and cleaned up (but this will happen asynchronously on the local event - /// loop). - CloseAsynchronously, -} - /// Data needed to spawn a process. Serializes the `std::io::process::Command` /// builder. pub struct ProcessConfig<'a> { @@ -202,28 +188,6 @@ pub trait IoFactory { hint: Option) -> IoResult>; - // filesystem operations - fn fs_from_raw_fd(&mut self, fd: c_int, close: CloseBehavior) - -> Box; - fn fs_open(&mut self, path: &CString, fm: FileMode, fa: FileAccess) - -> IoResult>; - fn fs_unlink(&mut self, path: &CString) -> IoResult<()>; - fn fs_stat(&mut self, path: &CString) -> IoResult; - fn fs_mkdir(&mut self, path: &CString, mode: uint) -> IoResult<()>; - fn fs_chmod(&mut self, path: &CString, mode: uint) -> IoResult<()>; - fn fs_rmdir(&mut self, path: &CString) -> IoResult<()>; - fn fs_rename(&mut self, path: &CString, to: &CString) -> IoResult<()>; - fn fs_readdir(&mut self, path: &CString, flags: c_int) -> - IoResult>; - fn fs_lstat(&mut self, path: &CString) -> IoResult; - fn fs_chown(&mut self, path: &CString, uid: int, gid: int) -> - IoResult<()>; - fn fs_readlink(&mut self, path: &CString) -> IoResult; - fn fs_symlink(&mut self, src: &CString, dst: &CString) -> IoResult<()>; - fn fs_link(&mut self, src: &CString, dst: &CString) -> IoResult<()>; - fn fs_utime(&mut self, src: &CString, atime: u64, mtime: u64) -> - IoResult<()>; - // misc fn timer_init(&mut self) -> IoResult>; fn spawn(&mut self, cfg: ProcessConfig) @@ -296,19 +260,6 @@ pub trait RtioTimer { fn period(&mut self, msecs: u64, cb: Box); } -pub trait RtioFileStream { - fn read(&mut self, buf: &mut [u8]) -> IoResult; - fn write(&mut self, buf: &[u8]) -> IoResult<()>; - fn pread(&mut self, buf: &mut [u8], offset: u64) -> IoResult; - fn pwrite(&mut self, buf: &[u8], offset: u64) -> IoResult<()>; - fn seek(&mut self, pos: i64, whence: SeekStyle) -> IoResult; - fn tell(&self) -> IoResult; - fn fsync(&mut self) -> IoResult<()>; - fn datasync(&mut self) -> IoResult<()>; - fn truncate(&mut self, offset: i64) -> IoResult<()>; - fn fstat(&mut self) -> IoResult; -} - pub trait RtioProcess { fn id(&self) -> libc::pid_t; fn kill(&mut self, signal: int) -> IoResult<()>; @@ -399,43 +350,6 @@ pub enum ProcessExit { ExitSignal(int), } -pub enum FileMode { - Open, - Append, - Truncate, -} - -pub enum FileAccess { - Read, - Write, - ReadWrite, -} - -pub struct FileStat { - pub size: u64, - pub kind: u64, - pub perm: u64, - pub created: u64, - pub modified: u64, - pub accessed: u64, - pub device: u64, - pub inode: u64, - pub rdev: u64, - pub nlink: u64, - pub uid: u64, - pub gid: u64, - pub blksize: u64, - pub blocks: u64, - pub flags: u64, - pub gen: u64, -} - -pub enum SeekStyle { - SeekSet, - SeekEnd, - SeekCur, -} - pub struct AddrinfoHint { pub family: uint, pub socktype: uint, 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, + 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 { - 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::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 { - 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 { - 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 { /// /// See `stat` pub fn lstat(path: &Path) -> IoResult { - 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(result: IoResult, from: &Path, to: &Path) -> IoResult { - 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 { - 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 { /// 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> { - 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> { pub fn walk_dir(path: &Path) -> IoResult { 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 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 { - 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 78abbb9f80d..03c073c1477 100644 --- a/src/libstd/io/mod.rs +++ b/src/libstd/io/mod.rs @@ -316,6 +316,7 @@ impl IoError { 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. diff --git a/src/libstd/io/stdio.rs b/src/libstd/io/stdio.rs index 7bae67c0aa6..98644cfc7e9 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::FileDesc; 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; @@ -75,14 +75,14 @@ use uint; // case pipe also doesn't work, but magically file does! enum StdSource { TTY(Box), - File(Box), + File(FileDesc), } fn src(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))), + Err(_) => f(File(FileDesc::new(fd, false))), }) }).map_err(IoError::from_rtio_error).unwrap() } @@ -278,10 +278,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_err(IoError::from_rtio_error) }, 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 @@ -372,9 +372,9 @@ impl Writer for StdWriter { let max_size = if cfg!(windows) {8192} else {uint::MAX}; for chunk in buf.chunks(max_size) { try!(match self.inner { - TTY(ref mut tty) => tty.write(chunk), + TTY(ref mut tty) => tty.write(chunk).map_err(IoError::from_rtio_error), File(ref mut file) => file.write(chunk), - }.map_err(IoError::from_rtio_error)) + }) } Ok(()) } diff --git a/src/libstd/platform_imp/unix/fs.rs b/src/libstd/platform_imp/unix/fs.rs new file mode 100644 index 00000000000..3dcd99859e8 --- /dev/null +++ b/src/libstd/platform_imp/unix/fs.rs @@ -0,0 +1,411 @@ +// Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , 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 { + 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 { + 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 { + 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 { + let mut stat: libc::stat = unsafe { mem::zeroed() }; + match unsafe { libc::fstat(self.fd(), &mut stat) } { + 0 => Ok(mkstat(&stat)), + _ => Err(super::last_error()), + } + } + + /// Extract the actual filedescriptor without closing it. + pub fn unwrap(self) -> fd_t { + let fd = self.fd; + unsafe { mem::forget(self) }; + fd + } +} + +/* + +impl RtioTTY for FileDesc { + fn read(&mut self, buf: &mut [u8]) -> IoResult { + self.inner_read(buf) + } + fn write(&mut self, buf: &[u8]) -> IoResult<()> { + self.inner_write(buf) + } + fn set_raw(&mut self, _raw: bool) -> IoResult<()> { + Err(super::unimpl()) + } + fn get_winsize(&mut self) -> IoResult<(int, int)> { + Err(super::unimpl()) + } + fn isatty(&self) -> bool { false } +} +*/ + +impl Drop for FileDesc { + fn drop(&mut self) { + // closing stdio file handles makes no sense, so never do it. Also, note + // that errors are ignored when closing a file descriptor. The reason + // for this is that if an error occurs we don't actually know if the + // file descriptor was closed or not, and if we retried (for something + // like EINTR), we might close another valid file descriptor (opened + // after we closed ours. + if self.close_on_drop && self.fd > libc::STDERR_FILENO { + let n = unsafe { libc::close(self.fd) }; + if n != 0 { + println!("error {} when closing file descriptor {}", n, self.fd); + } + } + } +} + +pub fn open(path: &Path, fm: FileMode, fa: FileAccess) -> IoResult { + 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> { + use libc::{dirent_t}; + use libc::{opendir, readdir_r, closedir}; + + fn prune(root: &CString, dirs: Vec) -> Vec { + 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::::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 { + 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 = 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 { + 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 { + 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/platform_imp/windows/fs.rs b/src/libstd/platform_imp/windows/fs.rs new file mode 100644 index 00000000000..a07688b2fed --- /dev/null +++ b/src/libstd/platform_imp/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 or the MIT license +// , 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 { + 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 { + 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 { + self.seek_common(pos, style) + } + + pub fn tell(&self) -> IoResult { + 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 { + 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> { + sys::to_utf16(s.as_str()) +} + +pub fn open(path: &Path, fm: FileMode, fa: FileAccess) -> IoResult { + // 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> { + fn prune(root: &Path, dirs: Vec) -> Vec { + 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) -> 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 { + // 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 { + 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 { + // 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) + }) +} -- cgit 1.4.1-3-g733a5 From d34b1b0ca9bf5e0d7cd30952f5de0ab09ed57b41 Mon Sep 17 00:00:00 2001 From: Aaron Turon Date: Fri, 10 Oct 2014 10:11:49 -0700 Subject: Runtime removal: refactor pipes and networking This patch continues the runtime removal by moving pipe and networking-related code into `sys`. Because this eliminates APIs in `libnative` and `librustrt`, it is a: [breaking-change] This functionality is likely to be available publicly, in some form, from `std` in the future. --- src/libnative/io/mod.rs | 54 -- src/libnative/io/net.rs | 1103 -------------------------------------- src/libnative/io/pipe_unix.rs | 339 ------------ src/libnative/io/pipe_windows.rs | 769 -------------------------- src/librustrt/rtio.rs | 127 ----- src/libstd/io/net/addrinfo.rs | 32 +- src/libstd/io/net/mod.rs | 45 +- src/libstd/io/net/pipe.rs | 65 ++- src/libstd/io/net/tcp.rs | 105 ++-- src/libstd/io/net/udp.rs | 60 +-- src/libstd/io/pipe.rs | 58 +- src/libstd/os.rs | 35 +- src/libstd/sys/common/net.rs | 909 +++++++++++++++++++++++++++++++ src/libstd/sys/unix/mod.rs | 50 +- src/libstd/sys/unix/os.rs | 11 + src/libstd/sys/unix/pipe.rs | 321 +++++++++++ src/libstd/sys/unix/tcp.rs | 157 ++++++ src/libstd/sys/unix/udp.rs | 11 + src/libstd/sys/windows/mod.rs | 12 +- src/libstd/sys/windows/pipe.rs | 751 ++++++++++++++++++++++++++ src/libstd/sys/windows/tcp.rs | 219 ++++++++ src/libstd/sys/windows/udp.rs | 11 + 22 files changed, 2569 insertions(+), 2675 deletions(-) delete mode 100644 src/libnative/io/net.rs delete mode 100644 src/libnative/io/pipe_unix.rs delete mode 100644 src/libnative/io/pipe_windows.rs create mode 100644 src/libstd/sys/common/net.rs create mode 100644 src/libstd/sys/unix/pipe.rs create mode 100644 src/libstd/sys/unix/tcp.rs create mode 100644 src/libstd/sys/unix/udp.rs create mode 100644 src/libstd/sys/windows/pipe.rs create mode 100644 src/libstd/sys/windows/tcp.rs create mode 100644 src/libstd/sys/windows/udp.rs (limited to 'src/libstd') diff --git a/src/libnative/io/mod.rs b/src/libnative/io/mod.rs index baf58b83dcd..2a76bc29f7c 100644 --- a/src/libnative/io/mod.rs +++ b/src/libnative/io/mod.rs @@ -35,8 +35,6 @@ pub use self::process::Process; mod helper_thread; // Native I/O implementations -pub mod addrinfo; -pub mod net; pub mod process; mod util; @@ -53,14 +51,6 @@ pub mod timer; #[path = "timer_windows.rs"] pub mod timer; -#[cfg(unix)] -#[path = "pipe_unix.rs"] -pub mod pipe; - -#[cfg(windows)] -#[path = "pipe_windows.rs"] -pub mod pipe; - #[cfg(windows)] #[path = "tty_windows.rs"] mod tty; @@ -126,52 +116,11 @@ pub struct IoFactory { impl IoFactory { pub fn new() -> IoFactory { - net::init(); IoFactory { _cannot_construct_outside_of_this_module: () } } } impl rtio::IoFactory for IoFactory { - // networking - fn tcp_connect(&mut self, addr: rtio::SocketAddr, - timeout: Option) - -> IoResult> - { - net::TcpStream::connect(addr, timeout).map(|s| { - box s as Box - }) - } - fn tcp_bind(&mut self, addr: rtio::SocketAddr) - -> IoResult> { - net::TcpListener::bind(addr).map(|s| { - box s as Box - }) - } - fn udp_bind(&mut self, addr: rtio::SocketAddr) - -> IoResult> { - net::UdpSocket::bind(addr).map(|u| { - box u as Box - }) - } - fn unix_bind(&mut self, path: &CString) - -> IoResult> { - pipe::UnixListener::bind(path).map(|s| { - box s as Box - }) - } - fn unix_connect(&mut self, path: &CString, - timeout: Option) -> IoResult> { - pipe::UnixStream::connect(path, timeout).map(|s| { - box s as Box - }) - } - fn get_host_addresses(&mut self, host: Option<&str>, servname: Option<&str>, - hint: Option) - -> IoResult> - { - addrinfo::GetAddrInfoRequest::run(host, servname, hint) - } - // misc fn timer_init(&mut self) -> IoResult> { timer::Timer::new().map(|t| box t as Box) @@ -189,9 +138,6 @@ impl rtio::IoFactory for IoFactory { fn kill(&mut self, pid: libc::pid_t, signum: int) -> IoResult<()> { process::Process::kill(pid, signum) } - fn pipe_open(&mut self, fd: c_int) -> IoResult> { - Ok(box file::FileDesc::new(fd, true) as Box) - } #[cfg(unix)] fn tty_open(&mut self, fd: c_int, _readable: bool) -> IoResult> { diff --git a/src/libnative/io/net.rs b/src/libnative/io/net.rs deleted file mode 100644 index a4b97a3eb84..00000000000 --- a/src/libnative/io/net.rs +++ /dev/null @@ -1,1103 +0,0 @@ -// 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 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use alloc::arc::Arc; -use libc; -use std::mem; -use std::ptr; -use std::rt::mutex; -use std::rt::rtio::{mod, IoResult, IoError}; -use std::sync::atomic; - -use super::{retry, keep_going}; -use super::c; -use super::util; - -#[cfg(unix)] use super::process; -#[cfg(unix)] use super::file::FileDesc; - -pub use self::os::{init, sock_t, last_error}; - -//////////////////////////////////////////////////////////////////////////////// -// sockaddr and misc bindings -//////////////////////////////////////////////////////////////////////////////// - -pub fn htons(u: u16) -> u16 { - u.to_be() -} -pub fn ntohs(u: u16) -> u16 { - Int::from_be(u) -} - -enum InAddr { - In4Addr(libc::in_addr), - In6Addr(libc::in6_addr), -} - -fn ip_to_inaddr(ip: rtio::IpAddr) -> InAddr { - match ip { - rtio::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) - }) - } - rtio::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), - ] - }) - } - } -} - -fn addr_to_sockaddr(addr: rtio::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::() - } - 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::() - } - }; - return len as libc::socklen_t; - } -} - -fn socket(addr: rtio::SocketAddr, ty: libc::c_int) -> IoResult { - unsafe { - let fam = match addr.ip { - rtio::Ipv4Addr(..) => libc::AF_INET, - rtio::Ipv6Addr(..) => libc::AF_INET6, - }; - match libc::socket(fam, ty, 0) { - -1 => Err(os::last_error()), - fd => Ok(fd), - } - } -} - -fn setsockopt(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::() as libc::socklen_t); - if ret != 0 { - Err(os::last_error()) - } else { - Ok(()) - } - } -} - -pub fn getsockopt(fd: sock_t, opt: libc::c_int, - val: libc::c_int) -> IoResult { - unsafe { - let mut slot: T = mem::zeroed(); - let mut len = mem::size_of::() as libc::socklen_t; - let ret = c::getsockopt(fd, opt, val, - &mut slot as *mut _ as *mut _, - &mut len); - if ret != 0 { - Err(os::last_error()) - } else { - assert!(len as uint == mem::size_of::()); - Ok(slot) - } - } -} - -fn sockname(fd: sock_t, - f: unsafe extern "system" fn(sock_t, *mut libc::sockaddr, - *mut libc::socklen_t) -> libc::c_int) - -> IoResult -{ - let mut storage: libc::sockaddr_storage = unsafe { mem::zeroed() }; - let mut len = mem::size_of::() 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(os::last_error()) - } - } - return sockaddr_to_addr(&storage, len as uint); -} - -pub fn sockaddr_to_addr(storage: &libc::sockaddr_storage, - len: uint) -> IoResult { - match storage.ss_family as libc::c_int { - libc::AF_INET => { - assert!(len as uint >= mem::size_of::()); - 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(rtio::SocketAddr { - ip: rtio::Ipv4Addr(a, b, c, d), - port: ntohs(storage.sin_port), - }) - } - libc::AF_INET6 => { - assert!(len as uint >= mem::size_of::()); - 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(rtio::SocketAddr { - ip: rtio::Ipv6Addr(a, b, c, d, e, f, g, h), - port: ntohs(storage.sin6_port), - }) - } - _ => { - #[cfg(unix)] use libc::EINVAL as ERROR; - #[cfg(windows)] use libc::WSAEINVAL as ERROR; - Err(IoError { - code: ERROR as uint, - extra: 0, - detail: None, - }) - } - } -} - -//////////////////////////////////////////////////////////////////////////////// -// TCP streams -//////////////////////////////////////////////////////////////////////////////// - -pub struct TcpStream { - inner: Arc, - read_deadline: u64, - write_deadline: u64, -} - -struct Inner { - fd: sock_t, - - // Unused on Linux, where this lock is not necessary. - #[allow(dead_code)] - lock: mutex::NativeMutex -} - -pub struct Guard<'a> { - pub fd: sock_t, - pub guard: mutex::LockGuard<'a>, -} - -impl Inner { - fn new(fd: sock_t) -> Inner { - Inner { fd: fd, lock: unsafe { mutex::NativeMutex::new() } } - } -} - -impl TcpStream { - pub fn connect(addr: rtio::SocketAddr, - timeout: Option) -> IoResult { - let fd = try!(socket(addr, libc::SOCK_STREAM)); - let ret = TcpStream::new(Inner::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!(util::connect_timeout(fd, addrp, len, timeout)); - Ok(ret) - }, - None => { - match retry(|| unsafe { libc::connect(fd, addrp, len) }) { - -1 => Err(os::last_error()), - _ => Ok(ret), - } - } - } - } - - fn new(inner: Inner) -> TcpStream { - TcpStream { - inner: Arc::new(inner), - read_deadline: 0, - write_deadline: 0, - } - } - - pub fn fd(&self) -> sock_t { self.inner.fd } - - fn set_nodelay(&mut self, nodelay: bool) -> IoResult<()> { - setsockopt(self.fd(), libc::IPPROTO_TCP, libc::TCP_NODELAY, - nodelay as libc::c_int) - } - - fn set_keepalive(&mut self, seconds: Option) -> 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!(util::set_nonblocking(self.fd(), true).is_ok()); - ret - } -} - -#[cfg(windows)] type wrlen = libc::c_int; -#[cfg(not(windows))] type wrlen = libc::size_t; - -impl rtio::RtioTcpStream for TcpStream { - fn read(&mut self, buf: &mut [u8]) -> IoResult { - 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) - } - - 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 - }; - match write(fd, self.write_deadline, buf, true, dolock, dowrite) { - Ok(_) => Ok(()), - Err(e) => Err(e) - } - } - fn peer_name(&mut self) -> IoResult { - sockname(self.fd(), libc::getpeername) - } - fn control_congestion(&mut self) -> IoResult<()> { - self.set_nodelay(false) - } - fn nodelay(&mut self) -> IoResult<()> { - self.set_nodelay(true) - } - fn keepalive(&mut self, delay_in_seconds: uint) -> IoResult<()> { - self.set_keepalive(Some(delay_in_seconds)) - } - fn letdie(&mut self) -> IoResult<()> { - self.set_keepalive(None) - } - - fn clone(&self) -> Box { - box TcpStream { - inner: self.inner.clone(), - read_deadline: 0, - write_deadline: 0, - } as Box - } - - fn close_write(&mut self) -> IoResult<()> { - super::mkerr_libc(unsafe { libc::shutdown(self.fd(), libc::SHUT_WR) }) - } - fn close_read(&mut self) -> IoResult<()> { - super::mkerr_libc(unsafe { libc::shutdown(self.fd(), libc::SHUT_RD) }) - } - - fn set_timeout(&mut self, timeout: Option) { - let deadline = timeout.map(|a| ::io::timer::now() + a).unwrap_or(0); - self.read_deadline = deadline; - self.write_deadline = deadline; - } - fn set_read_timeout(&mut self, timeout: Option) { - self.read_deadline = timeout.map(|a| ::io::timer::now() + a).unwrap_or(0); - } - fn set_write_timeout(&mut self, timeout: Option) { - self.write_deadline = timeout.map(|a| ::io::timer::now() + a).unwrap_or(0); - } -} - -impl rtio::RtioSocket for TcpStream { - fn socket_name(&mut self) -> IoResult { - sockname(self.fd(), libc::getsockname) - } -} - -impl Drop for Inner { - fn drop(&mut self) { unsafe { os::close(self.fd); } } -} - -#[unsafe_destructor] -impl<'a> Drop for Guard<'a> { - fn drop(&mut self) { - assert!(util::set_nonblocking(self.fd, false).is_ok()); - } -} - -//////////////////////////////////////////////////////////////////////////////// -// TCP listeners -//////////////////////////////////////////////////////////////////////////////// - -pub struct TcpListener { - inner: Inner, -} - -impl TcpListener { - pub fn bind(addr: rtio::SocketAddr) -> IoResult { - let fd = try!(socket(addr, libc::SOCK_STREAM)); - let ret = TcpListener { inner: Inner::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; - - // 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. - if cfg!(unix) { - try!(setsockopt(fd, libc::SOL_SOCKET, libc::SO_REUSEADDR, - 1 as libc::c_int)); - } - - match unsafe { libc::bind(fd, addrp, len) } { - -1 => Err(os::last_error()), - _ => Ok(ret), - } - } - - pub fn fd(&self) -> sock_t { self.inner.fd } - - pub fn native_listen(self, backlog: int) -> IoResult { - match unsafe { libc::listen(self.fd(), backlog as libc::c_int) } { - -1 => Err(os::last_error()), - - #[cfg(unix)] - _ => { - let (reader, writer) = try!(process::pipe()); - try!(util::set_nonblocking(reader.fd(), true)); - try!(util::set_nonblocking(writer.fd(), true)); - try!(util::set_nonblocking(self.fd(), true)); - Ok(TcpAcceptor { - inner: Arc::new(AcceptorInner { - listener: self, - reader: reader, - writer: writer, - closed: atomic::AtomicBool::new(false), - }), - deadline: 0, - }) - } - - #[cfg(windows)] - _ => { - let accept = try!(os::Event::new()); - let ret = unsafe { - c::WSAEventSelect(self.fd(), accept.handle(), c::FD_ACCEPT) - }; - if ret != 0 { - return Err(os::last_error()) - } - Ok(TcpAcceptor { - inner: Arc::new(AcceptorInner { - listener: self, - abort: try!(os::Event::new()), - accept: accept, - closed: atomic::AtomicBool::new(false), - }), - deadline: 0, - }) - } - } - } -} - -impl rtio::RtioTcpListener for TcpListener { - fn listen(self: Box) - -> IoResult> { - self.native_listen(128).map(|a| { - box a as Box - }) - } -} - -impl rtio::RtioSocket for TcpListener { - fn socket_name(&mut self) -> IoResult { - sockname(self.fd(), libc::getsockname) - } -} - -pub struct TcpAcceptor { - inner: Arc, - deadline: u64, -} - -#[cfg(unix)] -struct AcceptorInner { - listener: TcpListener, - reader: FileDesc, - writer: FileDesc, - closed: atomic::AtomicBool, -} - -#[cfg(windows)] -struct AcceptorInner { - listener: TcpListener, - abort: os::Event, - accept: os::Event, - closed: atomic::AtomicBool, -} - -impl TcpAcceptor { - pub fn fd(&self) -> sock_t { self.inner.listener.fd() } - - #[cfg(unix)] - pub fn native_accept(&mut self) -> IoResult { - // 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 util::wouldblock() => {} - -1 => return Err(os::last_error()), - fd => return Ok(TcpStream::new(Inner::new(fd as sock_t))), - } - try!(util::await([self.fd(), self.inner.reader.fd()], - deadline, util::Readable)); - } - - Err(util::eof()) - } - - #[cfg(windows)] - pub fn native_accept(&mut self) -> IoResult { - // 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 = ::io::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(util::timeout("accept timed out")) - } - c::WSA_WAIT_FAILED => return Err(os::last_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(os::last_error()) } - - if wsaevents.lNetworkEvents & c::FD_ACCEPT == 0 { continue } - match unsafe { - libc::accept(self.fd(), ptr::null_mut(), ptr::null_mut()) - } { - -1 if util::wouldblock() => {} - -1 => return Err(os::last_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(Inner::new(fd)); - let ret = unsafe { - c::WSAEventSelect(fd, events[1], 0) - }; - if ret != 0 { return Err(os::last_error()) } - try!(util::set_nonblocking(fd, false)); - return Ok(stream) - } - } - } - - Err(util::eof()) - } -} - -impl rtio::RtioSocket for TcpAcceptor { - fn socket_name(&mut self) -> IoResult { - sockname(self.fd(), libc::getsockname) - } -} - -impl rtio::RtioTcpAcceptor for TcpAcceptor { - fn accept(&mut self) -> IoResult> { - self.native_accept().map(|s| box s as Box) - } - - fn accept_simultaneously(&mut self) -> IoResult<()> { Ok(()) } - fn dont_accept_simultaneously(&mut self) -> IoResult<()> { Ok(()) } - fn set_timeout(&mut self, timeout: Option) { - self.deadline = timeout.map(|a| ::io::timer::now() + a).unwrap_or(0); - } - - fn clone(&self) -> Box { - box TcpAcceptor { - inner: self.inner.clone(), - deadline: 0, - } as Box - } - - #[cfg(unix)] - fn close_accept(&mut self) -> IoResult<()> { - self.inner.closed.store(true, atomic::SeqCst); - let mut fd = FileDesc::new(self.inner.writer.fd(), false); - match fd.inner_write([0]) { - Ok(..) => Ok(()), - Err(..) if util::wouldblock() => Ok(()), - Err(e) => Err(e), - } - } - - #[cfg(windows)] - 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(os::last_error()) - } - } -} - -//////////////////////////////////////////////////////////////////////////////// -// UDP -//////////////////////////////////////////////////////////////////////////////// - -pub struct UdpSocket { - inner: Arc, - read_deadline: u64, - write_deadline: u64, -} - -impl UdpSocket { - pub fn bind(addr: rtio::SocketAddr) -> IoResult { - 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(os::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: rtio::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!(util::set_nonblocking(self.fd(), true).is_ok()); - ret - } -} - -impl rtio::RtioSocket for UdpSocket { - fn socket_name(&mut self) -> IoResult { - sockname(self.fd(), libc::getsockname) - } -} - -#[cfg(windows)] type msglen_t = libc::c_int; -#[cfg(unix)] type msglen_t = libc::size_t; - -impl rtio::RtioUdpSocket for UdpSocket { - fn recv_from(&mut self, buf: &mut [u8]) -> IoResult<(uint, rtio::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::() 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)) - }) - } - - fn send_to(&mut self, buf: &[u8], dst: rtio::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(util::short_write(n, "couldn't send entire packet at once")) - } else { - Ok(()) - } - } - - fn join_multicast(&mut self, multi: rtio::IpAddr) -> IoResult<()> { - match multi { - rtio::Ipv4Addr(..) => { - self.set_membership(multi, libc::IP_ADD_MEMBERSHIP) - } - rtio::Ipv6Addr(..) => { - self.set_membership(multi, libc::IPV6_ADD_MEMBERSHIP) - } - } - } - fn leave_multicast(&mut self, multi: rtio::IpAddr) -> IoResult<()> { - match multi { - rtio::Ipv4Addr(..) => { - self.set_membership(multi, libc::IP_DROP_MEMBERSHIP) - } - rtio::Ipv6Addr(..) => { - self.set_membership(multi, libc::IPV6_DROP_MEMBERSHIP) - } - } - } - - fn loop_multicast_locally(&mut self) -> IoResult<()> { - self.set_multicast_loop(true) - } - fn dont_loop_multicast_locally(&mut self) -> IoResult<()> { - self.set_multicast_loop(false) - } - - 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) - } - fn time_to_live(&mut self, ttl: int) -> IoResult<()> { - setsockopt(self.fd(), libc::IPPROTO_IP, libc::IP_TTL, ttl as libc::c_int) - } - - fn hear_broadcasts(&mut self) -> IoResult<()> { - self.set_broadcast(true) - } - fn ignore_broadcasts(&mut self) -> IoResult<()> { - self.set_broadcast(false) - } - - fn clone(&self) -> Box { - box UdpSocket { - inner: self.inner.clone(), - read_deadline: 0, - write_deadline: 0, - } as Box - } - - fn set_timeout(&mut self, timeout: Option) { - let deadline = timeout.map(|a| ::io::timer::now() + a).unwrap_or(0); - self.read_deadline = deadline; - self.write_deadline = deadline; - } - fn set_read_timeout(&mut self, timeout: Option) { - self.read_deadline = timeout.map(|a| ::io::timer::now() + a).unwrap_or(0); - } - fn set_write_timeout(&mut self, timeout: Option) { - self.write_deadline = timeout.map(|a| ::io::timer::now() + a).unwrap_or(0); - } -} - -//////////////////////////////////////////////////////////////////////////////// -// 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(fd: sock_t, - deadline: u64, - lock: || -> T, - read: |bool| -> libc::c_int) -> IoResult { - let mut ret = -1; - if deadline == 0 { - ret = retry(|| read(false)); - } - - if deadline != 0 || (ret == -1 && util::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!(util::await([fd], deadline, util::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 util::wouldblock() => {} - -1 => return Err(os::last_error()), - n => { ret = n; break } - } - } - } - - match ret { - 0 => Err(util::eof()), - n if n < 0 => Err(os::last_error()), - n => Ok(n as uint) - } -} - -pub fn write(fd: sock_t, - deadline: u64, - buf: &[u8], - write_everything: bool, - lock: || -> T, - write: |bool, *const u8, uint| -> i64) -> IoResult { - 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 && util::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 util::await([fd], deadline, util::Writable) { - Err(ref e) if e.code == libc::EOF as uint && written > 0 => { - assert!(deadline.is_some()); - return Err(util::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 util::wouldblock() => {} - -1 => return Err(os::last_error()), - n => { written += n as uint; } - } - } - ret = 0; - } - if ret < 0 { - Err(os::last_error()) - } else { - Ok(written) - } -} - -#[cfg(windows)] -mod os { - use libc; - use std::mem; - use std::rt::rtio::{IoError, IoResult}; - - use io::c; - - pub type sock_t = libc::SOCKET; - pub struct Event(c::WSAEVENT); - - impl Event { - pub fn new() -> IoResult { - let event = unsafe { c::WSACreateEvent() }; - if event == c::WSA_INVALID_EVENT { - Err(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()); } - } - } - - pub fn init() { - unsafe { - use std::rt::mutex::{StaticNativeMutex, NATIVE_MUTEX_INIT}; - static mut INITIALIZED: bool = false; - static LOCK: StaticNativeMutex = NATIVE_MUTEX_INIT; - - let _guard = LOCK.lock(); - if !INITIALIZED { - let mut data: c::WSADATA = mem::zeroed(); - let ret = c::WSAStartup(0x202, // version 2.2 - &mut data); - assert_eq!(ret, 0); - INITIALIZED = true; - } - } - } - - pub fn last_error() -> IoError { - use std::os; - let code = unsafe { c::WSAGetLastError() as uint }; - IoError { - code: code, - extra: 0, - detail: Some(os::error_string(code)), - } - } - - pub unsafe fn close(sock: sock_t) { let _ = libc::closesocket(sock); } -} - -#[cfg(unix)] -mod os { - use libc; - use std::rt::rtio::IoError; - use io; - - pub type sock_t = io::file::fd_t; - - pub fn init() {} - pub fn last_error() -> IoError { io::last_error() } - pub unsafe fn close(sock: sock_t) { let _ = libc::close(sock); } -} diff --git a/src/libnative/io/pipe_unix.rs b/src/libnative/io/pipe_unix.rs deleted file mode 100644 index 48f31615339..00000000000 --- a/src/libnative/io/pipe_unix.rs +++ /dev/null @@ -1,339 +0,0 @@ -// 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 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use alloc::arc::Arc; -use libc; -use std::c_str::CString; -use std::mem; -use std::rt::mutex; -use std::rt::rtio; -use std::rt::rtio::{IoResult, IoError}; -use std::sync::atomic; - -use super::retry; -use super::net; -use super::util; -use super::c; -use super::process; -use super::file::{fd_t, FileDesc}; - -fn unix_socket(ty: libc::c_int) -> IoResult { - 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 { - // the sun_path length is limited to SUN_LEN (with null) - assert!(mem::size_of::() >= - mem::size_of::()); - let s = unsafe { &mut *(storage as *mut _ as *mut libc::sockaddr_un) }; - - let len = addr.len(); - if len > s.sun_path.len() - 1 { - #[cfg(unix)] use libc::EINVAL as ERROR; - #[cfg(windows)] use libc::WSAEINVAL as ERROR; - return Err(IoError { - code: ERROR as uint, - extra: 0, - detail: Some("path must be smaller than SUN_LEN".to_string()), - }) - } - 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::() + 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) -> IoResult { - 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!(util::connect_timeout(inner.fd, addrp, len, timeout_ms)); - Ok(inner) - } - } -} - -fn bind(addr: &CString, ty: libc::c_int) -> IoResult { - 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, - read_deadline: u64, - write_deadline: u64, -} - -impl UnixStream { - pub fn connect(addr: &CString, - timeout: Option) -> IoResult { - connect(addr, libc::SOCK_STREAM, timeout).map(|inner| { - UnixStream::new(Arc::new(inner)) - }) - } - - fn new(inner: Arc) -> 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) -> net::Guard<'a> { - let ret = net::Guard { - fd: self.fd(), - guard: unsafe { self.inner.lock.lock() }, - }; - assert!(util::set_nonblocking(self.fd(), true).is_ok()); - ret - } -} - -impl rtio::RtioPipe for UnixStream { - fn read(&mut self, buf: &mut [u8]) -> IoResult { - 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 - }; - net::read(fd, self.read_deadline, dolock, doread) - } - - 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 net::write(fd, self.write_deadline, buf, true, dolock, dowrite) { - Ok(_) => Ok(()), - Err(e) => Err(e) - } - } - - fn clone(&self) -> Box { - box UnixStream::new(self.inner.clone()) as Box - } - - fn close_write(&mut self) -> IoResult<()> { - super::mkerr_libc(unsafe { libc::shutdown(self.fd(), libc::SHUT_WR) }) - } - fn close_read(&mut self) -> IoResult<()> { - super::mkerr_libc(unsafe { libc::shutdown(self.fd(), libc::SHUT_RD) }) - } - fn set_timeout(&mut self, timeout: Option) { - let deadline = timeout.map(|a| ::io::timer::now() + a).unwrap_or(0); - self.read_deadline = deadline; - self.write_deadline = deadline; - } - fn set_read_timeout(&mut self, timeout: Option) { - self.read_deadline = timeout.map(|a| ::io::timer::now() + a).unwrap_or(0); - } - fn set_write_timeout(&mut self, timeout: Option) { - self.write_deadline = timeout.map(|a| ::io::timer::now() + a).unwrap_or(0); - } -} - -//////////////////////////////////////////////////////////////////////////////// -// Unix Listener -//////////////////////////////////////////////////////////////////////////////// - -pub struct UnixListener { - inner: Inner, - path: CString, -} - -impl UnixListener { - pub fn bind(addr: &CString) -> IoResult { - bind(addr, libc::SOCK_STREAM).map(|fd| { - UnixListener { inner: fd, path: addr.clone() } - }) - } - - fn fd(&self) -> fd_t { self.inner.fd } - - pub fn native_listen(self, backlog: int) -> IoResult { - match unsafe { libc::listen(self.fd(), backlog as libc::c_int) } { - -1 => Err(super::last_error()), - - #[cfg(unix)] - _ => { - let (reader, writer) = try!(process::pipe()); - try!(util::set_nonblocking(reader.fd(), true)); - try!(util::set_nonblocking(writer.fd(), true)); - try!(util::set_nonblocking(self.fd(), true)); - Ok(UnixAcceptor { - inner: Arc::new(AcceptorInner { - listener: self, - reader: reader, - writer: writer, - closed: atomic::AtomicBool::new(false), - }), - deadline: 0, - }) - } - } - } -} - -impl rtio::RtioUnixListener for UnixListener { - fn listen(self: Box) - -> IoResult> { - self.native_listen(128).map(|a| { - box a as Box - }) - } -} - -pub struct UnixAcceptor { - inner: Arc, - deadline: u64, -} - -#[cfg(unix)] -struct AcceptorInner { - listener: UnixListener, - reader: FileDesc, - writer: FileDesc, - closed: atomic::AtomicBool, -} - -impl UnixAcceptor { - fn fd(&self) -> fd_t { self.inner.listener.fd() } - - pub fn native_accept(&mut self) -> IoResult { - 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::(); - 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 util::wouldblock() => {} - -1 => return Err(super::last_error()), - fd => return Ok(UnixStream::new(Arc::new(Inner::new(fd)))), - } - } - try!(util::await([self.fd(), self.inner.reader.fd()], - deadline, util::Readable)); - } - - Err(util::eof()) - } -} - -impl rtio::RtioUnixAcceptor for UnixAcceptor { - fn accept(&mut self) -> IoResult> { - self.native_accept().map(|s| box s as Box) - } - fn set_timeout(&mut self, timeout: Option) { - self.deadline = timeout.map(|a| ::io::timer::now() + a).unwrap_or(0); - } - - fn clone(&self) -> Box { - box UnixAcceptor { - inner: self.inner.clone(), - deadline: 0, - } as Box - } - - #[cfg(unix)] - fn close_accept(&mut self) -> IoResult<()> { - self.inner.closed.store(true, atomic::SeqCst); - let mut fd = FileDesc::new(self.inner.writer.fd(), false); - match fd.inner_write([0]) { - Ok(..) => Ok(()), - Err(..) if util::wouldblock() => Ok(()), - Err(e) => Err(e), - } - } -} - -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/libnative/io/pipe_windows.rs b/src/libnative/io/pipe_windows.rs deleted file mode 100644 index f764470f37d..00000000000 --- a/src/libnative/io/pipe_windows.rs +++ /dev/null @@ -1,769 +0,0 @@ -// 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 or the MIT license -// , 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 std::c_str::CString; -use std::mem; -use std::os; -use std::ptr; -use std::rt::rtio; -use std::rt::rtio::{IoResult, IoError}; -use std::sync::atomic; -use std::rt::mutex; - -use super::c; -use super::util; -use super::file::to_utf16; - -struct Event(libc::HANDLE); - -impl Event { - fn new(manual_reset: bool, initial_state: bool) -> IoResult { - 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 { - 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 = ::io::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(util::timeout("operation timed out")) - }, - n => Ok((n - WAIT_OBJECT_0) as uint) - } -} - -fn epipe() -> IoError { - IoError { - code: libc::ERROR_BROKEN_PIPE as uint, - extra: 0, - detail: None, - } -} - -//////////////////////////////////////////////////////////////////////////////// -// Unix Streams -//////////////////////////////////////////////////////////////////////////////// - -pub struct UnixStream { - inner: Arc, - write: Option, - read: Option, - read_deadline: u64, - write_deadline: u64, -} - -impl UnixStream { - fn try_connect(p: *const u16) -> Option { - // 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) -> IoResult { - let addr = try!(to_utf16(addr)); - let start = ::io::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 = ::io::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(util::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(()) - } - } -} - -impl rtio::RtioPipe for UnixStream { - fn read(&mut self, buf: &mut [u8]) -> IoResult { - 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(util::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(util::timeout("read timed out")) - } - if self.read_closed() { - return Err(util::eof()) - } - } - } - - 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(IoError { - code: err as uint, - extra: 0, - detail: Some(os::error_string(err as uint)), - }) - } - // 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 { - code: libc::ERROR_OPERATION_ABORTED as uint, - extra: amt, - detail: Some("short write during write".to_string()), - }) - } else { - Err(util::timeout("write timed out")) - } - } - if self.write_closed() { - return Err(epipe()) - } - continue // retry - } - } - offset += bytes_written as uint; - } - Ok(()) - } - - fn clone(&self) -> Box { - box UnixStream { - inner: self.inner.clone(), - read: None, - write: None, - read_deadline: 0, - write_deadline: 0, - } as Box - } - - 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() - } - - 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() - } - - fn set_timeout(&mut self, timeout: Option) { - let deadline = timeout.map(|a| ::io::timer::now() + a).unwrap_or(0); - self.read_deadline = deadline; - self.write_deadline = deadline; - } - fn set_read_timeout(&mut self, timeout: Option) { - self.read_deadline = timeout.map(|a| ::io::timer::now() + a).unwrap_or(0); - } - fn set_write_timeout(&mut self, timeout: Option) { - self.write_deadline = timeout.map(|a| ::io::timer::now() + a).unwrap_or(0); - } -} - -//////////////////////////////////////////////////////////////////////////////// -// Unix Listener -//////////////////////////////////////////////////////////////////////////////// - -pub struct UnixListener { - handle: libc::HANDLE, - name: CString, -} - -impl UnixListener { - pub fn bind(addr: &CString) -> IoResult { - // 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)); - 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 native_listen(self) -> IoResult { - 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); } - } -} - -impl rtio::RtioUnixListener for UnixListener { - fn listen(self: Box) - -> IoResult> { - self.native_listen().map(|a| { - box a as Box - }) - } -} - -pub struct UnixAcceptor { - inner: Arc, - listener: UnixListener, - event: Event, - deadline: u64, -} - -struct AcceptorState { - abort: Event, - closed: atomic::AtomicBool, -} - -impl UnixAcceptor { - pub fn native_accept(&mut self) -> IoResult { - // 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(util::eof()) } - - let name = try!(to_utf16(&self.listener.name)); - - // 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(util::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, - }) - } -} - -impl rtio::RtioUnixAcceptor for UnixAcceptor { - fn accept(&mut self) -> IoResult> { - self.native_accept().map(|s| box s as Box) - } - fn set_timeout(&mut self, timeout: Option) { - self.deadline = timeout.map(|i| i + ::io::timer::now()).unwrap_or(0); - } - - fn clone(&self) -> Box { - let name = to_utf16(&self.listener.name).ok().unwrap(); - box 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 - }, - }, - } as Box - } - - 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(()) - } - } -} - diff --git a/src/librustrt/rtio.rs b/src/librustrt/rtio.rs index 1f3ef60e6fb..3ebfcaea687 100644 --- a/src/librustrt/rtio.rs +++ b/src/librustrt/rtio.rs @@ -13,13 +13,9 @@ use core::prelude::*; use alloc::boxed::Box; use collections::string::String; -use collections::vec::Vec; -use core::fmt; use core::mem; use libc::c_int; -use libc; -use c_str::CString; use local::Local; use task::Task; @@ -173,87 +169,15 @@ impl<'a> LocalIo<'a> { } pub trait IoFactory { - // networking - fn tcp_connect(&mut self, addr: SocketAddr, - timeout: Option) -> IoResult>; - fn tcp_bind(&mut self, addr: SocketAddr) - -> IoResult>; - fn udp_bind(&mut self, addr: SocketAddr) - -> IoResult>; - fn unix_bind(&mut self, path: &CString) - -> IoResult>; - fn unix_connect(&mut self, path: &CString, - timeout: Option) -> IoResult>; - fn get_host_addresses(&mut self, host: Option<&str>, servname: Option<&str>, - hint: Option) - -> IoResult>; - - // misc fn timer_init(&mut self) -> IoResult>; fn spawn(&mut self, cfg: ProcessConfig) -> IoResult<(Box, Vec>>)>; fn kill(&mut self, pid: libc::pid_t, signal: int) -> IoResult<()>; - fn pipe_open(&mut self, fd: c_int) -> IoResult>; fn tty_open(&mut self, fd: c_int, readable: bool) -> IoResult>; } -pub trait RtioTcpListener : RtioSocket { - fn listen(self: Box) -> IoResult>; -} - -pub trait RtioTcpAcceptor : RtioSocket { - fn accept(&mut self) -> IoResult>; - fn accept_simultaneously(&mut self) -> IoResult<()>; - fn dont_accept_simultaneously(&mut self) -> IoResult<()>; - fn set_timeout(&mut self, timeout: Option); - fn clone(&self) -> Box; - fn close_accept(&mut self) -> IoResult<()>; -} - -pub trait RtioTcpStream : RtioSocket { - fn read(&mut self, buf: &mut [u8]) -> IoResult; - fn write(&mut self, buf: &[u8]) -> IoResult<()>; - fn peer_name(&mut self) -> IoResult; - fn control_congestion(&mut self) -> IoResult<()>; - fn nodelay(&mut self) -> IoResult<()>; - fn keepalive(&mut self, delay_in_seconds: uint) -> IoResult<()>; - fn letdie(&mut self) -> IoResult<()>; - fn clone(&self) -> Box; - fn close_write(&mut self) -> IoResult<()>; - fn close_read(&mut self) -> IoResult<()>; - fn set_timeout(&mut self, timeout_ms: Option); - fn set_read_timeout(&mut self, timeout_ms: Option); - fn set_write_timeout(&mut self, timeout_ms: Option); -} - -pub trait RtioSocket { - fn socket_name(&mut self) -> IoResult; -} - -pub trait RtioUdpSocket : RtioSocket { - fn recv_from(&mut self, buf: &mut [u8]) -> IoResult<(uint, SocketAddr)>; - fn send_to(&mut self, buf: &[u8], dst: SocketAddr) -> IoResult<()>; - - fn join_multicast(&mut self, multi: IpAddr) -> IoResult<()>; - fn leave_multicast(&mut self, multi: IpAddr) -> IoResult<()>; - - fn loop_multicast_locally(&mut self) -> IoResult<()>; - fn dont_loop_multicast_locally(&mut self) -> IoResult<()>; - - fn multicast_time_to_live(&mut self, ttl: int) -> IoResult<()>; - fn time_to_live(&mut self, ttl: int) -> IoResult<()>; - - fn hear_broadcasts(&mut self) -> IoResult<()>; - fn ignore_broadcasts(&mut self) -> IoResult<()>; - - fn clone(&self) -> Box; - fn set_timeout(&mut self, timeout_ms: Option); - fn set_read_timeout(&mut self, timeout_ms: Option); - fn set_write_timeout(&mut self, timeout_ms: Option); -} - pub trait RtioTimer { fn sleep(&mut self, msecs: u64); fn oneshot(&mut self, msecs: u64, cb: Box); @@ -313,54 +237,3 @@ pub struct IoError { } pub type IoResult = Result; - -#[deriving(PartialEq, Eq)] -pub enum IpAddr { - Ipv4Addr(u8, u8, u8, u8), - Ipv6Addr(u16, u16, u16, u16, u16, u16, u16, u16), -} - -impl fmt::Show for IpAddr { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - match *self { - Ipv4Addr(a, b, c, d) => write!(fmt, "{}.{}.{}.{}", a, b, c, d), - Ipv6Addr(a, b, c, d, e, f, g, h) => { - write!(fmt, - "{:04x}:{:04x}:{:04x}:{:04x}:{:04x}:{:04x}:{:04x}:{:04x}", - a, b, c, d, e, f, g, h) - } - } - } -} - -#[deriving(PartialEq, Eq)] -pub struct SocketAddr { - pub ip: IpAddr, - pub port: u16, -} - -pub enum StdioContainer { - Ignored, - InheritFd(i32), - CreatePipe(bool, bool), -} - -pub enum ProcessExit { - ExitStatus(int), - ExitSignal(int), -} - -pub struct AddrinfoHint { - pub family: uint, - pub socktype: uint, - pub protocol: uint, - pub flags: uint, -} - -pub struct AddrinfoInfo { - pub address: SocketAddr, - pub family: uint, - pub socktype: uint, - pub protocol: uint, - pub flags: uint, -} 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> { #[allow(unused_variables)] fn lookup(hostname: Option<&str>, servname: Option<&str>, hint: Option) -> IoResult> { - 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( - addr: A, - action: |&mut rtio::IoFactory, rtio::SocketAddr| -> Result -) -> Result { - 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(addr: A, action: |SocketAddr| -> IoResult) -> IoResult { 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, + 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(path: &P) -> IoResult { - 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) { - 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) { - 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) { - 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 { - 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, + 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(path: &P) -> IoResult { - 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 for UnixListener { fn listen(self) -> IoResult { - 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, + 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) { - 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 for UnixAcceptor { fn accept(&mut self) -> IoResult { - 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, + inner: TcpStreamImp, } impl TcpStream { - fn new(s: Box) -> 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(addr: A) -> IoResult { - 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 { - 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 { - 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) -> 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) { - 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) { - 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) { - 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 { - 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, + 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(addr: A) -> IoResult { - 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 { - 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 for TcpListener { fn listen(self) -> IoResult { - 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 for TcpListener { /// a `TcpListener`'s `listen` method, and this object can be used to accept new /// `TcpStream` instances. pub struct TcpAcceptor { - obj: Box, + 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) { self.obj.set_timeout(ms); } + pub fn set_timeout(&mut self, ms: Option) { 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 for TcpAcceptor { fn accept(&mut self) -> IoResult { - 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, + 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(addr: A) -> IoResult { - 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(&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 { - 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) { - 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) { - 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) { - 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, + inner: Arc } pub struct PipePair { @@ -55,14 +57,14 @@ impl PipeStream { /// } /// ``` pub fn open(fd: libc::c_int) -> IoResult { - 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) -> 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 { - 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 { - 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/os.rs b/src/libstd/os.rs index 175e23bf819..ea42117bab6 100644 --- a/src/libstd/os.rs +++ b/src/libstd/os.rs @@ -43,6 +43,7 @@ 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; @@ -603,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 { - return _pipe(); - - #[cfg(unix)] - unsafe fn _pipe() -> IoResult { - 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 { - // 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 diff --git a/src/libstd/sys/common/net.rs b/src/libstd/sys/common/net.rs new file mode 100644 index 00000000000..0559005100f --- /dev/null +++ b/src/libstd/sys/common/net.rs @@ -0,0 +1,909 @@ +// 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 or the MIT license +// , 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; + +#[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::() + } + 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::() + } + }; + return len as libc::socklen_t; + } +} + +pub fn socket(addr: SocketAddr, ty: libc::c_int) -> IoResult { + 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(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::() as libc::socklen_t); + if ret != 0 { + Err(last_net_error()) + } else { + Ok(()) + } + } +} + +pub fn getsockopt(fd: sock_t, opt: libc::c_int, + val: libc::c_int) -> IoResult { + unsafe { + let mut slot: T = mem::zeroed(); + let mut len = mem::size_of::() 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::()); + 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 +{ + let mut storage: libc::sockaddr_storage = unsafe { mem::zeroed() }; + let mut len = mem::size_of::() 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 { + match storage.ss_family as libc::c_int { + libc::AF_INET => { + assert!(len as uint >= mem::size_of::()); + 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::()); + 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) + -> Result, 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(fd: sock_t, + deadline: u64, + lock: || -> T, + read: |bool| -> libc::c_int) -> IoResult { + 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(fd: sock_t, + deadline: u64, + buf: &[u8], + write_everything: bool, + lock: || -> T, + write: |bool, *const u8, uint| -> i64) -> IoResult { + 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, + 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, + read_deadline: u64, + write_deadline: u64, +} + +impl TcpStream { + pub fn connect(addr: SocketAddr, timeout: Option) -> IoResult { + 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) -> 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 { + 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 { + 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) { + 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) { + self.read_deadline = timeout.map(|a| timer::now() + a).unwrap_or(0); + } + pub fn set_write_timeout(&mut self, timeout: Option) { + self.write_deadline = timeout.map(|a| timer::now() + a).unwrap_or(0); + } + + pub fn socket_name(&mut self) -> IoResult { + 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, + read_deadline: u64, + write_deadline: u64, +} + +impl UdpSocket { + pub fn bind(addr: SocketAddr) -> IoResult { + 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 { + 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::() 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) { + 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) { + self.read_deadline = timeout.map(|a| timer::now() + a).unwrap_or(0); + } + pub fn set_write_timeout(&mut self, timeout: Option) { + 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/mod.rs b/src/libstd/sys/unix/mod.rs index ad5de2dad48..5a43fd08f90 100644 --- a/src/libstd/sys/unix/mod.rs +++ b/src/libstd/sys/unix/mod.rs @@ -8,24 +8,51 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +#![allow(missing_doc)] + extern crate libc; use num; use prelude::*; use io::{mod, IoResult, IoError}; +use sys_common::mkerr_libc; +pub mod c; pub mod fs; pub mod os; -pub mod c; +pub mod tcp; +pub mod udp; +pub mod pipe; + +pub mod addrinfo { + pub use sys_common::net::get_host_addresses; +} -pub type sock_t = io::file::fd_t; +// 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 { - let errno = os::errno() as i32; - let mut err = decode_error(errno); - err.detail = Some(os::error_string(errno)); + 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 } @@ -64,6 +91,12 @@ pub fn decode_error(errno: i32) -> IoError { 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> (f: || -> I) -> I { let minus_one = -num::one::(); @@ -86,7 +119,10 @@ pub fn wouldblock() -> bool { err == libc::EWOULDBLOCK as int || err == libc::EAGAIN as int } -pub fn set_nonblocking(fd: net::sock_t, nb: bool) -> IoResult<()> { +pub fn set_nonblocking(fd: sock_t, nb: bool) -> IoResult<()> { let set = nb as libc::c_int; - super::mkerr_libc(retry(|| unsafe { c::ioctl(fd, c::FIONBIO, &set) })) + 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 index 34699eb27c1..4e495f043bc 100644 --- a/src/libstd/sys/unix/os.rs +++ b/src/libstd/sys/unix/os.rs @@ -11,6 +11,8 @@ use libc; use libc::{c_int, c_char}; use prelude::*; +use io::IoResult; +use sys::fs::FileDesc; use os::TMPBUF_SZ; @@ -99,3 +101,12 @@ pub fn error_string(errno: i32) -> String { ::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 or the MIT license +// , 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 { + 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 { + // the sun_path length is limited to SUN_LEN (with null) + assert!(mem::size_of::() >= + mem::size_of::()); + 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::() + 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) -> IoResult { + 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 { + 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, + read_deadline: u64, + write_deadline: u64, +} + +impl UnixStream { + pub fn connect(addr: &CString, + timeout: Option) -> IoResult { + connect(addr, libc::SOCK_STREAM, timeout).map(|inner| { + UnixStream::new(Arc::new(inner)) + }) + } + + fn new(inner: Arc) -> 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 { + 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) { + 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) { + self.read_deadline = timeout.map(|a| timer::now() + a).unwrap_or(0); + } + + pub fn set_write_timeout(&mut self, timeout: Option) { + 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 { + 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 { + 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, + 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 { + 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::(); + 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) { + 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/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 or the MIT license +// , 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 { + 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 { + 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 { + sockname(self.fd(), libc::getsockname) + } +} + +pub struct TcpAcceptor { + inner: Arc, + 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 { + // 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 { + sockname(self.fd(), libc::getsockname) + } + + pub fn set_timeout(&mut self, timeout: Option) { + 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/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 or the MIT license +// , 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/mod.rs b/src/libstd/sys/windows/mod.rs index 5f4129c1484..85fbc6b936c 100644 --- a/src/libstd/sys/windows/mod.rs +++ b/src/libstd/sys/windows/mod.rs @@ -33,12 +33,21 @@ macro_rules! helper_init( (static $name:ident: Helper<$m:ty>) => ( }; ) ) +pub mod c; pub mod fs; pub mod os; -pub mod c; +pub mod tcp; +pub mod udp; +pub mod pipe; + +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 @@ -140,7 +149,6 @@ pub fn set_nonblocking(fd: sock_t, nb: bool) -> IoResult<()> { } } -// FIXME: call this pub fn init_net() { unsafe { static START: Once = ONCE_INIT; 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 or the MIT license +// , 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 { + 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 { + 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, + write: Option, + read: Option, + read_deadline: u64, + write_deadline: u64, +} + +impl UnixStream { + fn try_connect(p: *const u16) -> Option { + // 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) -> IoResult { + 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 { + 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) { + 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) { + self.read_deadline = timeout.map(|a| timer::now() + a).unwrap_or(0); + } + pub fn set_write_timeout(&mut self, timeout: Option) { + 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 { + // 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 { + 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, + listener: UnixListener, + event: Event, + deadline: u64, +} + +struct AcceptorState { + abort: Event, + closed: atomic::AtomicBool, +} + +impl UnixAcceptor { + pub fn accept(&mut self) -> IoResult { + // 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) { + 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/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 or the MIT license +// , 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 { + 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 { + 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 { + 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 { + sockname(self.fd(), libc::getsockname) + } +} + +pub struct TcpAcceptor { + inner: Arc, + 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 { + // 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 { + sockname(self.fd(), libc::getsockname) + } + + pub fn set_timeout(&mut self, timeout: Option) { + 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/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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +pub use sys_common::net::UdpSocket; -- cgit 1.4.1-3-g733a5 From 3d195482a45bf3ed0f12dc9d70d14192262ca711 Mon Sep 17 00:00:00 2001 From: Aaron Turon Date: Wed, 15 Oct 2014 15:45:59 -0700 Subject: Runtime removal: refactor helper threads This patch continues the runtime removal by moving libnative::io::helper_thread into sys::helper_signal and sys_common::helper_thread Because this eliminates APIs in `libnative` and `librustrt`, it is a: [breaking-change] This functionality is likely to be available publicly, in some form, from `std` in the future. --- src/libnative/io/helper_thread.rs | 197 -------------------------------- src/libnative/io/mod.rs | 2 - src/libstd/sys/common/helper_thread.rs | 142 +++++++++++++++++++++++ src/libstd/sys/common/mod.rs | 1 + src/libstd/sys/common/net.rs | 2 + src/libstd/sys/unix/helper_signal.rs | 29 +++++ src/libstd/sys/unix/mod.rs | 11 ++ src/libstd/sys/windows/helper_signal.rs | 38 ++++++ src/libstd/sys/windows/mod.rs | 1 + 9 files changed, 224 insertions(+), 199 deletions(-) delete mode 100644 src/libnative/io/helper_thread.rs create mode 100644 src/libstd/sys/common/helper_thread.rs create mode 100644 src/libstd/sys/unix/helper_signal.rs create mode 100644 src/libstd/sys/windows/helper_signal.rs (limited to 'src/libstd') diff --git a/src/libnative/io/helper_thread.rs b/src/libnative/io/helper_thread.rs deleted file mode 100644 index d1368ad31f4..00000000000 --- a/src/libnative/io/helper_thread.rs +++ /dev/null @@ -1,197 +0,0 @@ -// 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 or the MIT license -// , 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. - -#![macro_escape] - -use std::cell::UnsafeCell; -use std::mem; -use std::rt::bookkeeping; -use std::rt::mutex::StaticNativeMutex; -use std::rt; -use std::task::TaskBuilder; - -use NativeTaskBuilder; - -/// 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 { - /// Internal lock which protects the remaining fields - pub lock: StaticNativeMutex, - - // You'll notice that the remaining fields are UnsafeCell, 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>, - - /// OS handle used to wake up a blocked helper thread - pub signal: UnsafeCell, - - /// Flag if this helper thread has booted and been initialized yet. - pub initialized: UnsafeCell, -} - -macro_rules! helper_init( (static $name:ident: Helper<$m:ty>) => ( - static $name: Helper<$m> = Helper { - lock: ::std::rt::mutex::NATIVE_MUTEX_INIT, - chan: ::std::cell::UnsafeCell { value: 0 as *mut Sender<$m> }, - signal: ::std::cell::UnsafeCell { value: 0 }, - initialized: ::std::cell::UnsafeCell { value: false }, - }; -) ) - -impl Helper { - /// 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(&'static self, - f: || -> T, - helper: fn(imp::signal, Receiver, 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) = imp::new(); - *self.signal.get() = send as uint; - - let t = f(); - TaskBuilder::new().native().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); - imp::signal(*self.signal.get() as imp::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> = mem::transmute(*self.chan.get()); - *self.chan.get() = 0 as *mut Sender; - drop(chan); - imp::signal(*self.signal.get() as imp::signal); - - // Wait for the child to exit - guard.wait(); - drop(guard); - - // Clean up after ourselves - self.lock.destroy(); - imp::close(*self.signal.get() as imp::signal); - *self.signal.get() = 0; - } - } -} - -#[cfg(unix)] -mod imp { - use libc; - use std::os; - - use io::file::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).inner_write([0]).ok().unwrap(); - } - - pub fn close(fd: libc::c_int) { - let _fd = FileDesc::new(fd, true); - } -} - -#[cfg(windows)] -mod imp { - use libc::{BOOL, LPCSTR, HANDLE, LPSECURITY_ATTRIBUTES, CloseHandle}; - use std::ptr; - use libc; - - 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/libnative/io/mod.rs b/src/libnative/io/mod.rs index 2a76bc29f7c..1d0b9f40d07 100644 --- a/src/libnative/io/mod.rs +++ b/src/libnative/io/mod.rs @@ -32,8 +32,6 @@ use std::num; // Local re-exports pub use self::process::Process; -mod helper_thread; - // Native I/O implementations pub mod process; mod util; diff --git a/src/libstd/sys/common/helper_thread.rs b/src/libstd/sys/common/helper_thread.rs new file mode 100644 index 00000000000..8c8ec4466a7 --- /dev/null +++ b/src/libstd/sys/common/helper_thread.rs @@ -0,0 +1,142 @@ +// 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 or the MIT license +// , 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. + +#![macro_escape] + +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 { + /// Internal lock which protects the remaining fields + pub lock: StaticNativeMutex, + + // You'll notice that the remaining fields are UnsafeCell, 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>, + + /// OS handle used to wake up a blocked helper thread + pub signal: UnsafeCell, + + /// Flag if this helper thread has booted and been initialized yet. + pub initialized: UnsafeCell, +} + +macro_rules! helper_init( (static $name:ident: Helper<$m:ty>) => ( + static $name: Helper<$m> = Helper { + lock: ::std::rt::mutex::NATIVE_MUTEX_INIT, + chan: ::std::cell::UnsafeCell { value: 0 as *mut Sender<$m> }, + signal: ::std::cell::UnsafeCell { value: 0 }, + initialized: ::std::cell::UnsafeCell { value: false }, + }; +) ) + +impl Helper { + /// 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(&'static self, + f: || -> T, + helper: fn(helper_signal::signal, Receiver, 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> = mem::transmute(*self.chan.get()); + *self.chan.get() = 0 as *mut Sender; + 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 index 402c62bb35e..75c2987078d 100644 --- a/src/libstd/sys/common/mod.rs +++ b/src/libstd/sys/common/mod.rs @@ -20,6 +20,7 @@ use path::BytesContainer; use collections; pub mod net; +pub mod helper_thread; // common error constructors diff --git a/src/libstd/sys/common/net.rs b/src/libstd/sys/common/net.rs index 0559005100f..7c44142d93c 100644 --- a/src/libstd/sys/common/net.rs +++ b/src/libstd/sys/common/net.rs @@ -24,6 +24,8 @@ use prelude::*; use cmp; use io; +// FIXME: move uses of Arc and deadline tracking to std::io + #[deriving(Show)] pub enum SocketStatus { Readable, 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 or the MIT license +// , 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 index 5a43fd08f90..6295864e0e1 100644 --- a/src/libstd/sys/unix/mod.rs +++ b/src/libstd/sys/unix/mod.rs @@ -17,12 +17,23 @@ 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 addrinfo { pub use sys_common::net::get_host_addresses; 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 or the MIT license +// , 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 index 85fbc6b936c..6f6ca3f2e62 100644 --- a/src/libstd/sys/windows/mod.rs +++ b/src/libstd/sys/windows/mod.rs @@ -39,6 +39,7 @@ pub mod os; pub mod tcp; pub mod udp; pub mod pipe; +pub mod helper_signal; pub mod addrinfo { pub use sys_common::net::get_host_addresses; -- cgit 1.4.1-3-g733a5 From 0f98e75b69d16edce9ca60d7961b8440856a3f72 Mon Sep 17 00:00:00 2001 From: Aaron Turon Date: Thu, 9 Oct 2014 16:27:28 -0700 Subject: Runtime removal: refactor process This patch continues the runtime removal by moving and refactoring the process implementation into the new `sys` module. Because this eliminates APIs in `libnative` and `librustrt`, it is a: [breaking-change] This functionality is likely to be available publicly, in some form, from `std` in the future. --- src/libnative/io/mod.rs | 20 -- src/librustrt/rtio.rs | 66 ---- src/libstd/io/process.rs | 228 ++++++++----- src/libstd/platform_imp/unix/fs.rs | 411 ----------------------- src/libstd/platform_imp/windows/fs.rs | 460 -------------------------- src/libstd/sys/common/helper_thread.rs | 11 - src/libstd/sys/unix/fs.rs | 411 +++++++++++++++++++++++ src/libstd/sys/unix/mod.rs | 2 +- src/libstd/sys/unix/process.rs | 587 +++++++++++++++++++++++++++++++++ src/libstd/sys/windows/fs.rs | 460 ++++++++++++++++++++++++++ src/libstd/sys/windows/mod.rs | 1 + src/libstd/sys/windows/process.rs | 511 ++++++++++++++++++++++++++++ 12 files changed, 2121 insertions(+), 1047 deletions(-) delete mode 100644 src/libstd/platform_imp/unix/fs.rs delete mode 100644 src/libstd/platform_imp/windows/fs.rs create mode 100644 src/libstd/sys/unix/fs.rs create mode 100644 src/libstd/sys/unix/process.rs create mode 100644 src/libstd/sys/windows/fs.rs create mode 100644 src/libstd/sys/windows/process.rs (limited to 'src/libstd') diff --git a/src/libnative/io/mod.rs b/src/libnative/io/mod.rs index 1d0b9f40d07..29370dee88b 100644 --- a/src/libnative/io/mod.rs +++ b/src/libnative/io/mod.rs @@ -29,13 +29,6 @@ use std::os; use std::rt::rtio::{mod, IoResult, IoError}; use std::num; -// Local re-exports -pub use self::process::Process; - -// Native I/O implementations -pub mod process; -mod util; - #[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd", @@ -123,19 +116,6 @@ impl rtio::IoFactory for IoFactory { fn timer_init(&mut self) -> IoResult> { timer::Timer::new().map(|t| box t as Box) } - fn spawn(&mut self, cfg: rtio::ProcessConfig) - -> IoResult<(Box, - Vec>>)> { - process::Process::spawn(cfg).map(|(p, io)| { - (box p as Box, - io.into_iter().map(|p| p.map(|p| { - box p as Box - })).collect()) - }) - } - fn kill(&mut self, pid: libc::pid_t, signum: int) -> IoResult<()> { - process::Process::kill(pid, signum) - } #[cfg(unix)] fn tty_open(&mut self, fd: c_int, _readable: bool) -> IoResult> { diff --git a/src/librustrt/rtio.rs b/src/librustrt/rtio.rs index 3ebfcaea687..cdcefc2088e 100644 --- a/src/librustrt/rtio.rs +++ b/src/librustrt/rtio.rs @@ -46,61 +46,6 @@ pub trait RemoteCallback { fn fire(&mut self); } -/// Data needed to spawn a process. Serializes the `std::io::process::Command` -/// builder. -pub struct ProcessConfig<'a> { - /// Path to the program to run. - pub program: &'a CString, - - /// Arguments to pass to the program (doesn't include the program itself). - pub args: &'a [CString], - - /// Optional environment to specify for the program. If this is None, then - /// it will inherit the current process's environment. - pub env: Option<&'a [(&'a CString, &'a CString)]>, - - /// Optional working directory for the new process. If this is None, then - /// the current directory of the running process is inherited. - pub cwd: Option<&'a CString>, - - /// Configuration for the child process's stdin handle (file descriptor 0). - /// This field defaults to `CreatePipe(true, false)` so the input can be - /// written to. - pub stdin: StdioContainer, - - /// Configuration for the child process's stdout handle (file descriptor 1). - /// This field defaults to `CreatePipe(false, true)` so the output can be - /// collected. - pub stdout: StdioContainer, - - /// Configuration for the child process's stdout handle (file descriptor 2). - /// This field defaults to `CreatePipe(false, true)` so the output can be - /// collected. - pub stderr: StdioContainer, - - /// Any number of streams/file descriptors/pipes may be attached to this - /// process. This list enumerates the file descriptors and such for the - /// process to be spawned, and the file descriptors inherited will start at - /// 3 and go to the length of this array. The first three file descriptors - /// (stdin/stdout/stderr) are configured with the `stdin`, `stdout`, and - /// `stderr` fields. - pub extra_io: &'a [StdioContainer], - - /// 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 - /// fail. Failure in the `setuid` call on unix will also cause the spawn to - /// fail. - pub uid: Option, - - /// Similar to `uid`, but sets the group id of the child process. This has - /// the same semantics as the `uid` field. - pub gid: Option, - - /// If true, the child process is spawned in a detached state. On unix, this - /// means that the child is the leader of a new process group. - pub detach: bool, -} - pub struct LocalIo<'a> { factory: &'a mut IoFactory+'a, } @@ -170,10 +115,6 @@ impl<'a> LocalIo<'a> { pub trait IoFactory { fn timer_init(&mut self) -> IoResult>; - fn spawn(&mut self, cfg: ProcessConfig) - -> IoResult<(Box, - Vec>>)>; - fn kill(&mut self, pid: libc::pid_t, signal: int) -> IoResult<()>; fn tty_open(&mut self, fd: c_int, readable: bool) -> IoResult>; } @@ -184,13 +125,6 @@ pub trait RtioTimer { fn period(&mut self, msecs: u64, cb: Box); } -pub trait RtioProcess { - fn id(&self) -> libc::pid_t; - fn kill(&mut self, signal: int) -> IoResult<()>; - fn wait(&mut self) -> IoResult; - fn set_timeout(&mut self, timeout: Option); -} - pub trait RtioPipe { fn read(&mut self, buf: &mut [u8]) -> IoResult; fn write(&mut self, buf: &[u8]) -> IoResult<()>; 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, + handle: ProcessImp, forget: bool, + /// None until wait() is called. + exit_code: Option, + + /// Manually delivered signal + exit_signal: Option, + + /// 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, + pub stdin: Option, /// 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, + pub stdout: Option, /// 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, - - /// Extra I/O handles as configured by the original `ProcessConfig` when - /// this process was created. This is by default empty. - pub extra_io: Vec>, + pub stderr: Option, } /// 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; @@ -160,7 +175,6 @@ pub struct Command { stdin: StdioContainer, stdout: StdioContainer, stderr: StdioContainer, - extra_io: Vec, uid: Option, gid: Option, 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 { - 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 = - 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::>()) - }; - 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, Option)> { + 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 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 { + self.uid.clone() + } + fn gid(&self) -> Option { + 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 { 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) { - 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/platform_imp/unix/fs.rs b/src/libstd/platform_imp/unix/fs.rs deleted file mode 100644 index 3dcd99859e8..00000000000 --- a/src/libstd/platform_imp/unix/fs.rs +++ /dev/null @@ -1,411 +0,0 @@ -// 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 or the MIT license -// , 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 { - 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 { - 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 { - 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 { - let mut stat: libc::stat = unsafe { mem::zeroed() }; - match unsafe { libc::fstat(self.fd(), &mut stat) } { - 0 => Ok(mkstat(&stat)), - _ => Err(super::last_error()), - } - } - - /// Extract the actual filedescriptor without closing it. - pub fn unwrap(self) -> fd_t { - let fd = self.fd; - unsafe { mem::forget(self) }; - fd - } -} - -/* - -impl RtioTTY for FileDesc { - fn read(&mut self, buf: &mut [u8]) -> IoResult { - self.inner_read(buf) - } - fn write(&mut self, buf: &[u8]) -> IoResult<()> { - self.inner_write(buf) - } - fn set_raw(&mut self, _raw: bool) -> IoResult<()> { - Err(super::unimpl()) - } - fn get_winsize(&mut self) -> IoResult<(int, int)> { - Err(super::unimpl()) - } - fn isatty(&self) -> bool { false } -} -*/ - -impl Drop for FileDesc { - fn drop(&mut self) { - // closing stdio file handles makes no sense, so never do it. Also, note - // that errors are ignored when closing a file descriptor. The reason - // for this is that if an error occurs we don't actually know if the - // file descriptor was closed or not, and if we retried (for something - // like EINTR), we might close another valid file descriptor (opened - // after we closed ours. - if self.close_on_drop && self.fd > libc::STDERR_FILENO { - let n = unsafe { libc::close(self.fd) }; - if n != 0 { - println!("error {} when closing file descriptor {}", n, self.fd); - } - } - } -} - -pub fn open(path: &Path, fm: FileMode, fa: FileAccess) -> IoResult { - 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> { - use libc::{dirent_t}; - use libc::{opendir, readdir_r, closedir}; - - fn prune(root: &CString, dirs: Vec) -> Vec { - 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::::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 { - 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 = 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 { - 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 { - 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/platform_imp/windows/fs.rs b/src/libstd/platform_imp/windows/fs.rs deleted file mode 100644 index a07688b2fed..00000000000 --- a/src/libstd/platform_imp/windows/fs.rs +++ /dev/null @@ -1,460 +0,0 @@ -// 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 or the MIT license -// , 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 { - 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 { - 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 { - self.seek_common(pos, style) - } - - pub fn tell(&self) -> IoResult { - 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 { - 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> { - sys::to_utf16(s.as_str()) -} - -pub fn open(path: &Path, fm: FileMode, fa: FileAccess) -> IoResult { - // 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> { - fn prune(root: &Path, dirs: Vec) -> Vec { - 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) -> 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 { - // 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 { - 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 { - // 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/common/helper_thread.rs b/src/libstd/sys/common/helper_thread.rs index 8c8ec4466a7..87907fde277 100644 --- a/src/libstd/sys/common/helper_thread.rs +++ b/src/libstd/sys/common/helper_thread.rs @@ -20,8 +20,6 @@ //! can be created in the future and there must be no active timers at that //! time. -#![macro_escape] - use mem; use rt::bookkeeping; use rt::mutex::StaticNativeMutex; @@ -57,15 +55,6 @@ pub struct Helper { pub initialized: UnsafeCell, } -macro_rules! helper_init( (static $name:ident: Helper<$m:ty>) => ( - static $name: Helper<$m> = Helper { - lock: ::std::rt::mutex::NATIVE_MUTEX_INIT, - chan: ::std::cell::UnsafeCell { value: 0 as *mut Sender<$m> }, - signal: ::std::cell::UnsafeCell { value: 0 }, - initialized: ::std::cell::UnsafeCell { value: false }, - }; -) ) - impl Helper { /// Lazily boots a helper thread, becoming a no-op if the helper has already /// been spawned. diff --git a/src/libstd/sys/unix/fs.rs b/src/libstd/sys/unix/fs.rs new file mode 100644 index 00000000000..3dcd99859e8 --- /dev/null +++ b/src/libstd/sys/unix/fs.rs @@ -0,0 +1,411 @@ +// Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , 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 { + 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 { + 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 { + 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 { + let mut stat: libc::stat = unsafe { mem::zeroed() }; + match unsafe { libc::fstat(self.fd(), &mut stat) } { + 0 => Ok(mkstat(&stat)), + _ => Err(super::last_error()), + } + } + + /// Extract the actual filedescriptor without closing it. + pub fn unwrap(self) -> fd_t { + let fd = self.fd; + unsafe { mem::forget(self) }; + fd + } +} + +/* + +impl RtioTTY for FileDesc { + fn read(&mut self, buf: &mut [u8]) -> IoResult { + self.inner_read(buf) + } + fn write(&mut self, buf: &[u8]) -> IoResult<()> { + self.inner_write(buf) + } + fn set_raw(&mut self, _raw: bool) -> IoResult<()> { + Err(super::unimpl()) + } + fn get_winsize(&mut self) -> IoResult<(int, int)> { + Err(super::unimpl()) + } + fn isatty(&self) -> bool { false } +} +*/ + +impl Drop for FileDesc { + fn drop(&mut self) { + // closing stdio file handles makes no sense, so never do it. Also, note + // that errors are ignored when closing a file descriptor. The reason + // for this is that if an error occurs we don't actually know if the + // file descriptor was closed or not, and if we retried (for something + // like EINTR), we might close another valid file descriptor (opened + // after we closed ours. + if self.close_on_drop && self.fd > libc::STDERR_FILENO { + let n = unsafe { libc::close(self.fd) }; + if n != 0 { + println!("error {} when closing file descriptor {}", n, self.fd); + } + } + } +} + +pub fn open(path: &Path, fm: FileMode, fa: FileAccess) -> IoResult { + 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> { + use libc::{dirent_t}; + use libc::{opendir, readdir_r, closedir}; + + fn prune(root: &CString, dirs: Vec) -> Vec { + 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::::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 { + 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 = 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 { + 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 { + let p = p.to_c_str(); + let mut stat: libc::stat = unsafe { mem::zeroed() }; + match unsafe { libc::lstat(p.as_ptr(), &mut stat) } { + 0 => Ok(mkstat(&stat)), + _ => Err(super::last_error()), + } +} + +pub fn utime(p: &Path, atime: u64, mtime: u64) -> IoResult<()> { + let p = p.to_c_str(); + let buf = libc::utimbuf { + actime: (atime / 1000) as libc::time_t, + modtime: (mtime / 1000) as libc::time_t, + }; + mkerr_libc(unsafe { libc::utime(p.as_ptr(), &buf) }) +} + +#[cfg(test)] +mod tests { + use super::FileDesc; + use libc; + use os; + use prelude::*; + + #[cfg_attr(target_os = "freebsd", ignore)] // hmm, maybe pipes have a tiny buffer + #[test] + fn test_file_desc() { + // Run this test with some pipes so we don't have to mess around with + // opening or closing files. + let os::Pipe { reader, writer } = unsafe { os::pipe().unwrap() }; + let mut reader = FileDesc::new(reader, true); + let mut writer = FileDesc::new(writer, true); + + writer.write(b"test").ok().unwrap(); + let mut buf = [0u8, ..4]; + match reader.read(buf) { + Ok(4) => { + assert_eq!(buf[0], 't' as u8); + assert_eq!(buf[1], 'e' as u8); + assert_eq!(buf[2], 's' as u8); + assert_eq!(buf[3], 't' as u8); + } + r => panic!("invalid read: {}", r), + } + + assert!(writer.read(buf).is_err()); + assert!(reader.write(buf).is_err()); + } +} diff --git a/src/libstd/sys/unix/mod.rs b/src/libstd/sys/unix/mod.rs index 6295864e0e1..b404dc7fdbd 100644 --- a/src/libstd/sys/unix/mod.rs +++ b/src/libstd/sys/unix/mod.rs @@ -17,7 +17,6 @@ use prelude::*; use io::{mod, IoResult, IoError}; use sys_common::mkerr_libc; - macro_rules! helper_init( (static $name:ident: Helper<$m:ty>) => ( static $name: Helper<$m> = Helper { lock: ::rt::mutex::NATIVE_MUTEX_INIT, @@ -34,6 +33,7 @@ pub mod tcp; pub mod udp; pub mod pipe; pub mod helper_signal; +pub mod process; pub mod addrinfo { pub use sys_common::net::get_host_addresses; diff --git a/src/libstd/sys/unix/process.rs b/src/libstd/sys/unix/process.rs new file mode 100644 index 00000000000..0965d98d9b0 --- /dev/null +++ b/src/libstd/sys/unix/process.rs @@ -0,0 +1,587 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , 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) + +/// 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, 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(cfg: &C, in_fd: Option

, + out_fd: Option

, err_fd: Option

) + -> IoResult + where C: ProcessConfig, 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,&'static ProcessConfig>(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

, 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 { + 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, + (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, 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 { + 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(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(env: Option<&collections::HashMap>, + cb: proc(*const c_void) -> T) -> T + where K: BytesContainer + Eq + Hash, V: BytesContainer +{ + // On posixy systems we can pass a char** for envp, which is a + // null-terminated array of "k=v\0" strings. Since we must create + // these strings locally, yet expose a raw pointer to them, we + // create a temporary vector to own the CStrings that outlives the + // call to cb. + match env { + Some(env) => { + let mut tmps = Vec::with_capacity(env.len()); + + for pair in env.iter() { + let mut kv = Vec::new(); + kv.push_all(pair.ref0().container_as_bytes()); + kv.push('=' as u8); + kv.push_all(pair.ref1().container_as_bytes()); + kv.push(0); // terminating null + tmps.push(kv); + } + + // As with `with_argv`, this is unsafe, since cb could leak the pointers. + let mut ptrs: Vec<*const libc::c_char> = + tmps.iter() + .map(|tmp| tmp.as_ptr() as *const libc::c_char) + .collect(); + ptrs.push(ptr::null()); + + cb(ptrs.as_ptr() as *const c_void) + } + _ => cb(ptr::null()) + } +} + +fn translate_status(status: c_int) -> ProcessExit { + #![allow(non_snake_case)] + #[cfg(any(target_os = "linux", target_os = "android"))] + mod imp { + pub fn WIFEXITED(status: i32) -> bool { (status & 0xff) == 0 } + pub fn WEXITSTATUS(status: i32) -> i32 { (status >> 8) & 0xff } + pub fn WTERMSIG(status: i32) -> i32 { status & 0x7f } + } + + #[cfg(any(target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "dragonfly"))] + mod imp { + pub fn WIFEXITED(status: i32) -> bool { (status & 0x7f) == 0 } + pub fn WEXITSTATUS(status: i32) -> i32 { status >> 8 } + pub fn WTERMSIG(status: i32) -> i32 { status & 0o177 } + } + + if imp::WIFEXITED(status) { + ExitStatus(imp::WEXITSTATUS(status) as int) + } else { + ExitSignal(imp::WTERMSIG(status) as int) + } +} diff --git a/src/libstd/sys/windows/fs.rs b/src/libstd/sys/windows/fs.rs new file mode 100644 index 00000000000..a07688b2fed --- /dev/null +++ b/src/libstd/sys/windows/fs.rs @@ -0,0 +1,460 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , 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 { + 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 { + 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 { + self.seek_common(pos, style) + } + + pub fn tell(&self) -> IoResult { + 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 { + 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> { + sys::to_utf16(s.as_str()) +} + +pub fn open(path: &Path, fm: FileMode, fa: FileAccess) -> IoResult { + // 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> { + fn prune(root: &Path, dirs: Vec) -> Vec { + 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) -> 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 { + // 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 { + 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 { + // FIXME: implementation is missing + Err(super::unimpl()) +} + +pub fn utime(p: &Path, atime: u64, mtime: u64) -> IoResult<()> { + let mut buf = libc::utimbuf { + actime: atime as libc::time64_t, + modtime: mtime as libc::time64_t, + }; + let p = try!(to_utf16(p)); + mkerr_libc(unsafe { + libc::wutime(p.as_ptr(), &mut buf) + }) +} diff --git a/src/libstd/sys/windows/mod.rs b/src/libstd/sys/windows/mod.rs index 6f6ca3f2e62..f50244701e4 100644 --- a/src/libstd/sys/windows/mod.rs +++ b/src/libstd/sys/windows/mod.rs @@ -40,6 +40,7 @@ pub mod tcp; pub mod udp; pub mod pipe; pub mod helper_signal; +pub mod process; pub mod addrinfo { pub use sys_common::net::get_host_addresses; diff --git a/src/libstd/sys/windows/process.rs b/src/libstd/sys/windows/process.rs new file mode 100644 index 00000000000..67e87841ed2 --- /dev/null +++ b/src/libstd/sys/windows/process.rs @@ -0,0 +1,511 @@ +// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , 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(cfg: &C, in_fd: Option

, + out_fd: Option

, err_fd: Option

) + -> IoResult + where C: ProcessConfig, 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::() 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

, 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::(); + let mut sa = libc::SECURITY_ATTRIBUTES { + nLength: size as libc::DWORD, + lpSecurityDescriptor: ptr::null_mut(), + bInheritHandle: 1, + }; + let mut filename: Vec = "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 = 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 { + 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 = 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(env: Option<&collections::HashMap>, + 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(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 = 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::>() + .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() + ); + } +} -- cgit 1.4.1-3-g733a5 From b8f1193bb1bb66610f479cd78e3dc5526e93058d Mon Sep 17 00:00:00 2001 From: Aaron Turon Date: Thu, 16 Oct 2014 18:57:11 -0700 Subject: Runtime removal: refactor timer This patch continues runtime removal by moving out timer-related code into `sys`. Because this eliminates APIs in `libnative` and `librustrt`, it is a: [breaking-change] This functionality is likely to be available publicly, in some form, from `std` in the future. --- src/libnative/io/mod.rs | 17 --- src/libnative/io/timer_unix.rs | 285 -------------------------------------- src/libnative/io/timer_windows.rs | 210 ---------------------------- src/libstd/io/timer.rs | 19 ++- src/libstd/sys/unix/mod.rs | 1 + src/libstd/sys/unix/timer.rs | 282 +++++++++++++++++++++++++++++++++++++ src/libstd/sys/windows/mod.rs | 1 + src/libstd/sys/windows/timer.rs | 208 ++++++++++++++++++++++++++++ 8 files changed, 500 insertions(+), 523 deletions(-) delete mode 100644 src/libnative/io/timer_unix.rs delete mode 100644 src/libnative/io/timer_windows.rs create mode 100644 src/libstd/sys/unix/timer.rs create mode 100644 src/libstd/sys/windows/timer.rs (limited to 'src/libstd') diff --git a/src/libnative/io/mod.rs b/src/libnative/io/mod.rs index 29370dee88b..5d6d23f5f03 100644 --- a/src/libnative/io/mod.rs +++ b/src/libnative/io/mod.rs @@ -29,19 +29,6 @@ use std::os; use std::rt::rtio::{mod, IoResult, IoError}; use std::num; -#[cfg(any(target_os = "macos", - target_os = "ios", - target_os = "freebsd", - target_os = "dragonfly", - target_os = "android", - target_os = "linux"))] -#[path = "timer_unix.rs"] -pub mod timer; - -#[cfg(target_os = "windows")] -#[path = "timer_windows.rs"] -pub mod timer; - #[cfg(windows)] #[path = "tty_windows.rs"] mod tty; @@ -112,10 +99,6 @@ impl IoFactory { } impl rtio::IoFactory for IoFactory { - // misc - fn timer_init(&mut self) -> IoResult> { - timer::Timer::new().map(|t| box t as Box) - } #[cfg(unix)] fn tty_open(&mut self, fd: c_int, _readable: bool) -> IoResult> { diff --git a/src/libnative/io/timer_unix.rs b/src/libnative/io/timer_unix.rs deleted file mode 100644 index c26e2e76cee..00000000000 --- a/src/libnative/io/timer_unix.rs +++ /dev/null @@ -1,285 +0,0 @@ -// 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 or the MIT license -// , 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 std::mem; -use std::os; -use std::ptr; -use std::rt::rtio; -use std::rt::rtio::IoResult; -use std::sync::atomic; -use std::comm; - -use io::c; -use platform_imp::fs::FileDesc; -use io::helper_thread::Helper; - -helper_init!(static HELPER: Helper) - -pub struct Timer { - id: uint, - inner: Option>, -} - -pub struct Inner { - cb: Option>, - interval: u64, - repeat: bool, - target: u64, - id: uint, -} - -pub enum Req { - // Add a new timer to the helper thread. - NewTimer(Box), - - // Remove a timer based on its id and then send it back on the channel - // provided - RemoveTimer(uint, Sender>), -} - -// 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, _: ()) { - 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> = vec![]; - let mut dead = vec![]; - - // inserts a timer into an array of timers (sorted by firing time) - fn insert(t: Box, active: &mut Vec>) { - 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>, - dead: &mut Vec<(uint, Box)>) { - 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.inner_read(buf).ok().unwrap(), 1); - } - - -1 if os::errno() == libc::EINTR as int => {} - n => panic!("helper thread panicked in select() with error: {} ({})", - n, os::last_os_error()) - } - } -} - -impl Timer { - pub fn new() -> IoResult { - // 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(ms: u64) { - 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?"); - } - } - } - - fn inner(&mut self) -> Box { - match self.inner.take() { - Some(i) => i, - None => { - let (tx, rx) = channel(); - HELPER.send(RemoveTimer(self.id, tx)); - rx.recv() - } - } - } -} - -impl rtio::RtioTimer for Timer { - fn sleep(&mut self, msecs: u64) { - let mut inner = self.inner(); - inner.cb = None; // cancel any previous request - self.inner = Some(inner); - - Timer::sleep(msecs); - } - - fn oneshot(&mut self, msecs: u64, cb: Box) { - 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)); - } - - fn period(&mut self, msecs: u64, cb: Box) { - 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)); - } -} - -impl Drop for Timer { - fn drop(&mut self) { - self.inner = Some(self.inner()); - } -} diff --git a/src/libnative/io/timer_windows.rs b/src/libnative/io/timer_windows.rs deleted file mode 100644 index c17c541fc01..00000000000 --- a/src/libnative/io/timer_windows.rs +++ /dev/null @@ -1,210 +0,0 @@ -// 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 or the MIT license -// , 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 std::ptr; -use std::rt::rtio; -use std::rt::rtio::{IoResult, Callback}; -use std::comm; - -use io::helper_thread::Helper; - -helper_init!(static HELPER: Helper) - -pub struct Timer { - obj: libc::HANDLE, - on_worker: bool, -} - -pub enum Req { - NewTimer(libc::HANDLE, Box, bool), - RemoveTimer(libc::HANDLE, Sender<()>), -} - -fn helper(input: libc::HANDLE, messages: Receiver, _: ()) { - 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 { - 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, }) - } - } - - pub fn sleep(ms: u64) { - use std::rt::rtio::RtioTimer; - let mut t = Timer::new().ok().expect("must allocate a timer!"); - t.sleep(ms); - } - - 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; - } -} - -impl rtio::RtioTimer for Timer { - 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) }; - } - - fn oneshot(&mut self, msecs: u64, cb: Box) { - 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; - } - - fn period(&mut self, msecs: u64, cb: Box) { - 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/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, + 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 { - 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/sys/unix/mod.rs b/src/libstd/sys/unix/mod.rs index b404dc7fdbd..03a4e56f00d 100644 --- a/src/libstd/sys/unix/mod.rs +++ b/src/libstd/sys/unix/mod.rs @@ -34,6 +34,7 @@ pub mod udp; pub mod pipe; pub mod helper_signal; pub mod process; +pub mod timer; pub mod addrinfo { pub use sys_common::net::get_host_addresses; 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 or the MIT license +// , 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) + +pub trait Callback { + fn call(&mut self); +} + +pub struct Timer { + id: uint, + inner: Option>, +} + +pub struct Inner { + cb: Option>, + interval: u64, + repeat: bool, + target: u64, + id: uint, +} + +pub enum Req { + // Add a new timer to the helper thread. + NewTimer(Box), + + // Remove a timer based on its id and then send it back on the channel + // provided + RemoveTimer(uint, Sender>), +} + +// 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, _: ()) { + 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> = vec![]; + let mut dead = vec![]; + + // inserts a timer into an array of timers (sorted by firing time) + fn insert(t: Box, active: &mut Vec>) { + 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>, + dead: &mut Vec<(uint, Box)>) { + 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 { + // 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) { + 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) { + 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 { + 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/windows/mod.rs b/src/libstd/sys/windows/mod.rs index f50244701e4..0dc06de33e0 100644 --- a/src/libstd/sys/windows/mod.rs +++ b/src/libstd/sys/windows/mod.rs @@ -41,6 +41,7 @@ pub mod udp; pub mod pipe; pub mod helper_signal; pub mod process; +pub mod timer; pub mod addrinfo { pub use sys_common::net::get_host_addresses; 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 or the MIT license +// , 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) + +pub trait Callback { + fn call(&mut self); +} + +pub struct Timer { + obj: libc::HANDLE, + on_worker: bool, +} + +pub enum Req { + NewTimer(libc::HANDLE, Box, bool), + RemoveTimer(libc::HANDLE, Sender<()>), +} + +fn helper(input: libc::HANDLE, messages: Receiver, _: ()) { + 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 { + 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) { + 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) { + 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; + } +} -- cgit 1.4.1-3-g733a5 From 431dcdc840a27f7c7418b7dff73a329eada8a407 Mon Sep 17 00:00:00 2001 From: Aaron Turon Date: Fri, 17 Oct 2014 13:33:08 -0700 Subject: Runtime removal: refactor tty This patch continues runtime removal by moving the tty implementations into `sys`. Because this eliminates APIs in `libnative` and `librustrt`, it is a: [breaking-change] This functionality is likely to be available publicly, in some form, from `std` in the future. --- src/libnative/io/mod.rs | 26 ------- src/libnative/io/tty_windows.rs | 160 -------------------------------------- src/libstd/io/stdio.rs | 26 +++---- src/libstd/sys/common/mod.rs | 8 ++ src/libstd/sys/unix/fs.rs | 19 ----- src/libstd/sys/unix/mod.rs | 1 + src/libstd/sys/unix/tty.rs | 47 ++++++++++++ src/libstd/sys/windows/mod.rs | 1 + src/libstd/sys/windows/tty.rs | 166 ++++++++++++++++++++++++++++++++++++++++ 9 files changed, 235 insertions(+), 219 deletions(-) delete mode 100644 src/libnative/io/tty_windows.rs create mode 100644 src/libstd/sys/unix/tty.rs create mode 100644 src/libstd/sys/windows/tty.rs (limited to 'src/libstd') diff --git a/src/libnative/io/mod.rs b/src/libnative/io/mod.rs index 5d6d23f5f03..8c7751588ce 100644 --- a/src/libnative/io/mod.rs +++ b/src/libnative/io/mod.rs @@ -99,30 +99,4 @@ impl IoFactory { } impl rtio::IoFactory for IoFactory { - #[cfg(unix)] - fn tty_open(&mut self, fd: c_int, _readable: bool) - -> IoResult> { - if unsafe { libc::isatty(fd) } != 0 { - Ok(box file::FileDesc::new(fd, true) as Box) - } else { - Err(IoError { - code: libc::ENOTTY as uint, - extra: 0, - detail: None, - }) - } - } - #[cfg(windows)] - fn tty_open(&mut self, fd: c_int, _readable: bool) - -> IoResult> { - if tty::is_tty(fd) { - Ok(box tty::WindowsTTY::new(fd) as Box) - } else { - Err(IoError { - code: libc::ERROR_INVALID_HANDLE as uint, - extra: 0, - detail: None, - }) - } - } } diff --git a/src/libnative/io/tty_windows.rs b/src/libnative/io/tty_windows.rs deleted file mode 100644 index cf2a0f9dda4..00000000000 --- a/src/libnative/io/tty_windows.rs +++ /dev/null @@ -1,160 +0,0 @@ -// 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 or the MIT license -// , 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 std::io::MemReader; -use std::ptr; -use std::rt::rtio::{IoResult, IoError, RtioTTY}; -use std::str::from_utf8; - -fn invalid_encoding() -> IoError { - IoError { - code: ERROR_ILLEGAL_CHARACTER as uint, - extra: 0, - detail: Some("text was not valid unicode".to_string()), - } -} - -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 WindowsTTY { - closeme: bool, - handle: HANDLE, - utf8: MemReader, -} - -impl WindowsTTY { - pub fn new(fd: c_int) -> WindowsTTY { - // 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 }; - WindowsTTY { - handle: handle, - utf8: MemReader::new(Vec::new()), - closeme: closeme, - } - } -} - -impl Drop for WindowsTTY { - fn drop(&mut self) { - if self.closeme { - // Nobody cares about the return value - let _ = unsafe { CloseHandle(self.handle) }; - } - } -} - -impl RtioTTY for WindowsTTY { - fn read(&mut self, buf: &mut [u8]) -> IoResult { - // 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()) - } - - fn write(&mut self, buf: &[u8]) -> IoResult<()> { - let utf16 = match from_utf8(buf) { - Some(utf8) => { - utf8.as_slice().utf16_units().collect::>() - } - 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(()), - } - } - - 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(()), - } - } - - 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 - fn isatty(&self) -> bool { true } -} diff --git a/src/libstd/io/stdio.rs b/src/libstd/io/stdio.rs index 98644cfc7e9..158d596ea13 100644 --- a/src/libstd/io/stdio.rs +++ b/src/libstd/io/stdio.rs @@ -36,7 +36,7 @@ use kinds::Send; use libc; use option::{Option, Some, None}; use boxed::Box; -use sys::fs::FileDesc; +use sys::{fs, tty}; use result::{Ok, Err}; use rt; use rt::local::Local; @@ -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), - File(FileDesc), + TTY(tty::TTY), + File(fs::FileDesc), } -fn src(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(FileDesc::new(fd, false))), - }) - }).map_err(IoError::from_rtio_error).unwrap() +fn src(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) @@ -278,7 +276,7 @@ impl Reader for StdReader { // print!'d prompt not being shown until after the user hits // enter. flush(); - tty.read(buf).map_err(IoError::from_rtio_error) + tty.read(buf).map(|i| i as uint) }, File(ref mut file) => file.read(buf).map(|i| i as uint), }; @@ -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 { @@ -372,7 +370,7 @@ impl Writer for StdWriter { let max_size = if cfg!(windows) {8192} else {uint::MAX}; for chunk in buf.chunks(max_size) { try!(match self.inner { - TTY(ref mut tty) => tty.write(chunk).map_err(IoError::from_rtio_error), + TTY(ref mut tty) => tty.write(chunk), File(ref mut file) => file.write(chunk), }) } diff --git a/src/libstd/sys/common/mod.rs b/src/libstd/sys/common/mod.rs index 75c2987078d..c5f8214a5c3 100644 --- a/src/libstd/sys/common/mod.rs +++ b/src/libstd/sys/common/mod.rs @@ -48,6 +48,14 @@ pub fn short_write(n: uint, desc: &'static str) -> IoError { } } +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(ret: Int) -> IoResult<()> { if !ret.is_zero() { diff --git a/src/libstd/sys/unix/fs.rs b/src/libstd/sys/unix/fs.rs index 3dcd99859e8..2d02c34e958 100644 --- a/src/libstd/sys/unix/fs.rs +++ b/src/libstd/sys/unix/fs.rs @@ -137,25 +137,6 @@ impl FileDesc { } } -/* - -impl RtioTTY for FileDesc { - fn read(&mut self, buf: &mut [u8]) -> IoResult { - self.inner_read(buf) - } - fn write(&mut self, buf: &[u8]) -> IoResult<()> { - self.inner_write(buf) - } - fn set_raw(&mut self, _raw: bool) -> IoResult<()> { - Err(super::unimpl()) - } - fn get_winsize(&mut self) -> IoResult<(int, int)> { - Err(super::unimpl()) - } - fn isatty(&self) -> bool { false } -} -*/ - impl Drop for FileDesc { fn drop(&mut self) { // closing stdio file handles makes no sense, so never do it. Also, note diff --git a/src/libstd/sys/unix/mod.rs b/src/libstd/sys/unix/mod.rs index 03a4e56f00d..4bd1dd20163 100644 --- a/src/libstd/sys/unix/mod.rs +++ b/src/libstd/sys/unix/mod.rs @@ -35,6 +35,7 @@ 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; 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 or the MIT license +// , 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 { + 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 { + 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/windows/mod.rs b/src/libstd/sys/windows/mod.rs index 0dc06de33e0..98da4d4e763 100644 --- a/src/libstd/sys/windows/mod.rs +++ b/src/libstd/sys/windows/mod.rs @@ -42,6 +42,7 @@ 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; 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 or the MIT license +// , 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 { + 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 { + // 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::>() + } + 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) }; + } + } +} -- cgit 1.4.1-3-g733a5 From fa94fdad3e880d2d6cbd82c12bd12caefbeb81a8 Mon Sep 17 00:00:00 2001 From: Aaron Turon Date: Fri, 17 Oct 2014 13:39:27 -0700 Subject: Runtime removal: fully remove rtio This patch cleans up the remnants of the runtime IO interface. Because this eliminates APIs in `libnative` and `librustrt`, it is a: [breaking-change] This functionality is likely to be available publicly, in some form, from `std` in the future. --- src/libgreen/basic.rs | 4 +- src/libgreen/simple.rs | 4 +- src/libgreen/task.rs | 9 ---- src/libnative/io/mod.rs | 102 ----------------------------------- src/libnative/lib.rs | 1 - src/libnative/task.rs | 8 --- src/librustrt/lib.rs | 1 - src/librustrt/rtio.rs | 130 +-------------------------------------------- src/librustrt/task.rs | 8 --- src/libstd/io/mod.rs | 15 +----- src/libstd/sys/unix/mod.rs | 5 ++ 11 files changed, 11 insertions(+), 276 deletions(-) delete mode 100644 src/libnative/io/mod.rs (limited to 'src/libstd') diff --git a/src/libgreen/basic.rs b/src/libgreen/basic.rs index b476f46833b..e2b8eb54ac3 100644 --- a/src/libgreen/basic.rs +++ b/src/libgreen/basic.rs @@ -18,7 +18,7 @@ use alloc::arc::Arc; use std::sync::atomic; use std::mem; -use std::rt::rtio::{EventLoop, IoFactory, RemoteCallback}; +use std::rt::rtio::{EventLoop, RemoteCallback}; use std::rt::rtio::{PausableIdleCallback, Callback}; use std::rt::exclusive::Exclusive; @@ -150,8 +150,6 @@ impl EventLoop for BasicLoop { Box } - fn io<'a>(&'a mut self) -> Option<&'a mut IoFactory> { None } - fn has_active_io(&self) -> bool { false } } diff --git a/src/libgreen/simple.rs b/src/libgreen/simple.rs index 6c33e7cc619..e26a099c028 100644 --- a/src/libgreen/simple.rs +++ b/src/libgreen/simple.rs @@ -16,7 +16,6 @@ use std::mem; use std::rt::Runtime; use std::rt::local::Local; use std::rt::mutex::NativeMutex; -use std::rt::rtio; use std::rt::task::{Task, BlockedTask, TaskOpts}; struct SimpleTask { @@ -79,9 +78,10 @@ impl Runtime for SimpleTask { _f: proc():Send) { panic!() } - fn local_io<'a>(&'a mut self) -> Option> { None } + fn stack_bounds(&self) -> (uint, uint) { panic!() } fn stack_guard(&self) -> Option { panic!() } + fn can_block(&self) -> bool { true } fn wrap(self: Box) -> Box { panic!() } } diff --git a/src/libgreen/task.rs b/src/libgreen/task.rs index 0c549fa66c1..428b6414412 100644 --- a/src/libgreen/task.rs +++ b/src/libgreen/task.rs @@ -24,7 +24,6 @@ use std::raw; use std::rt::Runtime; use std::rt::local::Local; use std::rt::mutex::NativeMutex; -use std::rt::rtio; use std::rt::stack; use std::rt::task::{Task, BlockedTask, TaskOpts}; use std::rt; @@ -468,14 +467,6 @@ impl Runtime for GreenTask { sched.run_task(me, sibling) } - // Local I/O is provided by the scheduler's event loop - fn local_io<'a>(&'a mut self) -> Option> { - match self.sched.as_mut().unwrap().event_loop.io() { - Some(io) => Some(rtio::LocalIo::new(io)), - None => None, - } - } - fn stack_bounds(&self) -> (uint, uint) { let c = self.coroutine.as_ref() .expect("GreenTask.stack_bounds called without a coroutine"); diff --git a/src/libnative/io/mod.rs b/src/libnative/io/mod.rs deleted file mode 100644 index 8c7751588ce..00000000000 --- a/src/libnative/io/mod.rs +++ /dev/null @@ -1,102 +0,0 @@ -// 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 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -//! Native thread-blocking I/O implementation -//! -//! This module contains the implementation of native thread-blocking -//! implementations of I/O on all platforms. This module is not intended to be -//! used directly, but rather the rust runtime will fall back to using it if -//! necessary. -//! -//! Rust code normally runs inside of green tasks with a local scheduler using -//! asynchronous I/O to cooperate among tasks. This model is not always -//! available, however, and that's where these native implementations come into -//! play. The only dependencies of these modules are the normal system libraries -//! that you would find on the respective platform. - -#![allow(non_snake_case)] - -use libc::{mod, c_int}; -use std::c_str::CString; -use std::os; -use std::rt::rtio::{mod, IoResult, IoError}; -use std::num; - -#[cfg(windows)] -#[path = "tty_windows.rs"] -mod tty; - -fn unimpl() -> IoError { - #[cfg(unix)] use libc::ENOSYS as ERROR; - #[cfg(windows)] use libc::ERROR_CALL_NOT_IMPLEMENTED as ERROR; - IoError { - code: ERROR as uint, - extra: 0, - detail: Some("not yet supported by the `native` runtime, maybe try `green`.".to_string()), - } -} - -fn last_error() -> IoError { - let errno = os::errno() as uint; - IoError { - code: os::errno() as uint, - extra: 0, - detail: Some(os::error_string(errno)), - } -} - -#[cfg(windows)] -#[inline] -fn retry (f: || -> I) -> I { f() } // PR rust-lang/rust/#17020 - -#[cfg(unix)] -#[inline] -fn retry> (f: || -> I) -> I { - let minus_one = -num::one::(); - loop { - let n = f(); - if n == minus_one && os::errno() == libc::EINTR as int { } - else { return n } - } -} - - -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; -} - -/// Implementation of rt::rtio's IoFactory trait to generate handles to the -/// native I/O functionality. -pub struct IoFactory { - _cannot_construct_outside_of_this_module: () -} - -impl IoFactory { - pub fn new() -> IoFactory { - IoFactory { _cannot_construct_outside_of_this_module: () } - } -} - -impl rtio::IoFactory for IoFactory { -} diff --git a/src/libnative/lib.rs b/src/libnative/lib.rs index c0ec4c16ab0..4e25feb9d75 100644 --- a/src/libnative/lib.rs +++ b/src/libnative/lib.rs @@ -74,7 +74,6 @@ use std::str; pub use task::NativeTaskBuilder; -pub mod io; pub mod task; #[cfg(any(windows, android))] diff --git a/src/libnative/task.rs b/src/libnative/task.rs index e702c12bdff..6d640b61b18 100644 --- a/src/libnative/task.rs +++ b/src/libnative/task.rs @@ -19,13 +19,11 @@ use std::mem; use std::rt::bookkeeping; use std::rt::local::Local; use std::rt::mutex::NativeMutex; -use std::rt::rtio; use std::rt::stack; use std::rt::task::{Task, BlockedTask, TaskOpts}; use std::rt::thread::Thread; use std::rt; -use io; use std::task::{TaskBuilder, Spawner}; /// Creates a new Task which is ready to execute as a 1:1 task. @@ -42,7 +40,6 @@ fn ops() -> Box { box Ops { lock: unsafe { NativeMutex::new() }, awoken: false, - io: io::IoFactory::new(), // these *should* get overwritten stack_bounds: (0, 0), stack_guard: 0 @@ -112,7 +109,6 @@ impl NativeTaskBuilder for TaskBuilder { struct Ops { lock: NativeMutex, // native synchronization awoken: bool, // used to prevent spurious wakeups - io: io::IoFactory, // local I/O factory // This field holds the known bounds of the stack in (lo, hi) form. Not all // native tasks necessarily know their precise bounds, hence this is @@ -272,10 +268,6 @@ impl rt::Runtime for Ops { NativeSpawner.spawn(opts, f); } - - fn local_io<'a>(&'a mut self) -> Option> { - Some(rtio::LocalIo::new(&mut self.io as &mut rtio::IoFactory)) - } } #[cfg(test)] diff --git a/src/librustrt/lib.rs b/src/librustrt/lib.rs index 9a8bd3cdfc8..fee748e29d9 100644 --- a/src/librustrt/lib.rs +++ b/src/librustrt/lib.rs @@ -90,7 +90,6 @@ pub trait Runtime { cur_task: Box, opts: TaskOpts, f: proc():Send); - fn local_io<'a>(&'a mut self) -> Option>; /// The (low, high) edges of the current stack. fn stack_bounds(&self) -> (uint, uint); // (lo, hi) /// The last writable byte of the stack next to the guard page diff --git a/src/librustrt/rtio.rs b/src/librustrt/rtio.rs index cdcefc2088e..86de8168189 100644 --- a/src/librustrt/rtio.rs +++ b/src/librustrt/rtio.rs @@ -12,12 +12,6 @@ use core::prelude::*; use alloc::boxed::Box; -use collections::string::String; -use core::mem; -use libc::c_int; - -use local::Local; -use task::Task; pub trait EventLoop { fn run(&mut self); @@ -27,8 +21,7 @@ pub trait EventLoop { fn remote_callback(&mut self, Box) -> Box; - /// The asynchronous I/O services. Not all event loops may provide one. - fn io<'a>(&'a mut self) -> Option<&'a mut IoFactory>; + // last vestige of IoFactory fn has_active_io(&self) -> bool; } @@ -46,128 +39,7 @@ pub trait RemoteCallback { fn fire(&mut self); } -pub struct LocalIo<'a> { - factory: &'a mut IoFactory+'a, -} - -#[unsafe_destructor] -impl<'a> Drop for LocalIo<'a> { - fn drop(&mut self) { - // FIXME(pcwalton): Do nothing here for now, but eventually we may want - // something. For now this serves to make `LocalIo` noncopyable. - } -} - -impl<'a> LocalIo<'a> { - /// Returns the local I/O: either the local scheduler's I/O services or - /// the native I/O services. - pub fn borrow() -> Option> { - // FIXME(#11053): bad - // - // This is currently very unsafely implemented. We don't actually - // *take* the local I/O so there's a very real possibility that we - // can have two borrows at once. Currently there is not a clear way - // to actually borrow the local I/O factory safely because even if - // ownership were transferred down to the functions that the I/O - // factory implements it's just too much of a pain to know when to - // relinquish ownership back into the local task (but that would be - // the safe way of implementing this function). - // - // In order to get around this, we just transmute a copy out of the task - // in order to have what is likely a static lifetime (bad). - let mut t: Box = match Local::try_take() { - Some(t) => t, - None => return None, - }; - let ret = t.local_io().map(|t| { - unsafe { mem::transmute_copy(&t) } - }); - Local::put(t); - return ret; - } - - pub fn maybe_raise(f: |io: &mut IoFactory| -> IoResult) - -> IoResult - { - #[cfg(unix)] use libc::EINVAL as ERROR; - #[cfg(windows)] use libc::ERROR_CALL_NOT_IMPLEMENTED as ERROR; - match LocalIo::borrow() { - Some(mut io) => f(io.get()), - None => Err(IoError { - code: ERROR as uint, - extra: 0, - detail: None, - }), - } - } - - pub fn new<'a>(io: &'a mut IoFactory+'a) -> LocalIo<'a> { - LocalIo { factory: io } - } - - /// Returns the underlying I/O factory as a trait reference. - #[inline] - pub fn get<'a>(&'a mut self) -> &'a mut IoFactory { - let f: &'a mut IoFactory = self.factory; - f - } -} - -pub trait IoFactory { - fn timer_init(&mut self) -> IoResult>; - fn tty_open(&mut self, fd: c_int, readable: bool) - -> IoResult>; -} - -pub trait RtioTimer { - fn sleep(&mut self, msecs: u64); - fn oneshot(&mut self, msecs: u64, cb: Box); - fn period(&mut self, msecs: u64, cb: Box); -} - -pub trait RtioPipe { - fn read(&mut self, buf: &mut [u8]) -> IoResult; - fn write(&mut self, buf: &[u8]) -> IoResult<()>; - fn clone(&self) -> Box; - - fn close_write(&mut self) -> IoResult<()>; - fn close_read(&mut self) -> IoResult<()>; - fn set_timeout(&mut self, timeout_ms: Option); - fn set_read_timeout(&mut self, timeout_ms: Option); - fn set_write_timeout(&mut self, timeout_ms: Option); -} - -pub trait RtioUnixListener { - fn listen(self: Box) -> IoResult>; -} - -pub trait RtioUnixAcceptor { - fn accept(&mut self) -> IoResult>; - fn set_timeout(&mut self, timeout: Option); - fn clone(&self) -> Box; - fn close_accept(&mut self) -> IoResult<()>; -} - -pub trait RtioTTY { - fn read(&mut self, buf: &mut [u8]) -> IoResult; - fn write(&mut self, buf: &[u8]) -> IoResult<()>; - fn set_raw(&mut self, raw: bool) -> IoResult<()>; - fn get_winsize(&mut self) -> IoResult<(int, int)>; - fn isatty(&self) -> bool; -} - pub trait PausableIdleCallback { fn pause(&mut self); fn resume(&mut self); } - -pub trait RtioSignal {} - -#[deriving(Show)] -pub struct IoError { - pub code: uint, - pub extra: uint, - pub detail: Option, -} - -pub type IoResult = Result; diff --git a/src/librustrt/task.rs b/src/librustrt/task.rs index ad3b1dc7e16..554e4784eac 100644 --- a/src/librustrt/task.rs +++ b/src/librustrt/task.rs @@ -26,7 +26,6 @@ use core::raw; use local_data; use Runtime; use local::Local; -use rtio::LocalIo; use unwind; use unwind::Unwinder; use collections::str::SendStr; @@ -421,13 +420,6 @@ impl Task { ops.maybe_yield(self); } - /// Acquires a handle to the I/O factory that this task contains, normally - /// stored in the task's runtime. This factory may not always be available, - /// which is why the return type is `Option` - pub fn local_io<'a>(&'a mut self) -> Option> { - self.imp.as_mut().unwrap().local_io() - } - /// Returns the stack bounds for this task in (lo, hi) format. The stack /// bounds may not be known for all tasks, so the return value may be /// `None`. diff --git a/src/libstd/io/mod.rs b/src/libstd/io/mod.rs index 03c073c1477..31eab4363d0 100644 --- a/src/libstd/io/mod.rs +++ b/src/libstd/io/mod.rs @@ -228,15 +228,15 @@ 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; use string::String; use uint; @@ -328,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/sys/unix/mod.rs b/src/libstd/sys/unix/mod.rs index 4bd1dd20163..d3f55d59534 100644 --- a/src/libstd/sys/unix/mod.rs +++ b/src/libstd/sys/unix/mod.rs @@ -9,6 +9,11 @@ // 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; -- cgit 1.4.1-3-g733a5