diff options
43 files changed, 2380 insertions, 644 deletions
diff --git a/.travis.yml b/.travis.yml index cf8a20cb..e08e05e4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,9 +16,7 @@ addons: - binutils-dev rust: - - 1.2.0 # Oldest supported version - - 1.7.0 - - 1.8.0 + - 1.7.0 # Oldest supported version - stable - beta - nightly @@ -48,12 +46,6 @@ matrix: - os: osx env: ARCH=i686 rust: stable - - os: osx - env: ARCH=x86_64 - rust: 1.2.0 - - os: osx - env: ARCH=i686 - rust: 1.2.0 # Docker builds for other targets - os: linux env: TARGET=aarch64-unknown-linux-gnu DOCKER_IMAGE=posborne/rust-cross:arm @@ -77,6 +69,9 @@ matrix: sudo: true allow_failures: - rust: nightly + # We need to upgrade the lowest supported version. However, the build + # infrastructure for arm/mips/android is not ready yet. + - rust: 1.7.0 - env: TARGET=mips-unknown-linux-gnu DOCKER_IMAGE=posborne/rust-cross:mips - env: TARGET=mipsel-unknown-linux-gnu DOCKER_IMAGE=posborne/rust-cross:mips - env: TARGET=arm-linux-androideabi DOCKER_IMAGE=posborne/rust-cross:android diff --git a/CHANGELOG.md b/CHANGELOG.md index 76f7ef5f..335098bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,11 +6,55 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added +- Added support for POSIX AIO + ([#483](https://github.com/nix-rust/nix/pull/483)) +- Added support for XNU system control sockets + ([#478](https://github.com/nix-rust/nix/pull/478)) +- Added support for `ioctl` calls on BSD platforms + ([#478](https://github.com/nix-rust/nix/pull/478)) +- Added struct `TimeSpec` + ([#475](https://github.com/nix-rust/nix/pull/475)) + ([#483](https://github.com/nix-rust/nix/pull/483)) +- Added complete definitions for all kqueue-related constants on all supported + OSes + ([#415](https://github.com/nix-rust/nix/pull/415)) - Added function `epoll_create1` and bitflags `EpollCreateFlags` in `::nix::sys::epoll` in order to support `::libc::epoll_create1`. ([#410](https://github.com/nix-rust/nix/pull/410)) +- Added `setresuid` and `setresgid` for Linux in `::nix::unistd` + ([#448](https://github.com/nix-rust/nix/pull/448)) +- Added `getpgid` in `::nix::unistd` + ([#433](https://github.com/nix-rust/nix/pull/433)) +- Added `tcgetpgrp` and `tcsetpgrp` in `::nix::unistd` + ([#451](https://github.com/nix-rust/nix/pull/451)) +- Added `CLONE_NEWCGROUP` in `::nix::sched` + ([#457](https://github.com/nix-rust/nix/pull/457)) +- Added `getpgrp` in `::nix::unistd` + ([#491](https://github.com/nix-rust/nix/pull/491)) +- Added `fchdir` in `::nix::unistd` + ([#497](https://github.com/nix-rust/nix/pull/497)) ### Changed +- `epoll_ctl` now could accept None as argument `event` + when op is `EpollOp::EpollCtlDel`. + ([#480](https://github.com/nix-rust/nix/pull/480)) +- Removed the `bad` keyword from the `ioctl!` macro + ([#478](https://github.com/nix-rust/nix/pull/478)) +- Changed `TimeVal` into an opaque Newtype + ([#475](https://github.com/nix-rust/nix/pull/475)) +- `kill`'s signature, defined in `::nix::sys::signal`, changed, so that the + signal parameter has type `T: Into<Option<Signal>>`. `None` as an argument + for that parameter will result in a 0 passed to libc's `kill`, while a + `Some`-argument will result in the previous behavior for the contained + `Signal`. + ([#445](https://github.com/nix-rust/nix/pull/445)) +- The minimum supported version of rustc is now 1.7.0. + ([#444](https://github.com/nix-rust/nix/pull/444)) +- Changed `KEvent` to an opaque structure that may only be modified by its + constructor and the `ev_set` method. + ([#415](https://github.com/nix-rust/nix/pull/415)) + ([#442](https://github.com/nix-rust/nix/pull/442)) + ([#463](https://github.com/nix-rust/nix/pull/463)) - `pipe2` now calls `libc::pipe2` where available. Previously it was emulated using `pipe`, which meant that setting `O_CLOEXEC` was not atomic. ([#427](https://github.com/nix-rust/nix/pull/427)) @@ -22,13 +66,29 @@ This project adheres to [Semantic Versioning](http://semver.org/). accessible with the new method `events()` of `EpollEvent`. Instances of `EpollEvent` can be constructed using the new method `new()` of EpollEvent. ([#410](https://github.com/nix-rust/nix/pull/410)) +- `SigFlags` in `::nix::sys::signal` has be renamed to `SigmaskHow` and its type + has changed from `bitflags` to `enum` in order to conform to our conventions. + ([#460](https://github.com/nix-rust/nix/pull/460)) +- `sethostname` now takes a `&str` instead of a `&[u8]` as this provides an API + that makes more sense in normal, correct usage of the API. +- `gethostname` previously did not expose the actual length of the hostname + written from the underlying system call at all. This has been updated to + return a `&CStr` within the provided buffer that is always properly + NUL-terminated (this is not guaranteed by the call with all platforms/libc + implementations). ### Fixed +- Fixed multiple issues with Unix domain sockets on non-Linux OSes + ([#474](https://github.com/nix-rust/nix/pull/415)) +- Fixed using kqueue with `EVFILT_USER` on FreeBSD + ([#415](https://github.com/nix-rust/nix/pull/415)) - Fixed the build on FreeBSD, and fixed the getsockopt, sendmsg, and recvmsg functions on that same OS. ([#397](https://github.com/nix-rust/nix/pull/397)) - Fixed an off-by-one bug in `UnixAddr::new_abstract` in `::nix::sys::socket`. ([#429](https://github.com/nix-rust/nix/pull/429)) +- Fixed clone passing a potentially unaligned stack. + ([#490](https://github.com/nix-rust/nix/pull/490)) ## [0.7.0] 2016-09-09 @@ -2,11 +2,12 @@ name = "nix" description = "Rust friendly bindings to *nix APIs" -version = "0.7.1-pre" -authors = ["Carl Lerche <me@carllerche.com>"] +version = "0.8.0" +authors = ["The nix-rust Project Developers"] homepage = "https://github.com/nix-rust/nix" repository = "https://github.com/nix-rust/nix" license = "MIT" +categories = ["os::unix-apis"] exclude = [ ".gitignore", ".travis.yml", @@ -14,8 +15,6 @@ exclude = [ "test/**/*" ] -build = "build.rs" - [features] eventfd = [] execvpe = [] @@ -28,10 +27,6 @@ bitflags = "0.4" cfg-if = "0.1.0" void = "1.0.2" -[build-dependencies] -rustc_version = "0.1.7" -semver = "0.1.20" # Old version for compatibility with rustc_version. - [dev-dependencies] rand = "0.3.8" tempdir = "0.3" @@ -3,7 +3,9 @@ [![Build Status](https://travis-ci.org/nix-rust/nix.svg?branch=master)](https://travis-ci.org/nix-rust/nix) [![crates.io](http://meritbadge.herokuapp.com/nix)](https://crates.io/crates/nix) -[Documentation](https://nix-rust.github.io/nix/nix/index.html) +[Documentation (Releases)](https://docs.rs/nix/) + +[Documentation (Development)](https://nix-rust.github.io/nix/nix/index.html) Nix seeks to provide friendly bindings to various *nix platform APIs (Linux, Darwin, ...). The goal is to not provide a 100% unified interface, but to unify diff --git a/RELEASE_PROCEDURE.md b/RELEASE_PROCEDURE.md index 14496f23..9502765c 100644 --- a/RELEASE_PROCEDURE.md +++ b/RELEASE_PROCEDURE.md @@ -34,8 +34,8 @@ After the release a commit with the following changes is added to the master branch. - Add a new Unreleased section header to CHANGELOG.md. -- In `Cargo.toml`, update the version to the next `-dev` version, e.g. - `v0.8.4-dev`. +- In `Cargo.toml`, update the version to the nextversion, e.g. + `v0.8.4`. - In `Cargo.tml`, revert the libc dependency to its git master branch. -- Commit with a message like "Bump to v0.8.4-dev" +- Commit with a message like "Bump to v0.8.4" - Make a pull request. diff --git a/build.rs b/build.rs deleted file mode 100644 index 084bb0d7..00000000 --- a/build.rs +++ /dev/null @@ -1,10 +0,0 @@ -extern crate rustc_version; -extern crate semver; - -use semver::Version; - -fn main() { - if rustc_version::version() >= Version::parse("1.6.0").unwrap() { - println!("cargo:rustc-cfg=raw_pointer_derive_allowed"); - } -} diff --git a/nix-test/src/sizes.c b/nix-test/src/sizes.c index 97a9b4a3..ca862003 100644 --- a/nix-test/src/sizes.c +++ b/nix-test/src/sizes.c @@ -1,5 +1,7 @@ -#include "sys/socket.h" -#include "sys/uio.h" +#include <sys/socket.h> +#include <sys/uio.h> + +#include <string.h> #define SIZE_OF_T(TYPE) \ do { \ @@ -77,8 +77,16 @@ use std::fmt; use std::error; use libc::PATH_MAX; +/// Nix Result Type pub type Result<T> = result::Result<T, Error>; +/// Nix Error Type +/// +/// The nix error type provides a common way of dealing with +/// various system system/libc calls that might fail. Each +/// error has a corresponding errno (usually the one from the +/// underlying OS) to which it can be mapped in addition to +/// implementing other common traits. #[derive(Clone, Copy, Debug, PartialEq)] pub enum Error { Sys(errno::Errno), @@ -86,18 +94,23 @@ pub enum Error { } impl Error { + + /// Create a nix Error from a given errno pub fn from_errno(errno: errno::Errno) -> Error { Error::Sys(errno) } + /// Get the current errno and convert it to a nix Error pub fn last() -> Error { Error::Sys(errno::Errno::last()) } + /// Create a new invalid argument error (`EINVAL`) pub fn invalid_argument() -> Error { Error::Sys(errno::EINVAL) } + /// Get the errno associated with this error pub fn errno(&self) -> errno::Errno { match *self { Error::Sys(errno) => errno, diff --git a/src/macros.rs b/src/macros.rs index e6d58b10..4954bed6 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -186,3 +186,11 @@ macro_rules! libc_bitflags { } }; } + +/// A Rust version of the familiar C `offset_of` macro. It returns the byte +/// offset of `field` within struct `ty` +macro_rules! offset_of { + ($ty:ty, $field:ident) => { + &(*(0 as *const $ty)).$field as *const _ as usize + } +} diff --git a/src/sched.rs b/src/sched.rs index 91a7c42a..66b4ed4f 100644 --- a/src/sched.rs +++ b/src/sched.rs @@ -24,6 +24,7 @@ libc_bitflags!{ CLONE_DETACHED, CLONE_UNTRACED, CLONE_CHILD_SETTID, + CLONE_NEWCGROUP, CLONE_NEWUTS, CLONE_NEWIPC, CLONE_NEWUSER, @@ -113,8 +114,9 @@ pub fn clone(mut cb: CloneCb, let res = unsafe { let combined = flags.bits() | signal.unwrap_or(0); let ptr = stack.as_mut_ptr().offset(stack.len() as isize); + let ptr_aligned = ptr.offset((ptr as usize % 16) as isize * -1); ffi::clone(mem::transmute(callback as extern "C" fn(*mut Box<::std::ops::FnMut() -> isize>) -> i32), - ptr as *mut c_void, + ptr_aligned as *mut c_void, combined, &mut cb) }; diff --git a/src/sys/aio.rs b/src/sys/aio.rs new file mode 100644 index 00000000..f0fce435 --- /dev/null +++ b/src/sys/aio.rs @@ -0,0 +1,249 @@ +use {Error, Errno, Result}; +use std::os::unix::io::RawFd; +use libc::{c_void, off_t, size_t}; +use libc; +use std::marker::PhantomData; +use std::mem; +use std::ptr::{null, null_mut}; +use sys::signal::*; +use sys::time::TimeSpec; + +/// Mode for `aio_fsync`. Controls whether only data or both data and metadata +/// are synced. +#[repr(i32)] +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum AioFsyncMode { + /// do it like `fsync` + O_SYNC = libc::O_SYNC, + /// on supported operating systems only, do it like `fdatasync` + #[cfg(any(target_os = "openbsd", target_os = "bitrig", + target_os = "netbsd", target_os = "macos", target_os = "ios", + target_os = "linux"))] + O_DSYNC = libc::O_DSYNC +} + +/// When used with `lio_listio`, determines whether a given `aiocb` should be +/// used for a read operation, a write operation, or ignored. Has no effect for +/// any other aio functions. +#[repr(i32)] +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum LioOpcode { + LIO_NOP = libc::LIO_NOP, + LIO_WRITE = libc::LIO_WRITE, + LIO_READ = libc::LIO_READ +} + +/// Mode for `lio_listio`. +#[repr(i32)] +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum LioMode { + /// Requests that `lio_listio` block until all requested operations have + /// been completed + LIO_WAIT = libc::LIO_WAIT, + /// Requests that `lio_listio` return immediately + LIO_NOWAIT = libc::LIO_NOWAIT, +} + +/// Return values for `aio_cancel` +#[repr(i32)] +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum AioCancelStat { + /// All outstanding requests were canceled + AioCanceled = libc::AIO_CANCELED, + /// Some requests were not canceled. Their status should be checked with + /// `aio_error` + AioNotCanceled = libc::AIO_NOTCANCELED, + /// All of the requests have already finished + AioAllDone = libc::AIO_ALLDONE, +} + +/// The basic structure used by all aio functions. Each `aiocb` represents one +/// I/O request. +#[repr(C)] +pub struct AioCb<'a> { + aiocb: libc::aiocb, + phantom: PhantomData<&'a mut [u8]> +} + +impl<'a> AioCb<'a> { + /// Constructs a new `AioCb` with no associated buffer. + /// + /// The resulting `AioCb` structure is suitable for use with `aio_fsync`. + /// * `fd` File descriptor. Required for all aio functions. + /// * `prio` If POSIX Prioritized IO is supported, then the operation will + /// be prioritized at the process's priority level minus `prio` + /// * `sigev_notify` Determines how you will be notified of event + /// completion. + pub fn from_fd(fd: RawFd, prio: ::c_int, + sigev_notify: SigevNotify) -> AioCb<'a> { + let mut a = AioCb::common_init(fd, prio, sigev_notify); + a.aio_offset = 0; + a.aio_nbytes = 0; + a.aio_buf = null_mut(); + + let aiocb = AioCb { aiocb: a, phantom: PhantomData}; + aiocb + } + + /// Constructs a new `AioCb`. + /// + /// * `fd` File descriptor. Required for all aio functions. + /// * `offs` File offset + /// * `buf` A memory buffer + /// * `prio` If POSIX Prioritized IO is supported, then the operation will + /// be prioritized at the process's priority level minus `prio` + /// * `sigev_notify` Determines how you will be notified of event + /// completion. + /// * `opcode` This field is only used for `lio_listio`. It determines + /// which operation to use for this individual aiocb + pub fn from_mut_slice(fd: RawFd, offs: off_t, buf: &'a mut [u8], + prio: ::c_int, sigev_notify: SigevNotify, + opcode: LioOpcode) -> AioCb { + let mut a = AioCb::common_init(fd, prio, sigev_notify); + a.aio_offset = offs; + a.aio_nbytes = buf.len() as size_t; + a.aio_buf = buf.as_ptr() as *mut c_void; + a.aio_lio_opcode = opcode as ::c_int; + + let aiocb = AioCb { aiocb: a, phantom: PhantomData}; + aiocb + } + + /// Like `from_mut_slice`, but works on constant slices rather than + /// mutable slices. + /// + /// This is technically unsafe, but in practice it's fine + /// to use with any aio functions except `aio_read` and `lio_listio` (with + /// `opcode` set to `LIO_READ`). This method is useful when writing a const + /// buffer with `aio_write`, since from_mut_slice can't work with const + /// buffers. + // Note: another solution to the problem of writing const buffers would be + // to genericize AioCb for both &mut [u8] and &[u8] buffers. aio_read could + // take the former and aio_write could take the latter. However, then + // lio_listio wouldn't work, because that function needs a slice of AioCb, + // and they must all be the same type. We're basically stuck with using an + // unsafe function, since aio (as designed in C) is an unsafe API. + pub unsafe fn from_slice(fd: RawFd, offs: off_t, buf: &'a [u8], + prio: ::c_int, sigev_notify: SigevNotify, + opcode: LioOpcode) -> AioCb { + let mut a = AioCb::common_init(fd, prio, sigev_notify); + a.aio_offset = offs; + a.aio_nbytes = buf.len() as size_t; + a.aio_buf = buf.as_ptr() as *mut c_void; + a.aio_lio_opcode = opcode as ::c_int; + + let aiocb = AioCb { aiocb: a, phantom: PhantomData}; + aiocb + } + + fn common_init(fd: RawFd, prio: ::c_int, + sigev_notify: SigevNotify) -> libc::aiocb { + // Use mem::zeroed instead of explicitly zeroing each field, because the + // number and name of reserved fields is OS-dependent. On some OSes, + // some reserved fields are used the kernel for state, and must be + // explicitly zeroed when allocated. + let mut a = unsafe { mem::zeroed::<libc::aiocb>()}; + a.aio_fildes = fd; + a.aio_reqprio = prio; + a.aio_sigevent = SigEvent::new(sigev_notify).sigevent(); + a + } + + /// Update the notification settings for an existing `aiocb` + pub fn set_sigev_notify(&mut self, sigev_notify: SigevNotify) { + self.aiocb.aio_sigevent = SigEvent::new(sigev_notify).sigevent(); + } +} + +/// Cancels outstanding AIO requests. If `aiocb` is `None`, then all requests +/// for `fd` will be cancelled. Otherwise, only the given `AioCb` will be +/// cancelled. +pub fn aio_cancel(fd: RawFd, aiocb: Option<&mut AioCb>) -> Result<AioCancelStat> { + let p: *mut libc::aiocb = match aiocb { + None => null_mut(), + Some(x) => &mut x.aiocb + }; + match unsafe { libc::aio_cancel(fd, p) } { + libc::AIO_CANCELED => Ok(AioCancelStat::AioCanceled), + libc::AIO_NOTCANCELED => Ok(AioCancelStat::AioNotCanceled), + libc::AIO_ALLDONE => Ok(AioCancelStat::AioAllDone), + -1 => Err(Error::last()), + _ => panic!("unknown aio_cancel return value") + } +} + +/// Retrieve error status of an asynchronous operation. If the request has not +/// yet completed, returns `EINPROGRESS`. Otherwise, returns `Ok` or any other +/// error. +pub fn aio_error(aiocb: &mut AioCb) -> Result<()> { + let p: *mut libc::aiocb = &mut aiocb.aiocb; + match unsafe { libc::aio_error(p) } { + 0 => Ok(()), + num if num > 0 => Err(Error::from_errno(Errno::from_i32(num))), + -1 => Err(Error::last()), + num => panic!("unknown aio_error return value {:?}", num) + } +} + +/// An asynchronous version of `fsync`. +pub fn aio_fsync(mode: AioFsyncMode, aiocb: &mut AioCb) -> Result<()> { + let p: *mut libc::aiocb = &mut aiocb.aiocb; + Errno::result(unsafe { libc::aio_fsync(mode as ::c_int, p) }).map(drop) +} + +/// Asynchronously reads from a file descriptor into a buffer +pub fn aio_read(aiocb: &mut AioCb) -> Result<()> { + let p: *mut libc::aiocb = &mut aiocb.aiocb; + Errno::result(unsafe { libc::aio_read(p) }).map(drop) +} + +/// Retrieve return status of an asynchronous operation. Should only be called +/// once for each `AioCb`, after `aio_error` indicates that it has completed. +/// The result the same as for `read`, `write`, of `fsync`. +pub fn aio_return(aiocb: &mut AioCb) -> Result<isize> { + let p: *mut libc::aiocb = &mut aiocb.aiocb; + Errno::result(unsafe { libc::aio_return(p) }) +} + +/// Suspends the calling process until at least one of the specified `AioCb`s +/// has completed, a signal is delivered, or the timeout has passed. If +/// `timeout` is `None`, `aio_suspend` will block indefinitely. +pub fn aio_suspend(list: &[&AioCb], timeout: Option<TimeSpec>) -> Result<()> { + // We must use transmute because Rust doesn't understand that a pointer to a + // Struct is the same as a pointer to its first element. + let plist = unsafe { + mem::transmute::<&[&AioCb], *const [*const libc::aiocb]>(list) + }; + let p = plist as *const *const libc::aiocb; + let timep = match timeout { + None => null::<libc::timespec>(), + Some(x) => x.as_ref() as *const libc::timespec + }; + Errno::result(unsafe { + libc::aio_suspend(p, list.len() as i32, timep) + }).map(drop) +} + +/// Asynchronously writes from a buffer to a file descriptor +pub fn aio_write(aiocb: &mut AioCb) -> Result<()> { + let p: *mut libc::aiocb = &mut aiocb.aiocb; + Errno::result(unsafe { libc::aio_write(p) }).map(drop) +} + +/// Submits multiple asynchronous I/O requests with a single system call. The +/// order in which the requests are carried out is not specified. +#[cfg(not(any(target_os = "ios", target_os = "macos")))] +pub fn lio_listio(mode: LioMode, list: &[&mut AioCb], + sigev_notify: SigevNotify) -> Result<()> { + let sigev = SigEvent::new(sigev_notify); + let sigevp = &mut sigev.sigevent() as *mut libc::sigevent; + // We must use transmute because Rust doesn't understand that a pointer to a + // Struct is the same as a pointer to its first element. + let plist = unsafe { + mem::transmute::<&[&mut AioCb], *const [*mut libc::aiocb]>(list) + }; + let p = plist as *const *mut libc::aiocb; + Errno::result(unsafe { + libc::lio_listio(mode as i32, p, list.len() as i32, sigevp) + }).map(drop) +} diff --git a/src/sys/epoll.rs b/src/sys/epoll.rs index 9774318f..eb28ffb9 100644 --- a/src/sys/epoll.rs +++ b/src/sys/epoll.rs @@ -1,6 +1,9 @@ use {Errno, Result}; use libc::{self, c_int}; use std::os::unix::io::RawFd; +use std::ptr; +use std::mem; +use ::Error; bitflags!( #[repr(C)] @@ -23,7 +26,7 @@ bitflags!( } ); -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Eq, PartialEq)] #[repr(C)] pub enum EpollOp { EpollCtlAdd = 1, @@ -44,10 +47,14 @@ pub struct EpollEvent { } impl EpollEvent { - pub fn new(events: EpollFlags, data: u64) -> EpollEvent { + pub fn new(events: EpollFlags, data: u64) -> Self { EpollEvent { event: libc::epoll_event { events: events.bits(), u64: data } } } + pub fn empty() -> Self { + unsafe { mem::zeroed::<EpollEvent>() } + } + pub fn events(&self) -> EpollFlags { EpollFlags::from_bits(self.event.events).unwrap() } @@ -57,6 +64,16 @@ impl EpollEvent { } } +impl<'a> Into<&'a mut EpollEvent> for Option<&'a mut EpollEvent> { + #[inline] + fn into(self) -> &'a mut EpollEvent { + match self { + Some(epoll_event) => epoll_event, + None => unsafe { &mut *ptr::null_mut::<EpollEvent>() } + } + } +} + #[inline] pub fn epoll_create() -> Result<RawFd> { let res = unsafe { libc::epoll_create(1024) }; @@ -72,10 +89,16 @@ pub fn epoll_create1(flags: EpollCreateFlags) -> Result<RawFd> { } #[inline] -pub fn epoll_ctl(epfd: RawFd, op: EpollOp, fd: RawFd, event: &mut EpollEvent) -> Result<()> { - let res = unsafe { libc::epoll_ctl(epfd, op as c_int, fd, &mut event.event) }; - - Errno::result(res).map(drop) +pub fn epoll_ctl<'a, T>(epfd: RawFd, op: EpollOp, fd: RawFd, event: T) -> Result<()> + where T: Into<&'a mut EpollEvent> +{ + let event: &mut EpollEvent = event.into(); + if event as *const EpollEvent == ptr::null() && op != EpollOp::EpollCtlDel { + Err(Error::Sys(Errno::EINVAL)) + } else { + let res = unsafe { libc::epoll_ctl(epfd, op as c_int, fd, &mut event.event) }; + Errno::result(res).map(drop) + } } #[inline] diff --git a/src/sys/event.rs b/src/sys/event.rs index 0e94475e..405f38fc 100644 --- a/src/sys/event.rs +++ b/src/sys/event.rs @@ -3,280 +3,247 @@ use {Errno, Result}; #[cfg(not(target_os = "netbsd"))] -use libc::{timespec, time_t, c_int, c_long, uintptr_t}; +use libc::{timespec, time_t, c_int, c_long, intptr_t, uintptr_t}; #[cfg(target_os = "netbsd")] -use libc::{timespec, time_t, c_long, uintptr_t, size_t}; +use libc::{timespec, time_t, c_long, intptr_t, uintptr_t, size_t}; +use libc; use std::os::unix::io::RawFd; use std::ptr; +use std::mem; -pub use self::ffi::kevent as KEvent; - -mod ffi { - pub use libc::{c_int, c_void, uintptr_t, intptr_t, timespec, size_t, int64_t}; - use super::{EventFilter, EventFlag, FilterFlag}; - - #[cfg(not(target_os = "netbsd"))] - #[derive(Clone, Copy)] - #[repr(C)] - pub struct kevent { - pub ident: uintptr_t, // 8 - pub filter: EventFilter, // 2 - pub flags: EventFlag, // 2 - pub fflags: FilterFlag, // 4 - pub data: intptr_t, // 8 - pub udata: usize // 8 - } - - #[cfg(target_os = "netbsd")] - #[derive(Clone, Copy)] - #[repr(C)] - pub struct kevent { - pub ident: uintptr_t, - pub filter: EventFilter, - pub flags: EventFlag, - pub fflags: FilterFlag, - pub data: int64_t, - pub udata: intptr_t - } - - // Bug in rustc, cannot determine that kevent is #[repr(C)] - #[allow(improper_ctypes)] - extern { - pub fn kqueue() -> c_int; - - #[cfg(not(target_os = "netbsd"))] - pub fn kevent( - kq: c_int, - changelist: *const kevent, - nchanges: c_int, - eventlist: *mut kevent, - nevents: c_int, - timeout: *const timespec) -> c_int; - - #[cfg(target_os = "netbsd")] - pub fn kevent( - kq: c_int, - changelist: *const kevent, - nchanges: size_t, - eventlist: *mut kevent, - nevents: size_t, - timeout: *const timespec) -> c_int; - } +// Redefine kevent in terms of programmer-friendly enums and bitfields. +#[derive(Clone, Copy)] +#[repr(C)] +pub struct KEvent { + kevent: libc::kevent, } -#[cfg(not(any(target_os = "dragonfly", target_os = "netbsd")))] -#[repr(i16)] -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum EventFilter { - EVFILT_READ = -1, - EVFILT_WRITE = -2, - EVFILT_AIO = -3, - EVFILT_VNODE = -4, - EVFILT_PROC = -5, - EVFILT_SIGNAL = -6, - EVFILT_TIMER = -7, - EVFILT_MACHPORT = -8, - EVFILT_FS = -9, - EVFILT_USER = -10, - // -11: unused - EVFILT_VM = -12, - EVFILT_SYSCOUNT = 13 -} +#[cfg(any(target_os = "openbsd", target_os = "freebsd", + target_os = "dragonfly", target_os = "macos", + target_os = "ios"))] +type type_of_udata = *mut ::c_void; +#[cfg(any(target_os = "netbsd"))] +type type_of_udata = intptr_t; -#[cfg(target_os = "dragonfly")] -#[repr(i16)] // u_short +#[cfg(not(target_os = "netbsd"))] +type type_of_event_filter = i16; +#[cfg(not(target_os = "netbsd"))] +#[repr(i16)] #[derive(Clone, Copy, Debug, PartialEq)] pub enum EventFilter { - EVFILT_READ = -1, - EVFILT_WRITE = -2, - EVFILT_AIO = -3, - EVFILT_VNODE = -4, - EVFILT_PROC = -5, - EVFILT_SIGNAL = -6, - EVFILT_TIMER = -7, - EVFILT_EXCEPT = -8, - EVFILT_USER = -9, + EVFILT_AIO = libc::EVFILT_AIO, + #[cfg(target_os = "dragonfly")] + EVFILT_EXCEPT = libc::EVFILT_EXCEPT, + #[cfg(any(target_os = "macos", target_os = "ios", + target_os = "dragonfly", + target_os = "freebsd"))] + EVFILT_FS = libc::EVFILT_FS, + #[cfg(target_os = "freebsd")] + EVFILT_LIO = libc::EVFILT_LIO, + #[cfg(any(target_os = "macos", target_os = "ios"))] + EVFILT_MACHPORT = libc::EVFILT_MACHPORT, + EVFILT_PROC = libc::EVFILT_PROC, + EVFILT_READ = libc::EVFILT_READ, + EVFILT_SIGNAL = libc::EVFILT_SIGNAL, + EVFILT_TIMER = libc::EVFILT_TIMER, + #[cfg(any(target_os = "macos", + target_os = "ios", + target_os = "dragonfly", + target_os = "freebsd"))] + EVFILT_USER = libc::EVFILT_USER, + #[cfg(any(target_os = "macos", target_os = "ios"))] + EVFILT_VM = libc::EVFILT_VM, + EVFILT_VNODE = libc::EVFILT_VNODE, + EVFILT_WRITE = libc::EVFILT_WRITE, } #[cfg(target_os = "netbsd")] +type type_of_event_filter = i32; +#[cfg(target_os = "netbsd")] #[repr(u32)] #[derive(Clone, Copy, Debug, PartialEq)] pub enum EventFilter { - EVFILT_READ = 0, - EVFILT_WRITE = 1, - EVFILT_AIO = 2, - EVFILT_VNODE = 3, - EVFILT_PROC = 4, - EVFILT_SIGNAL = 5, - EVFILT_TIMER = 6, - EVFILT_SYSCOUNT = 7 + EVFILT_READ = libc::EVFILT_READ, + EVFILT_WRITE = libc::EVFILT_WRITE, + EVFILT_AIO = libc::EVFILT_AIO, + EVFILT_VNODE = libc::EVFILT_VNODE, + EVFILT_PROC = libc::EVFILT_PROC, + EVFILT_SIGNAL = libc::EVFILT_SIGNAL, + EVFILT_TIMER = libc::EVFILT_TIMER, } -#[cfg(not(any(target_os = "dragonfly", target_os = "netbsd")))] -bitflags!( - flags EventFlag: u16 { - const EV_ADD = 0x0001, - const EV_DELETE = 0x0002, - const EV_ENABLE = 0x0004, - const EV_DISABLE = 0x0008, - const EV_RECEIPT = 0x0040, - const EV_ONESHOT = 0x0010, - const EV_CLEAR = 0x0020, - const EV_DISPATCH = 0x0080, - const EV_SYSFLAGS = 0xF000, - const EV_FLAG0 = 0x1000, - const EV_FLAG1 = 0x2000, - const EV_EOF = 0x8000, - const EV_ERROR = 0x4000 +#[cfg(any(target_os = "macos", target_os = "ios", + target_os = "freebsd", target_os = "dragonfly"))] +pub type type_of_event_flag = u16; +#[cfg(any(target_os = "netbsd", target_os = "openbsd"))] +pub type type_of_event_flag = u32; +libc_bitflags!{ + flags EventFlag: type_of_event_flag { + EV_ADD, + EV_CLEAR, + EV_DELETE, + EV_DISABLE, + EV_DISPATCH, + #[cfg(target_os = "freebsd")] + EV_DROP, + EV_ENABLE, + EV_EOF, + EV_ERROR, + #[cfg(any(target_os = "macos", target_os = "ios"))] + EV_FLAG0, + EV_FLAG1, + #[cfg(target_os = "dragonfly")] + EV_NODATA, + EV_ONESHOT, + #[cfg(any(target_os = "macos", target_os = "ios"))] + EV_OOBAND, + #[cfg(any(target_os = "macos", target_os = "ios"))] + EV_POLL, + #[cfg(not(target_os = "openbsd"))] + EV_RECEIPT, + EV_SYSFLAGS, } -); +} -#[cfg(target_os = "dragonfly")] bitflags!( - flags EventFlag: u16 { - const EV_ADD = 0x0001, - const EV_DELETE = 0x0002, - const EV_ENABLE = 0x0004, - const EV_DISABLE = 0x0008, - const EV_RECEIPT = 0x0040, - const EV_ONESHOT = 0x0010, - const EV_CLEAR = 0x0020, - const EV_SYSFLAGS = 0xF000, - const EV_NODATA = 0x1000, - const EV_FLAG1 = 0x2000, - const EV_EOF = 0x8000, - const EV_ERROR = 0x4000 + flags FilterFlag: u32 { + #[cfg(any(target_os = "macos", target_os = "ios"))] + const NOTE_ABSOLUTE = libc::NOTE_ABSOLUTE, + const NOTE_ATTRIB = libc::NOTE_ATTRIB, + const NOTE_CHILD = libc::NOTE_CHILD, + const NOTE_DELETE = libc::NOTE_DELETE, + #[cfg(target_os = "openbsd")] + const NOTE_EOF = libc::NOTE_EOF, + const NOTE_EXEC = libc::NOTE_EXEC, + const NOTE_EXIT = libc::NOTE_EXIT, + #[cfg(any(target_os = "macos", target_os = "ios"))] + const NOTE_EXIT_REPARENTED = libc::NOTE_EXIT_REPARENTED, + #[cfg(any(target_os = "macos", target_os = "ios"))] + const NOTE_EXITSTATUS = libc::NOTE_EXITSTATUS, + const NOTE_EXTEND = libc::NOTE_EXTEND, + #[cfg(any(target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "dragonfly"))] + const NOTE_FFAND = libc::NOTE_FFAND, + #[cfg(any(target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "dragonfly"))] + const NOTE_FFCOPY = libc::NOTE_FFCOPY, + #[cfg(any(target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "dragonfly"))] + const NOTE_FFCTRLMASK = libc::NOTE_FFCTRLMASK, + #[cfg(any(target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "dragonfly"))] + const NOTE_FFLAGSMASK = libc::NOTE_FFLAGSMASK, + #[cfg(any(target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "dragonfly"))] + const NOTE_FFNOP = libc::NOTE_FFNOP, + #[cfg(any(target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "dragonfly"))] + const NOTE_FFOR = libc::NOTE_FFOR, + const NOTE_FORK = libc::NOTE_FORK, + const NOTE_LINK = libc::NOTE_LINK, + const NOTE_LOWAT = libc::NOTE_LOWAT, + #[cfg(target_os = "freebsd")] + const NOTE_MSECONDS = libc::NOTE_MSECONDS, + #[cfg(any(target_os = "macos", target_os = "ios"))] + const NOTE_NONE = libc::NOTE_NONE, + #[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd"))] + const NOTE_NSECONDS = libc::NOTE_NSECONDS, + #[cfg(target_os = "dragonfly")] + const NOTE_OOB = libc::NOTE_OOB, + const NOTE_PCTRLMASK = libc::NOTE_PCTRLMASK, + const NOTE_PDATAMASK = libc::NOTE_PDATAMASK, + #[cfg(any(target_os = "macos", target_os = "ios"))] + const NOTE_REAP = libc::NOTE_REAP, + const NOTE_RENAME = libc::NOTE_RENAME, + const NOTE_REVOKE = libc::NOTE_REVOKE, + #[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd"))] + const NOTE_SECONDS = libc::NOTE_SECONDS, + #[cfg(any(target_os = "macos", target_os = "ios"))] + const NOTE_SIGNAL = libc::NOTE_SIGNAL, + const NOTE_TRACK = libc::NOTE_TRACK, + const NOTE_TRACKERR = libc::NOTE_TRACKERR, + #[cfg(any(target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "dragonfly"))] + const NOTE_TRIGGER = libc::NOTE_TRIGGER, + #[cfg(target_os = "openbsd")] + const NOTE_TRUNCATE = libc::NOTE_TRUNCATE, + #[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd"))] + const NOTE_USECONDS = libc::NOTE_USECONDS, + #[cfg(any(target_os = "macos", target_os = "ios"))] + const NOTE_VM_ERROR = libc::NOTE_VM_ERROR, + #[cfg(any(target_os = "macos", target_os = "ios"))] + const NOTE_VM_PRESSURE = libc::NOTE_VM_PRESSURE, + #[cfg(any(target_os = "macos", target_os = "ios"))] + const NOTE_VM_PRESSURE_SUDDEN_TERMINATE = libc::NOTE_VM_PRESSURE_SUDDEN_TERMINATE, + #[cfg(any(target_os = "macos", target_os = "ios"))] + const NOTE_VM_PRESSURE_TERMINATE = libc::NOTE_VM_PRESSURE_TERMINATE, + const NOTE_WRITE = libc::NOTE_WRITE, } ); -#[cfg(target_os = "netbsd")] -bitflags!( - flags EventFlag: u32 { - const EV_ADD = 0x0001, - const EV_DELETE = 0x0002, - const EV_ENABLE = 0x0004, - const EV_DISABLE = 0x0008, - const EV_ONESHOT = 0x0010, - const EV_CLEAR = 0x0020, - const EV_SYSFLAGS = 0xF000, - const EV_NODATA = 0x1000, - const EV_FLAG1 = 0x2000, - const EV_EOF = 0x8000, - const EV_ERROR = 0x4000 - } -); +pub fn kqueue() -> Result<RawFd> { + let res = unsafe { libc::kqueue() }; -#[cfg(not(any(target_os = "dragonfly", target_os="netbsd")))] -bitflags!( - flags FilterFlag: u32 { - const NOTE_TRIGGER = 0x01000000, - const NOTE_FFNOP = 0x00000000, - const NOTE_FFAND = 0x40000000, - const NOTE_FFOR = 0x80000000, - const NOTE_FFCOPY = 0xc0000000, - const NOTE_FFCTRLMASK = 0xc0000000, - const NOTE_FFLAGSMASK = 0x00ffffff, - const NOTE_LOWAT = 0x00000001, - const NOTE_DELETE = 0x00000001, - const NOTE_WRITE = 0x00000002, - const NOTE_EXTEND = 0x00000004, - const NOTE_ATTRIB = 0x00000008, - const NOTE_LINK = 0x00000010, - const NOTE_RENAME = 0x00000020, - const NOTE_REVOKE = 0x00000040, - const NOTE_NONE = 0x00000080, - const NOTE_EXIT = 0x80000000, - const NOTE_FORK = 0x40000000, - const NOTE_EXEC = 0x20000000, - const NOTE_REAP = 0x10000000, - const NOTE_SIGNAL = 0x08000000, - const NOTE_EXITSTATUS = 0x04000000, - const NOTE_RESOURCEEND = 0x02000000, - const NOTE_APPACTIVE = 0x00800000, - const NOTE_APPBACKGROUND = 0x00400000, - const NOTE_APPNONUI = 0x00200000, - const NOTE_APPINACTIVE = 0x00100000, - const NOTE_APPALLSTATES = 0x00f00000, - const NOTE_PDATAMASK = 0x000fffff, - const NOTE_PCTRLMASK = 0xfff00000, - const NOTE_EXIT_REPARENTED = 0x00080000, - const NOTE_VM_PRESSURE = 0x80000000, - const NOTE_VM_PRESSURE_TERMINATE = 0x40000000, - const NOTE_VM_PRESSURE_SUDDEN_TERMINATE = 0x20000000, - const NOTE_VM_ERROR = 0x10000000, - const NOTE_SECONDS = 0x00000001, - const NOTE_USECONDS = 0x00000002, - const NOTE_NSECONDS = 0x00000004, - const NOTE_ABSOLUTE = 0x00000008, - const NOTE_TRACK = 0x00000001, - const NOTE_TRACKERR = 0x00000002, - const NOTE_CHILD = 0x00000004 + Errno::result(res) +} + + +// KEvent can't derive Send because on some operating systems, udata is defined +// as a void*. However, KEvent's public API always treats udata as an intptr_t, +// which is safe to Send. +unsafe impl Send for KEvent { +} + +impl KEvent { + pub fn new(ident: uintptr_t, filter: EventFilter, flags: EventFlag, + fflags:FilterFlag, data: intptr_t, udata: intptr_t) -> KEvent { + KEvent { kevent: libc::kevent { + ident: ident, + filter: filter as type_of_event_filter, + flags: flags.bits(), + fflags: fflags.bits(), + data: data, + udata: udata as type_of_udata + } } } -); -#[cfg(target_os = "dragonfly")] -bitflags!( - flags FilterFlag: u32 { - const NOTE_TRIGGER = 0x01000000, - const NOTE_FFNOP = 0x00000000, - const NOTE_FFAND = 0x40000000, - const NOTE_FFOR = 0x80000000, - const NOTE_FFCOPY = 0xc0000000, - const NOTE_FFCTRLMASK = 0xc0000000, - const NOTE_FFLAGSMASK = 0x00ffffff, - const NOTE_LOWAT = 0x00000001, - const NOTE_DELETE = 0x00000001, - const NOTE_WRITE = 0x00000002, - const NOTE_EXTEND = 0x00000004, - const NOTE_ATTRIB = 0x00000008, - const NOTE_LINK = 0x00000010, - const NOTE_RENAME = 0x00000020, - const NOTE_REVOKE = 0x00000040, - const NOTE_EXIT = 0x80000000, - const NOTE_FORK = 0x40000000, - const NOTE_EXEC = 0x20000000, - const NOTE_SIGNAL = 0x08000000, - const NOTE_PDATAMASK = 0x000fffff, - const NOTE_PCTRLMASK = 0xf0000000, // NOTE: FreeBSD uses 0xfff00000, - const NOTE_TRACK = 0x00000001, - const NOTE_TRACKERR = 0x00000002, - const NOTE_CHILD = 0x00000004 + pub fn ident(&self) -> uintptr_t { + self.kevent.ident } -); -#[cfg(target_os = "netbsd")] -bitflags!( - flags FilterFlag: u32 { - const NOTE_LOWAT = 0x00000001, - const NOTE_DELETE = 0x00000001, - const NOTE_WRITE = 0x00000002, - const NOTE_EXTEND = 0x00000004, - const NOTE_ATTRIB = 0x00000008, - const NOTE_LINK = 0x00000010, - const NOTE_RENAME = 0x00000020, - const NOTE_REVOKE = 0x00000040, - const NOTE_EXIT = 0x80000000, - const NOTE_FORK = 0x40000000, - const NOTE_EXEC = 0x20000000, - const NOTE_SIGNAL = 0x08000000, - const NOTE_PDATAMASK = 0x000fffff, - const NOTE_PCTRLMASK = 0xf0000000, // NOTE: FreeBSD uses 0xfff00000, - const NOTE_TRACK = 0x00000001, - const NOTE_TRACKERR = 0x00000002, - const NOTE_CHILD = 0x00000004 + pub fn filter(&self) -> EventFilter { + unsafe { mem::transmute(self.kevent.filter as type_of_event_filter) } } -); -#[cfg(not(any(target_os = "dragonfly", target_os = "netbsd")))] -pub const EV_POLL: EventFlag = EV_FLAG0; + pub fn flags(&self) -> EventFlag { + EventFlag::from_bits(self.kevent.flags).unwrap() + } -#[cfg(not(any(target_os = "dragonfly", target_os = "netbsd")))] -pub const EV_OOBAND: EventFlag = EV_FLAG1; + pub fn fflags(&self) -> FilterFlag { + FilterFlag::from_bits(self.kevent.fflags).unwrap() + } -pub fn kqueue() -> Result<RawFd> { - let res = unsafe { ffi::kqueue() }; + pub fn data(&self) -> intptr_t { + self.kevent.data + } - Errno::result(res) + pub fn udata(&self) -> intptr_t { + self.kevent.udata as intptr_t + } } pub fn kevent(kq: RawFd, @@ -293,74 +260,70 @@ pub fn kevent(kq: RawFd, kevent_ts(kq, changelist, eventlist, Some(timeout)) } -#[cfg(not(target_os = "netbsd"))] -pub fn kevent_ts(kq: RawFd, - changelist: &[KEvent], - eventlist: &mut [KEvent], - timeout_opt: Option<timespec>) -> Result<usize> { - - let res = unsafe { - ffi::kevent( - kq, - changelist.as_ptr(), - changelist.len() as c_int, - eventlist.as_mut_ptr(), - eventlist.len() as c_int, - if let Some(ref timeout) = timeout_opt {timeout as *const timespec} else {ptr::null()}) - }; - - Errno::result(res).map(|r| r as usize) -} - +#[cfg(any(target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "openbsd"))] +type type_of_nchanges = c_int; #[cfg(target_os = "netbsd")] +type type_of_nchanges = size_t; + pub fn kevent_ts(kq: RawFd, changelist: &[KEvent], eventlist: &mut [KEvent], timeout_opt: Option<timespec>) -> Result<usize> { let res = unsafe { - ffi::kevent( + libc::kevent( kq, - changelist.as_ptr(), - changelist.len() as size_t, - eventlist.as_mut_ptr(), - eventlist.len() as size_t, + changelist.as_ptr() as *const libc::kevent, + changelist.len() as type_of_nchanges, + eventlist.as_mut_ptr() as *mut libc::kevent, + eventlist.len() as type_of_nchanges, if let Some(ref timeout) = timeout_opt {timeout as *const timespec} else {ptr::null()}) }; Errno::result(res).map(|r| r as usize) } -#[cfg(not(target_os = "netbsd"))] #[inline] pub fn ev_set(ev: &mut KEvent, ident: usize, filter: EventFilter, flags: EventFlag, fflags: FilterFlag, - udata: usize) { - - ev.ident = ident as uintptr_t; - ev.filter = filter; - ev.flags = flags; - ev.fflags = fflags; - ev.data = 0; - ev.udata = udata; + udata: intptr_t) { + + ev.kevent.ident = ident as uintptr_t; + ev.kevent.filter = filter as type_of_event_filter; + ev.kevent.flags = flags.bits(); + ev.kevent.fflags = fflags.bits(); + ev.kevent.data = 0; + ev.kevent.udata = udata as type_of_udata; } -#[cfg(target_os = "netbsd")] -#[inline] -pub fn ev_set(ev: &mut KEvent, - ident: usize, - filter: EventFilter, - flags: EventFlag, - fflags: FilterFlag, - udata: isize) { - - ev.ident = ident as uintptr_t; - ev.filter = filter; - ev.flags = flags; - ev.fflags = fflags; - ev.data = 0; - ev.udata = udata; +#[test] +fn test_struct_kevent() { + let udata : intptr_t = 12345; + + let expected = libc::kevent{ident: 0xdeadbeef, + filter: libc::EVFILT_READ, + flags: libc::EV_DISPATCH | libc::EV_ADD, + fflags: libc::NOTE_CHILD | libc::NOTE_EXIT, + data: 0x1337, + udata: udata as type_of_udata}; + let actual = KEvent::new(0xdeadbeef, + EventFilter::EVFILT_READ, + EV_DISPATCH | EV_ADD, + NOTE_CHILD | NOTE_EXIT, + 0x1337, + udata); + assert!(expected.ident == actual.ident()); + assert!(expected.filter == actual.filter() as type_of_event_filter); + assert!(expected.flags == actual.flags().bits()); + assert!(expected.fflags == actual.fflags().bits()); + assert!(expected.data == actual.data()); + assert!(expected.udata == actual.udata() as type_of_udata); + assert!(mem::size_of::<libc::kevent>() == mem::size_of::<KEvent>()); } diff --git a/src/sys/eventfd.rs b/src/sys/eventfd.rs index e6e410ec..8058e207 100644 --- a/src/sys/eventfd.rs +++ b/src/sys/eventfd.rs @@ -4,9 +4,9 @@ use {Errno, Result}; libc_bitflags! { flags EfdFlags: libc::c_int { - const EFD_CLOEXEC, // Since Linux 2.6.27 - const EFD_NONBLOCK, // Since Linux 2.6.27 - const EFD_SEMAPHORE, // Since Linux 2.6.30 + EFD_CLOEXEC, // Since Linux 2.6.27 + EFD_NONBLOCK, // Since Linux 2.6.27 + EFD_SEMAPHORE, // Since Linux 2.6.30 } } diff --git a/src/sys/ioctl/mod.rs b/src/sys/ioctl/mod.rs index 4d4d1072..a04e9d39 100644 --- a/src/sys/ioctl/mod.rs +++ b/src/sys/ioctl/mod.rs @@ -90,7 +90,7 @@ //! How do I get the magic numbers? //! =============================== //! -//! For Linux, look at your system's headers. For example, `/usr/include/linxu/input.h` has a lot +//! For Linux, look at your system's headers. For example, `/usr/include/linux/input.h` has a lot //! of lines defining macros which use `_IOR`, `_IOW`, `_IOC`, and `_IORW`. These macros //! correspond to the `ior!`, `iow!`, `ioc!`, and `iorw!` macros defined in this crate. //! Additionally, there is the `ioctl!` macro for creating a wrapper around `ioctl` that is @@ -104,33 +104,13 @@ #[macro_use] mod platform; -#[cfg(target_os = "macos")] -#[path = "platform/macos.rs"] -#[macro_use] -mod platform; - -#[cfg(target_os = "ios")] -#[path = "platform/ios.rs"] -#[macro_use] -mod platform; - -#[cfg(target_os = "freebsd")] -#[path = "platform/freebsd.rs"] -#[macro_use] -mod platform; - -#[cfg(target_os = "netbsd")] -#[path = "platform/netbsd.rs"] -#[macro_use] -mod platform; - -#[cfg(target_os = "openbsd")] -#[path = "platform/openbsd.rs"] -#[macro_use] -mod platform; - -#[cfg(target_os = "dragonfly")] -#[path = "platform/dragonfly.rs"] +#[cfg(any(target_os = "macos", + target_os = "ios", + target_os = "netbsd", + target_os = "openbsd", + target_os = "freebsd", + target_os = "dragonfly"))] +#[path = "platform/bsd.rs"] #[macro_use] mod platform; @@ -145,3 +125,74 @@ extern "C" { /// A hack to get the macros to work nicely. #[doc(hidden)] pub use ::libc as libc; + +/// Convert raw ioctl return value to a Nix result +#[macro_export] +macro_rules! convert_ioctl_res { + ($w:expr) => ( + { + $crate::Errno::result($w) + } + ); +} + +#[macro_export] +macro_rules! ioctl { + ($name:ident with $nr:expr) => ( + pub unsafe fn $name(fd: $crate::sys::ioctl::libc::c_int, + data: *mut u8) + -> $crate::Result<$crate::sys::ioctl::libc::c_int> { + convert_ioctl_res!($crate::sys::ioctl::ioctl(fd, $nr as $crate::sys::ioctl::libc::c_ulong, data)) + } + ); + (none $name:ident with $ioty:expr, $nr:expr) => ( + pub unsafe fn $name(fd: $crate::sys::ioctl::libc::c_int) + -> $crate::Result<$crate::sys::ioctl::libc::c_int> { + convert_ioctl_res!($crate::sys::ioctl::ioctl(fd, io!($ioty, $nr) as $crate::sys::ioctl::libc::c_ulong)) + } + ); + (read $name:ident with $ioty:expr, $nr:expr; $ty:ty) => ( + pub unsafe fn $name(fd: $crate::sys::ioctl::libc::c_int, + val: *mut $ty) + -> $crate::Result<$crate::sys::ioctl::libc::c_int> { + convert_ioctl_res!($crate::sys::ioctl::ioctl(fd, ior!($ioty, $nr, ::std::mem::size_of::<$ty>()) as $crate::sys::ioctl::libc::c_ulong, val)) + } + ); + (write $name:ident with $ioty:expr, $nr:expr; $ty:ty) => ( + pub unsafe fn $name(fd: $crate::sys::ioctl::libc::c_int, + val: *const $ty) + -> $crate::Result<$crate::sys::ioctl::libc::c_int> { + convert_ioctl_res!($crate::sys::ioctl::ioctl(fd, iow!($ioty, $nr, ::std::mem::size_of::<$ty>()) as $crate::sys::ioctl::libc::c_ulong, val)) + } + ); + (readwrite $name:ident with $ioty:expr, $nr:expr; $ty:ty) => ( + pub unsafe fn $name(fd: $crate::sys::ioctl::libc::c_int, + val: *mut $ty) + -> $crate::Result<$crate::sys::ioctl::libc::c_int> { + convert_ioctl_res!($crate::sys::ioctl::ioctl(fd, iorw!($ioty, $nr, ::std::mem::size_of::<$ty>()) as $crate::sys::ioctl::libc::c_ulong, val)) + } + ); + (read buf $name:ident with $ioty:expr, $nr:expr; $ty:ty) => ( + pub unsafe fn $name(fd: $crate::sys::ioctl::libc::c_int, + val: *mut $ty, + len: usize) + -> $crate::Result<$crate::sys::ioctl::libc::c_int> { + convert_ioctl_res!($crate::sys::ioctl::ioctl(fd, ior!($ioty, $nr, len) as $crate::sys::ioctl::libc::c_ulong, val)) + } + ); + (write buf $name:ident with $ioty:expr, $nr:expr; $ty:ty) => ( + pub unsafe fn $name(fd: $crate::sys::ioctl::libc::c_int, + val: *const $ty, + len: usize) -> $crate::Result<$crate::sys::ioctl::libc::c_int> { + convert_ioctl_res!($crate::sys::ioctl::ioctl(fd, iow!($ioty, $nr, len) as $crate::sys::ioctl::libc::c_ulong, val)) + } + ); + (readwrite buf $name:ident with $ioty:expr, $nr:expr; $ty:ty) => ( + pub unsafe fn $name(fd: $crate::sys::ioctl::libc::c_int, + val: *mut $ty, + len: usize) + -> $crate::Result<$crate::sys::ioctl::libc::c_int> { + convert_ioctl_res!($crate::sys::ioctl::ioctl(fd, iorw!($ioty, $nr, len) as $crate::sys::ioctl::libc::c_ulong, val)) + } + ); +} diff --git a/src/sys/ioctl/platform/bsd.rs b/src/sys/ioctl/platform/bsd.rs new file mode 100644 index 00000000..57b4d637 --- /dev/null +++ b/src/sys/ioctl/platform/bsd.rs @@ -0,0 +1,36 @@ +mod consts { + pub const VOID: u32 = 0x20000000; + pub const OUT: u32 = 0x40000000; + pub const IN: u32 = 0x80000000; + pub const INOUT: u32 = (IN|OUT); + pub const IOCPARM_MASK: u32 = 0x1fff; +} + +pub use self::consts::*; + +#[macro_export] +macro_rules! ioc { + ($inout:expr, $group:expr, $num:expr, $len:expr) => ( + $inout | (($len as u32 & $crate::sys::ioctl::IOCPARM_MASK) << 16) | (($group as u32) << 8) | ($num as u32) + ) +} + +#[macro_export] +macro_rules! io { + ($g:expr, $n:expr) => (ioc!($crate::sys::ioctl::VOID, $g, $n, 0)) +} + +#[macro_export] +macro_rules! ior { + ($g:expr, $n:expr, $len:expr) => (ioc!($crate::sys::ioctl::OUT, $g, $n, $len)) +} + +#[macro_export] +macro_rules! iow { + ($g:expr, $n:expr, $len:expr) => (ioc!($crate::sys::ioctl::IN, $g, $n, $len)) +} + +#[macro_export] +macro_rules! iorw { + ($g:expr, $n:expr, $len:expr) => (ioc!($crate::sys::ioctl::INOUT, $g, $n, $len)) +} diff --git a/src/sys/ioctl/platform/dragonfly.rs b/src/sys/ioctl/platform/dragonfly.rs deleted file mode 100644 index e69de29b..00000000 --- a/src/sys/ioctl/platform/dragonfly.rs +++ /dev/null diff --git a/src/sys/ioctl/platform/freebsd.rs b/src/sys/ioctl/platform/freebsd.rs deleted file mode 100644 index e69de29b..00000000 --- a/src/sys/ioctl/platform/freebsd.rs +++ /dev/null diff --git a/src/sys/ioctl/platform/ios.rs b/src/sys/ioctl/platform/ios.rs deleted file mode 100644 index e69de29b..00000000 --- a/src/sys/ioctl/platform/ios.rs +++ /dev/null diff --git a/src/sys/ioctl/platform/linux.rs b/src/sys/ioctl/platform/linux.rs index 60311224..aacea459 100644 --- a/src/sys/ioctl/platform/linux.rs +++ b/src/sys/ioctl/platform/linux.rs @@ -77,78 +77,6 @@ macro_rules! iorw { ($ty:expr, $nr:expr, $sz:expr) => (ioc!($crate::sys::ioctl::READ | $crate::sys::ioctl::WRITE, $ty, $nr, $sz)) } -/// Convert raw ioctl return value to a Nix result -#[macro_export] -macro_rules! convert_ioctl_res { - ($w:expr) => ( - { - $crate::Errno::result($w) - } - ); -} - -/// Declare a wrapper function around an ioctl. -#[macro_export] -macro_rules! ioctl { - (bad $name:ident with $nr:expr) => ( - pub unsafe fn $name(fd: $crate::sys::ioctl::libc::c_int, - data: *mut u8) - -> $crate::Result<$crate::sys::ioctl::libc::c_int> { - convert_ioctl_res!($crate::sys::ioctl::ioctl(fd, $nr as $crate::sys::ioctl::libc::c_ulong, data)) - } - ); - (none $name:ident with $ioty:expr, $nr:expr) => ( - pub unsafe fn $name(fd: $crate::sys::ioctl::libc::c_int) - -> $crate::Result<$crate::sys::ioctl::libc::c_int> { - convert_ioctl_res!($crate::sys::ioctl::ioctl(fd, io!($ioty, $nr) as $crate::sys::ioctl::libc::c_ulong)) - } - ); - (read $name:ident with $ioty:expr, $nr:expr; $ty:ty) => ( - pub unsafe fn $name(fd: $crate::sys::ioctl::libc::c_int, - val: *mut $ty) - -> $crate::Result<$crate::sys::ioctl::libc::c_int> { - convert_ioctl_res!($crate::sys::ioctl::ioctl(fd, ior!($ioty, $nr, ::std::mem::size_of::<$ty>()) as $crate::sys::ioctl::libc::c_ulong, val)) - } - ); - (write $name:ident with $ioty:expr, $nr:expr; $ty:ty) => ( - pub unsafe fn $name(fd: $crate::sys::ioctl::libc::c_int, - val: *const $ty) - -> $crate::Result<$crate::sys::ioctl::libc::c_int> { - convert_ioctl_res!($crate::sys::ioctl::ioctl(fd, iow!($ioty, $nr, ::std::mem::size_of::<$ty>()) as $crate::sys::ioctl::libc::c_ulong, val)) - } - ); - (readwrite $name:ident with $ioty:expr, $nr:expr; $ty:ty) => ( - pub unsafe fn $name(fd: $crate::sys::ioctl::libc::c_int, - val: *mut $ty) - -> $crate::Result<$crate::sys::ioctl::libc::c_int> { - convert_ioctl_res!($crate::sys::ioctl::ioctl(fd, iorw!($ioty, $nr, ::std::mem::size_of::<$ty>()) as $crate::sys::ioctl::libc::c_ulong, val)) - } - ); - (read buf $name:ident with $ioty:expr, $nr:expr; $ty:ty) => ( - pub unsafe fn $name(fd: $crate::sys::ioctl::libc::c_int, - val: *mut $ty, - len: usize) - -> $crate::Result<$crate::sys::ioctl::libc::c_int> { - convert_ioctl_res!($crate::sys::ioctl::ioctl(fd, ior!($ioty, $nr, len) as $crate::sys::ioctl::libc::c_ulong, val)) - } - ); - (write buf $name:ident with $ioty:expr, $nr:expr; $ty:ty) => ( - pub unsafe fn $name(fd: $crate::sys::ioctl::libc::c_int, - val: *const $ty, - len: usize) -> $crate::Result<$crate::sys::ioctl::libc::c_int> { - convert_ioctl_res!($crate::sys::ioctl::ioctl(fd, iow!($ioty, $nr, len) as $crate::sys::ioctl::libc::c_ulong, val)) - } - ); - (readwrite buf $name:ident with $ioty:expr, $nr:expr; $ty:ty) => ( - pub unsafe fn $name(fd: $crate::sys::ioctl::libc::c_int, - val: *const $ty, - len: usize) - -> $crate::Result<$crate::sys::ioctl::libc::c_int> { - convert_ioctl_res!($crate::sys::ioctl::ioctl(fd, iorw!($ioty, $nr, len) as $crate::sys::ioctl::libc::c_ulong, val)) - } - ); -} - /// Extracts the "direction" (read/write/none) from an encoded ioctl command. #[inline(always)] pub fn ioc_dir(nr: u32) -> u8 { diff --git a/src/sys/ioctl/platform/macos.rs b/src/sys/ioctl/platform/macos.rs deleted file mode 100644 index e69de29b..00000000 --- a/src/sys/ioctl/platform/macos.rs +++ /dev/null diff --git a/src/sys/ioctl/platform/netbsd.rs b/src/sys/ioctl/platform/netbsd.rs deleted file mode 100644 index e69de29b..00000000 --- a/src/sys/ioctl/platform/netbsd.rs +++ /dev/null diff --git a/src/sys/ioctl/platform/openbsd.rs b/src/sys/ioctl/platform/openbsd.rs deleted file mode 100644 index e69de29b..00000000 --- a/src/sys/ioctl/platform/openbsd.rs +++ /dev/null diff --git a/src/sys/mod.rs b/src/sys/mod.rs index 793bc70e..7675f944 100644 --- a/src/sys/mod.rs +++ b/src/sys/mod.rs @@ -1,3 +1,7 @@ +#[cfg(any(target_os = "freebsd", target_os = "dragonfly", target_os = "ios", + target_os = "netbsd", target_os = "macos", target_os = "linux"))] +pub mod aio; + #[cfg(any(target_os = "linux", target_os = "android"))] pub mod epoll; @@ -12,7 +16,7 @@ pub mod eventfd; #[cfg(target_os = "linux")] pub mod memfd; -#[cfg(not(any(target_os = "ios", target_os = "freebsd", target_os = "dragonfly")))] +#[macro_use] pub mod ioctl; #[cfg(any(target_os = "linux", target_os = "android"))] diff --git a/src/sys/select.rs b/src/sys/select.rs index 28b664aa..1d9a76c1 100644 --- a/src/sys/select.rs +++ b/src/sys/select.rs @@ -1,6 +1,6 @@ use std::ptr::null_mut; use std::os::unix::io::RawFd; -use libc::c_int; +use libc::{c_int, timeval}; use {Errno, Result}; use sys::time::TimeVal; @@ -56,8 +56,7 @@ impl FdSet { } mod ffi { - use libc::c_int; - use sys::time::TimeVal; + use libc::{c_int, timeval}; use super::FdSet; extern { @@ -65,7 +64,7 @@ mod ffi { readfds: *mut FdSet, writefds: *mut FdSet, errorfds: *mut FdSet, - timeout: *mut TimeVal) -> c_int; + timeout: *mut timeval) -> c_int; } } @@ -77,7 +76,8 @@ pub fn select(nfds: c_int, let readfds = readfds.map(|set| set as *mut FdSet).unwrap_or(null_mut()); let writefds = writefds.map(|set| set as *mut FdSet).unwrap_or(null_mut()); let errorfds = errorfds.map(|set| set as *mut FdSet).unwrap_or(null_mut()); - let timeout = timeout.map(|tv| tv as *mut TimeVal).unwrap_or(null_mut()); + let timeout = timeout.map(|tv| tv as *mut TimeVal as *mut timeval) + .unwrap_or(null_mut()); let res = unsafe { ffi::select(nfds, readfds, writefds, errorfds, timeout) diff --git a/src/sys/signal.rs b/src/sys/signal.rs index bdc25b47..26cf51fd 100644 --- a/src/sys/signal.rs +++ b/src/sys/signal.rs @@ -4,6 +4,8 @@ use libc; use {Errno, Error, Result}; use std::mem; +#[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] +use std::os::unix::io::RawFd; use std::ptr; // Currently there is only one definition of c_int in libc, as well as only one @@ -206,12 +208,12 @@ bitflags!{ } } -bitflags!{ - flags SigFlags: libc::c_int { - const SIG_BLOCK = libc::SIG_BLOCK, - const SIG_UNBLOCK = libc::SIG_UNBLOCK, - const SIG_SETMASK = libc::SIG_SETMASK, - } +#[repr(i32)] +#[derive(Clone, Copy, PartialEq)] +pub enum SigmaskHow { + SIG_BLOCK = libc::SIG_BLOCK, + SIG_UNBLOCK = libc::SIG_UNBLOCK, + SIG_SETMASK = libc::SIG_SETMASK, } #[derive(Clone, Copy)] @@ -268,27 +270,27 @@ impl SigSet { /// Gets the currently blocked (masked) set of signals for the calling thread. pub fn thread_get_mask() -> Result<SigSet> { let mut oldmask: SigSet = unsafe { mem::uninitialized() }; - try!(pthread_sigmask(SigFlags::empty(), None, Some(&mut oldmask))); + try!(pthread_sigmask(SigmaskHow::SIG_SETMASK, None, Some(&mut oldmask))); Ok(oldmask) } /// Sets the set of signals as the signal mask for the calling thread. pub fn thread_set_mask(&self) -> Result<()> { - pthread_sigmask(SIG_SETMASK, Some(self), None) + pthread_sigmask(SigmaskHow::SIG_SETMASK, Some(self), None) } /// Adds the set of signals to the signal mask for the calling thread. pub fn thread_block(&self) -> Result<()> { - pthread_sigmask(SIG_BLOCK, Some(self), None) + pthread_sigmask(SigmaskHow::SIG_BLOCK, Some(self), None) } /// Removes the set of signals from the signal mask for the calling thread. pub fn thread_unblock(&self) -> Result<()> { - pthread_sigmask(SIG_UNBLOCK, Some(self), None) + pthread_sigmask(SigmaskHow::SIG_UNBLOCK, Some(self), None) } /// Sets the set of signals as the signal mask, and returns the old mask. - pub fn thread_swap_mask(&self, how: SigFlags) -> Result<SigSet> { + pub fn thread_swap_mask(&self, how: SigmaskHow) -> Result<SigSet> { let mut oldmask: SigSet = unsafe { mem::uninitialized() }; try!(pthread_sigmask(how, Some(self), Some(&mut oldmask))); Ok(oldmask) @@ -311,7 +313,6 @@ impl AsRef<libc::sigset_t> for SigSet { } #[allow(unknown_lints)] -#[cfg_attr(not(raw_pointer_derive_allowed), allow(raw_pointer_derive))] #[derive(Clone, Copy, PartialEq)] pub enum SigHandler { SigDfl, @@ -369,7 +370,7 @@ pub unsafe fn sigaction(signal: Signal, sigaction: &SigAction) -> Result<SigActi /// /// For more information, visit the [pthread_sigmask](http://man7.org/linux/man-pages/man3/pthread_sigmask.3.html), /// or [sigprocmask](http://man7.org/linux/man-pages/man2/sigprocmask.2.html) man pages. -pub fn pthread_sigmask(how: SigFlags, +pub fn pthread_sigmask(how: SigmaskHow, set: Option<&SigSet>, oldset: Option<&mut SigSet>) -> Result<()> { if set.is_none() && oldset.is_none() { @@ -378,7 +379,7 @@ pub fn pthread_sigmask(how: SigFlags, let res = unsafe { // if set or oldset is None, pass in null pointers instead - libc::pthread_sigmask(how.bits(), + libc::pthread_sigmask(how as libc::c_int, set.map_or_else(|| ptr::null::<libc::sigset_t>(), |s| &s.sigset as *const libc::sigset_t), oldset.map_or_else(|| ptr::null_mut::<libc::sigset_t>(), @@ -388,8 +389,12 @@ pub fn pthread_sigmask(how: SigFlags, Errno::result(res).map(drop) } -pub fn kill(pid: libc::pid_t, signal: Signal) -> Result<()> { - let res = unsafe { libc::kill(pid, signal as libc::c_int) }; +pub fn kill<T: Into<Option<Signal>>>(pid: libc::pid_t, signal: T) -> Result<()> { + let res = unsafe { libc::kill(pid, + match signal.into() { + Some(s) => s as libc::c_int, + None => 0, + }) }; Errno::result(res).map(drop) } @@ -400,6 +405,107 @@ pub fn raise(signal: Signal) -> Result<()> { Errno::result(res).map(drop) } + +#[cfg(target_os = "freebsd")] +pub type type_of_thread_id = libc::lwpid_t; +#[cfg(target_os = "linux")] +pub type type_of_thread_id = libc::pid_t; + +/// Used to request asynchronous notification of certain events, for example, +/// with POSIX AIO, POSIX message queues, and POSIX timers. +// sigval is actually a union of a int and a void*. But it's never really used +// as a pointer, because neither libc nor the kernel ever dereference it. nix +// therefore presents it as an intptr_t, which is how kevent uses it. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum SigevNotify { + /// No notification will be delivered + SigevNone, + /// The signal given by `signal` will be delivered to the process. The + /// value in `si_value` will be present in the `si_value` field of the + /// `siginfo_t` structure of the queued signal. + SigevSignal { signal: Signal, si_value: libc::intptr_t }, + // Note: SIGEV_THREAD is not implemented because libc::sigevent does not + // expose a way to set the union members needed by SIGEV_THREAD. + /// A new `kevent` is posted to the kqueue `kq`. The `kevent`'s `udata` + /// field will contain the value in `udata`. + #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] + SigevKevent { kq: RawFd, udata: libc::intptr_t }, + /// The signal `signal` is queued to the thread whose LWP ID is given in + /// `thread_id`. The value stored in `si_value` will be present in the + /// `si_value` of the `siginfo_t` structure of the queued signal. + #[cfg(any(target_os = "freebsd", target_os = "linux"))] + SigevThreadId { signal: Signal, thread_id: type_of_thread_id, + si_value: libc::intptr_t }, +} + +/// Used to request asynchronous notification of the completion of certain +/// events, such as POSIX AIO and timers. +#[repr(C)] +pub struct SigEvent { + sigevent: libc::sigevent +} + +impl SigEvent { + // Note: this constructor does not allow the user to set the + // sigev_notify_kevent_flags field. That's considered ok because on FreeBSD + // at least those flags don't do anything useful. That field is part of a + // union that shares space with the more genuinely useful + // Note: This constructor also doesn't allow the caller to set the + // sigev_notify_function or sigev_notify_attributes fields, which are + // required for SIGEV_THREAD. That's considered ok because on no operating + // system is SIGEV_THREAD the most efficient way to deliver AIO + // notification. FreeBSD and Dragonfly programs should prefer SIGEV_KEVENT. + // Linux, Solaris, and portable programs should prefer SIGEV_THREAD_ID or + // SIGEV_SIGNAL. That field is part of a union that shares space with the + // more genuinely useful sigev_notify_thread_id + pub fn new(sigev_notify: SigevNotify) -> SigEvent { + let mut sev = unsafe { mem::zeroed::<libc::sigevent>()}; + sev.sigev_notify = match sigev_notify { + SigevNotify::SigevNone => libc::SIGEV_NONE, + SigevNotify::SigevSignal{..} => libc::SIGEV_SIGNAL, + #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] + SigevNotify::SigevKevent{..} => libc::SIGEV_KEVENT, + #[cfg(any(target_os = "linux", target_os = "freebsd"))] + SigevNotify::SigevThreadId{..} => libc::SIGEV_THREAD_ID + }; + sev.sigev_signo = match sigev_notify { + SigevNotify::SigevSignal{ signal, .. } => signal as ::c_int, + #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] + SigevNotify::SigevKevent{ kq, ..} => kq, + #[cfg(any(target_os = "linux", target_os = "freebsd"))] + SigevNotify::SigevThreadId{ signal, .. } => signal as ::c_int, + _ => 0 + }; + sev.sigev_value.sival_ptr = match sigev_notify { + SigevNotify::SigevNone => ptr::null_mut::<libc::c_void>(), + SigevNotify::SigevSignal{ si_value, .. } => si_value as *mut ::c_void, + #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] + SigevNotify::SigevKevent{ udata, .. } => udata as *mut ::c_void, + #[cfg(any(target_os = "linux", target_os = "freebsd"))] + SigevNotify::SigevThreadId{ si_value, .. } => si_value as *mut ::c_void, + }; + SigEvent::set_tid(&mut sev, &sigev_notify); + SigEvent{sigevent: sev} + } + + #[cfg(any(target_os = "linux", target_os = "freebsd"))] + fn set_tid(sev: &mut libc::sigevent, sigev_notify: &SigevNotify) { + sev.sigev_notify_thread_id = match sigev_notify { + &SigevNotify::SigevThreadId { thread_id, .. } => thread_id, + _ => 0 as type_of_thread_id + }; + } + + #[cfg(not(any(target_os = "freebsd", target_os = "linux")))] + fn set_tid(_sev: &mut libc::sigevent, _sigev_notify: &SigevNotify) { + } + + pub fn sigevent(&self) -> libc::sigevent { + self.sigevent + } +} + + #[cfg(test)] mod tests { use super::*; @@ -439,12 +545,46 @@ mod tests { assert!(two_signals.contains(SIGUSR2)); } + // This test doesn't actually test get_mask functionality, see the set_mask test for that. + #[test] + fn test_thread_signal_get_mask() { + assert!(SigSet::thread_get_mask().is_ok()); + } + + #[test] + fn test_thread_signal_set_mask() { + let prev_mask = SigSet::thread_get_mask().expect("Failed to get existing signal mask!"); + + let mut test_mask = prev_mask; + test_mask.add(SIGUSR1); + + assert!(test_mask.thread_set_mask().is_ok()); + let new_mask = SigSet::thread_get_mask().expect("Failed to get new mask!"); + + assert!(new_mask.contains(SIGUSR1)); + assert!(!new_mask.contains(SIGUSR2)); + + prev_mask.thread_set_mask().expect("Failed to revert signal mask!"); + } + #[test] fn test_thread_signal_block() { let mut mask = SigSet::empty(); mask.add(SIGUSR1); assert!(mask.thread_block().is_ok()); + + assert!(SigSet::thread_get_mask().unwrap().contains(SIGUSR1)); + } + + #[test] + fn test_thread_signal_unblock() { + let mut mask = SigSet::empty(); + mask.add(SIGUSR1); + + assert!(mask.thread_unblock().is_ok()); + + assert!(!SigSet::thread_get_mask().unwrap().contains(SIGUSR1)); } #[test] @@ -455,13 +595,15 @@ mod tests { assert!(SigSet::thread_get_mask().unwrap().contains(SIGUSR1)); - let mask2 = SigSet::empty(); - mask.add(SIGUSR2); + let mut mask2 = SigSet::empty(); + mask2.add(SIGUSR2); - let oldmask = mask2.thread_swap_mask(SIG_SETMASK).unwrap(); + let oldmask = mask2.thread_swap_mask(SigmaskHow::SIG_SETMASK).unwrap(); assert!(oldmask.contains(SIGUSR1)); assert!(!oldmask.contains(SIGUSR2)); + + assert!(SigSet::thread_get_mask().unwrap().contains(SIGUSR2)); } // TODO(#251): Re-enable after figuring out flakiness. diff --git a/src/sys/socket/addr.rs b/src/sys/socket/addr.rs index e3c1401c..5f8b130a 100644 --- a/src/sys/socket/addr.rs +++ b/src/sys/socket/addr.rs @@ -7,6 +7,10 @@ use std::path::Path; use std::os::unix::ffi::OsStrExt; #[cfg(any(target_os = "linux", target_os = "android"))] use ::sys::socket::addr::netlink::NetlinkAddr; +#[cfg(any(target_os = "macos", target_os = "ios"))] +use std::os::unix::io::RawFd; +#[cfg(any(target_os = "macos", target_os = "ios"))] +use ::sys::socket::addr::sys_control::SysControlAddr; // TODO: uncomment out IpAddr functions: rust-lang/rfcs#988 @@ -26,6 +30,8 @@ pub enum AddressFamily { Netlink = consts::AF_NETLINK, #[cfg(any(target_os = "linux", target_os = "android"))] Packet = consts::AF_PACKET, + #[cfg(any(target_os = "macos", target_os = "ios"))] + System = consts::AF_SYSTEM, } #[derive(Copy)] @@ -348,10 +354,12 @@ impl fmt::Display for Ipv6Addr { * */ -/// A wrapper around `sockaddr_un`. We track the length of `sun_path`, -/// because it may not be null-terminated (unconnected and abstract -/// sockets). Note that the actual sockaddr length is greater by -/// `size_of::<sa_family_t>()`. +/// A wrapper around `sockaddr_un`. We track the length of `sun_path` (excluding +/// a terminating null), because it may not be null-terminated. For example, +/// unconnected and Linux abstract sockets are never null-terminated, and POSIX +/// does not require that `sun_len` include the terminating null even for normal +/// sockets. Note that the actual sockaddr length is greater by +/// `offset_of!(libc::sockaddr_un, sun_path)` #[derive(Copy)] pub struct UnixAddr(pub libc::sockaddr_un, pub usize); @@ -365,7 +373,7 @@ impl UnixAddr { .. mem::zeroed() }; - let bytes = cstr.to_bytes_with_nul(); + let bytes = cstr.to_bytes(); if bytes.len() > ret.sun_path.len() { return Err(Error::Sys(Errno::ENAMETOOLONG)); @@ -416,7 +424,13 @@ impl UnixAddr { None } else { let p = self.sun_path(); - Some(Path::new(<OsStr as OsStrExt>::from_bytes(&p[..p.len()-1]))) + // POSIX only requires that `sun_len` be at least long enough to + // contain the pathname, and it need not be null-terminated. So we + // need to create a string that is the shorter of the + // null-terminated length or the full length. + let ptr = &self.0.sun_path as *const libc::c_char; + let reallen = unsafe { libc::strnlen(ptr, p.len()) }; + Some(Path::new(<OsStr as OsStrExt>::from_bytes(&p[..reallen]))) } } } @@ -467,7 +481,9 @@ pub enum SockAddr { Inet(InetAddr), Unix(UnixAddr), #[cfg(any(target_os = "linux", target_os = "android"))] - Netlink(NetlinkAddr) + Netlink(NetlinkAddr), + #[cfg(any(target_os = "macos", target_os = "ios"))] + SysControl(SysControlAddr), } impl SockAddr { @@ -484,6 +500,11 @@ impl SockAddr { SockAddr::Netlink(NetlinkAddr::new(pid, groups)) } + #[cfg(any(target_os = "macos", target_os = "ios"))] + pub fn new_sys_control(sockfd: RawFd, name: &str, unit: u32) -> Result<SockAddr> { + SysControlAddr::from_name(sockfd, name, unit).map(|a| SockAddr::SysControl(a)) + } + pub fn family(&self) -> AddressFamily { match *self { SockAddr::Inet(InetAddr::V4(..)) => AddressFamily::Inet, @@ -491,6 +512,8 @@ impl SockAddr { SockAddr::Unix(..) => AddressFamily::Unix, #[cfg(any(target_os = "linux", target_os = "android"))] SockAddr::Netlink(..) => AddressFamily::Netlink, + #[cfg(any(target_os = "macos", target_os = "ios"))] + SockAddr::SysControl(..) => AddressFamily::System, } } @@ -502,9 +525,11 @@ impl SockAddr { match *self { SockAddr::Inet(InetAddr::V4(ref addr)) => (mem::transmute(addr), mem::size_of::<libc::sockaddr_in>() as libc::socklen_t), SockAddr::Inet(InetAddr::V6(ref addr)) => (mem::transmute(addr), mem::size_of::<libc::sockaddr_in6>() as libc::socklen_t), - SockAddr::Unix(UnixAddr(ref addr, len)) => (mem::transmute(addr), (len + mem::size_of::<libc::sa_family_t>()) as libc::socklen_t), + SockAddr::Unix(UnixAddr(ref addr, len)) => (mem::transmute(addr), (len + offset_of!(libc::sockaddr_un, sun_path)) as libc::socklen_t), #[cfg(any(target_os = "linux", target_os = "android"))] SockAddr::Netlink(NetlinkAddr(ref sa)) => (mem::transmute(sa), mem::size_of::<libc::sockaddr_nl>() as libc::socklen_t), + #[cfg(any(target_os = "macos", target_os = "ios"))] + SockAddr::SysControl(SysControlAddr(ref sa)) => (mem::transmute(sa), mem::size_of::<sys_control::sockaddr_ctl>() as libc::socklen_t), } } } @@ -537,6 +562,8 @@ impl hash::Hash for SockAddr { SockAddr::Unix(ref a) => a.hash(s), #[cfg(any(target_os = "linux", target_os = "android"))] SockAddr::Netlink(ref a) => a.hash(s), + #[cfg(any(target_os = "macos", target_os = "ios"))] + SockAddr::SysControl(ref a) => a.hash(s), } } } @@ -554,6 +581,8 @@ impl fmt::Display for SockAddr { SockAddr::Unix(ref unix) => unix.fmt(f), #[cfg(any(target_os = "linux", target_os = "android"))] SockAddr::Netlink(ref nl) => nl.fmt(f), + #[cfg(any(target_os = "macos", target_os = "ios"))] + SockAddr::SysControl(ref sc) => sc.fmt(f), } } } @@ -612,3 +641,102 @@ pub mod netlink { } } } + +#[cfg(any(target_os = "macos", target_os = "ios"))] +pub mod sys_control { + use ::sys::socket::consts; + use ::sys::socket::addr::{AddressFamily}; + use libc::{c_uchar, uint16_t, uint32_t}; + use std::{fmt, mem}; + use std::hash::{Hash, Hasher}; + use std::os::unix::io::RawFd; + use {Errno, Error, Result}; + + #[repr(C)] + pub struct ctl_ioc_info { + pub ctl_id: uint32_t, + pub ctl_name: [c_uchar; MAX_KCTL_NAME], + } + + const CTL_IOC_MAGIC: u8 = 'N' as u8; + const CTL_IOC_INFO: u8 = 3; + const MAX_KCTL_NAME: usize = 96; + + ioctl!(readwrite ctl_info with CTL_IOC_MAGIC, CTL_IOC_INFO; ctl_ioc_info); + + #[repr(C)] + #[derive(Copy, Clone)] + pub struct sockaddr_ctl { + pub sc_len: c_uchar, + pub sc_family: c_uchar, + pub ss_sysaddr: uint16_t, + pub sc_id: uint32_t, + pub sc_unit: uint32_t, + pub sc_reserved: [uint32_t; 5], + } + + #[derive(Copy, Clone)] + pub struct SysControlAddr(pub sockaddr_ctl); + + // , PartialEq, Eq, Debug, Hash + impl PartialEq for SysControlAddr { + fn eq(&self, other: &Self) -> bool { + let (inner, other) = (self.0, other.0); + (inner.sc_id, inner.sc_unit) == + (other.sc_id, other.sc_unit) + } + } + + impl Eq for SysControlAddr {} + + impl Hash for SysControlAddr { + fn hash<H: Hasher>(&self, s: &mut H) { + let inner = self.0; + (inner.sc_id, inner.sc_unit).hash(s); + } + } + + + impl SysControlAddr { + pub fn new(id: u32, unit: u32) -> SysControlAddr { + let addr = sockaddr_ctl { + sc_len: mem::size_of::<sockaddr_ctl>() as c_uchar, + sc_family: AddressFamily::System as c_uchar, + ss_sysaddr: consts::AF_SYS_CONTROL as uint16_t, + sc_id: id, + sc_unit: unit, + sc_reserved: [0; 5] + }; + + SysControlAddr(addr) + } + + pub fn from_name(sockfd: RawFd, name: &str, unit: u32) -> Result<SysControlAddr> { + if name.len() > MAX_KCTL_NAME { + return Err(Error::Sys(Errno::ENAMETOOLONG)); + } + + let mut ctl_name = [0; MAX_KCTL_NAME]; + ctl_name[..name.len()].clone_from_slice(name.as_bytes()); + let mut info = ctl_ioc_info { ctl_id: 0, ctl_name: ctl_name }; + + unsafe { try!(ctl_info(sockfd, &mut info)); } + + Ok(SysControlAddr::new(info.ctl_id, unit)) + } + + pub fn id(&self) -> u32 { + self.0.sc_id + } + + pub fn unit(&self) -> u32 { + self.0.sc_unit + } + } + + impl fmt::Display for SysControlAddr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "id: {} unit: {}", self.id(), self.unit()) + } + } +} diff --git a/src/sys/socket/consts.rs b/src/sys/socket/consts.rs index 63eaf28a..3c5efdf7 100644 --- a/src/sys/socket/consts.rs +++ b/src/sys/socket/consts.rs @@ -132,6 +132,11 @@ mod os { pub const AF_INET6: c_int = 28; #[cfg(any(target_os = "macos", target_os = "ios"))] pub const AF_INET6: c_int = 30; + #[cfg(any(target_os = "macos", target_os = "ios"))] + pub const AF_SYSTEM: c_int = 32; + + #[cfg(any(target_os = "macos", target_os = "ios"))] + pub const AF_SYS_CONTROL: c_int = 2; pub const SOCK_STREAM: c_int = 1; pub const SOCK_DGRAM: c_int = 2; @@ -144,6 +149,8 @@ mod os { pub const IPPROTO_IPV6: c_int = 41; pub const IPPROTO_TCP: c_int = 6; pub const IPPROTO_UDP: c_int = 17; + #[cfg(any(target_os = "macos", target_os = "ios"))] + pub const SYSPROTO_CONTROL: c_int = 2; pub const SO_ACCEPTCONN: c_int = 0x0002; pub const SO_BROADCAST: c_int = 0x0020; diff --git a/src/sys/socket/mod.rs b/src/sys/socket/mod.rs index 69f26aa0..645dfe41 100644 --- a/src/sys/socket/mod.rs +++ b/src/sys/socket/mod.rs @@ -50,17 +50,8 @@ pub use self::multicast::{ }; pub use self::consts::*; -#[cfg(any(not(target_os = "linux"), not(target_arch = "x86")))] pub use libc::sockaddr_storage; -// Working around rust-lang/rust#23425 -#[cfg(all(target_os = "linux", target_arch = "x86"))] -pub struct sockaddr_storage { - pub ss_family: sa_family_t, - pub __ss_align: u32, - pub __ss_pad2: [u8; 120], -} - #[derive(Clone, Copy, PartialEq, Eq, Debug)] #[repr(i32)] pub enum SockType { @@ -238,8 +229,13 @@ impl<'a> ControlMessage<'a> { let padlen = cmsg_align(mem::size_of_val(&cmsg)) - mem::size_of_val(&cmsg); - let buf2 = &mut &mut buf[padlen..]; - copy_bytes(fds, buf2); + + let mut tmpbuf = &mut [][..]; + mem::swap(&mut tmpbuf, buf); + let (_padding, mut remainder) = tmpbuf.split_at_mut(padlen); + mem::swap(buf, &mut remainder); + + copy_bytes(fds, buf); }, ControlMessage::Unknown(UnknownCmsg(orig_cmsg, bytes)) => { copy_bytes(orig_cmsg, buf); @@ -608,6 +604,12 @@ pub fn getsockname(fd: RawFd) -> Result<SockAddr> { } } +/// Return the appropriate SockAddr type from a `sockaddr_storage` of a certain +/// size. In C this would usually be done by casting. The `len` argument +/// should be the number of bytes in the sockaddr_storage that are actually +/// allocated and valid. It must be at least as large as all the useful parts +/// of the structure. Note that in the case of a `sockaddr_un`, `len` need not +/// include the terminating null. pub unsafe fn sockaddr_storage_to_addr( addr: &sockaddr_storage, len: usize) -> Result<SockAddr> { @@ -627,7 +629,9 @@ pub unsafe fn sockaddr_storage_to_addr( Ok(SockAddr::Inet(InetAddr::V6((*(addr as *const _ as *const sockaddr_in6))))) } consts::AF_UNIX => { - Ok(SockAddr::Unix(UnixAddr(*(addr as *const _ as *const sockaddr_un), len))) + let sun = *(addr as *const _ as *const sockaddr_un); + let pathlen = len - offset_of!(sockaddr_un, sun_path); + Ok(SockAddr::Unix(UnixAddr(sun, pathlen))) } #[cfg(any(target_os = "linux", target_os = "android"))] consts::AF_NETLINK => { diff --git a/src/sys/time.rs b/src/sys/time.rs index 1750481c..0d977045 100644 --- a/src/sys/time.rs +++ b/src/sys/time.rs @@ -1,55 +1,328 @@ -use std::{fmt, ops}; -use libc::{time_t, suseconds_t}; +use std::{cmp, fmt, ops}; +use libc::{c_long, time_t, suseconds_t, timespec, timeval}; -#[repr(C)] -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] -pub struct TimeVal { - pub tv_sec: time_t, - pub tv_usec: suseconds_t, +pub trait TimeValLike: Sized { + #[inline] + fn zero() -> Self { + Self::seconds(0) + } + + #[inline] + fn hours(hours: i64) -> Self { + let secs = hours.checked_mul(SECS_PER_HOUR) + .expect("TimeValLike::hours ouf of bounds"); + Self::seconds(secs) + } + + #[inline] + fn minutes(minutes: i64) -> Self { + let secs = minutes.checked_mul(SECS_PER_MINUTE) + .expect("TimeValLike::minutes out of bounds"); + Self::seconds(secs) + } + + fn seconds(seconds: i64) -> Self; + fn milliseconds(milliseconds: i64) -> Self; + fn microseconds(microseconds: i64) -> Self; + fn nanoseconds(nanoseconds: i64) -> Self; + + #[inline] + fn num_hours(&self) -> i64 { + self.num_seconds() / 3600 + } + + #[inline] + fn num_minutes(&self) -> i64 { + self.num_seconds() / 60 + } + + fn num_seconds(&self) -> i64; + fn num_milliseconds(&self) -> i64; + fn num_microseconds(&self) -> i64; + fn num_nanoseconds(&self) -> i64; } -const MICROS_PER_SEC: i64 = 1_000_000; +#[repr(C)] +#[derive(Clone, Copy)] +pub struct TimeSpec(timespec); + +const NANOS_PER_SEC: i64 = 1_000_000_000; const SECS_PER_MINUTE: i64 = 60; const SECS_PER_HOUR: i64 = 3600; #[cfg(target_pointer_width = "64")] -const MAX_SECONDS: i64 = (::std::i64::MAX / MICROS_PER_SEC) - 1; +const TS_MAX_SECONDS: i64 = (::std::i64::MAX / NANOS_PER_SEC) - 1; #[cfg(target_pointer_width = "32")] -const MAX_SECONDS: i64 = ::std::isize::MAX as i64; +const TS_MAX_SECONDS: i64 = ::std::isize::MAX as i64; -const MIN_SECONDS: i64 = -MAX_SECONDS; +const TS_MIN_SECONDS: i64 = -TS_MAX_SECONDS; -impl TimeVal { + +impl AsRef<timespec> for TimeSpec { + fn as_ref(&self) -> ×pec { + &self.0 + } +} + +impl fmt::Debug for TimeSpec { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt.debug_struct("TimeSpec") + .field("tv_sec", &self.tv_sec()) + .field("tv_nsec", &self.tv_nsec()) + .finish() + } +} + +impl cmp::PartialEq for TimeSpec { + // The implementation of cmp is simplified by assuming that the struct is + // normalized. That is, tv_nsec must always be within [0, 1_000_000_000) + fn eq(&self, other: &TimeSpec) -> bool { + self.tv_sec() == other.tv_sec() && self.tv_nsec() == other.tv_nsec() + } +} + +impl cmp::Eq for TimeSpec {} + +impl cmp::Ord for TimeSpec { + // The implementation of cmp is simplified by assuming that the struct is + // normalized. That is, tv_nsec must always be within [0, 1_000_000_000) + fn cmp(&self, other: &TimeSpec) -> cmp::Ordering { + if self.tv_sec() == other.tv_sec() { + self.tv_nsec().cmp(&other.tv_nsec()) + } else { + self.tv_sec().cmp(&other.tv_sec()) + } + } +} + +impl cmp::PartialOrd for TimeSpec { + fn partial_cmp(&self, other: &TimeSpec) -> Option<cmp::Ordering> { + Some(self.cmp(other)) + } +} + +impl TimeValLike for TimeSpec { #[inline] - pub fn zero() -> TimeVal { - TimeVal::microseconds(0) + fn seconds(seconds: i64) -> TimeSpec { + assert!(seconds >= TS_MIN_SECONDS && seconds <= TS_MAX_SECONDS, + "TimeSpec out of bounds; seconds={}", seconds); + TimeSpec(timespec {tv_sec: seconds as time_t, tv_nsec: 0 }) } #[inline] - pub fn hours(hours: i64) -> TimeVal { - let secs = hours.checked_mul(SECS_PER_HOUR) - .expect("TimeVal::hours ouf of bounds"); + fn milliseconds(milliseconds: i64) -> TimeSpec { + let nanoseconds = milliseconds.checked_mul(1_000_000) + .expect("TimeSpec::milliseconds out of bounds"); - TimeVal::seconds(secs) + TimeSpec::nanoseconds(nanoseconds) } + /// Makes a new `TimeSpec` with given number of microseconds. #[inline] - pub fn minutes(minutes: i64) -> TimeVal { - let secs = minutes.checked_mul(SECS_PER_MINUTE) - .expect("TimeVal::minutes out of bounds"); + fn microseconds(microseconds: i64) -> TimeSpec { + let nanoseconds = microseconds.checked_mul(1_000) + .expect("TimeSpec::milliseconds out of bounds"); + + TimeSpec::nanoseconds(nanoseconds) + } + + /// Makes a new `TimeSpec` with given number of nanoseconds. + #[inline] + fn nanoseconds(nanoseconds: i64) -> TimeSpec { + let (secs, nanos) = div_mod_floor_64(nanoseconds, NANOS_PER_SEC); + assert!(secs >= TS_MIN_SECONDS && secs <= TS_MAX_SECONDS, + "TimeSpec out of bounds"); + TimeSpec(timespec {tv_sec: secs as time_t, + tv_nsec: nanos as c_long }) + } + + fn num_seconds(&self) -> i64 { + if self.tv_sec() < 0 && self.tv_nsec() > 0 { + (self.tv_sec() + 1) as i64 + } else { + self.tv_sec() as i64 + } + } + + fn num_milliseconds(&self) -> i64 { + self.num_nanoseconds() / 1_000_000 + } + + fn num_microseconds(&self) -> i64 { + self.num_nanoseconds() / 1_000_000_000 + } + + fn num_nanoseconds(&self) -> i64 { + let secs = self.num_seconds() * 1_000_000_000; + let nsec = self.nanos_mod_sec(); + secs + nsec as i64 + } +} + +impl TimeSpec { + fn nanos_mod_sec(&self) -> c_long { + if self.tv_sec() < 0 && self.tv_nsec() > 0 { + self.tv_nsec() - NANOS_PER_SEC as c_long + } else { + self.tv_nsec() + } + } + + pub fn tv_sec(&self) -> time_t { + self.0.tv_sec + } + + pub fn tv_nsec(&self) -> c_long { + self.0.tv_nsec + } +} + +impl ops::Neg for TimeSpec { + type Output = TimeSpec; + + fn neg(self) -> TimeSpec { + TimeSpec::nanoseconds(-self.num_nanoseconds()) + } +} + +impl ops::Add for TimeSpec { + type Output = TimeSpec; + + fn add(self, rhs: TimeSpec) -> TimeSpec { + TimeSpec::nanoseconds( + self.num_nanoseconds() + rhs.num_nanoseconds()) + } +} + +impl ops::Sub for TimeSpec { + type Output = TimeSpec; + + fn sub(self, rhs: TimeSpec) -> TimeSpec { + TimeSpec::nanoseconds( + self.num_nanoseconds() - rhs.num_nanoseconds()) + } +} + +impl ops::Mul<i32> for TimeSpec { + type Output = TimeSpec; + + fn mul(self, rhs: i32) -> TimeSpec { + let usec = self.num_nanoseconds().checked_mul(rhs as i64) + .expect("TimeSpec multiply out of bounds"); - TimeVal::seconds(secs) + TimeSpec::nanoseconds(usec) } +} + +impl ops::Div<i32> for TimeSpec { + type Output = TimeSpec; + + fn div(self, rhs: i32) -> TimeSpec { + let usec = self.num_nanoseconds() / rhs as i64; + TimeSpec::nanoseconds(usec) + } +} + +impl fmt::Display for TimeSpec { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let (abs, sign) = if self.tv_sec() < 0 { + (-*self, "-") + } else { + (*self, "") + }; + let sec = abs.tv_sec(); + + try!(write!(f, "{}", sign)); + + if abs.tv_nsec() == 0 { + if abs.tv_sec() == 1 { + try!(write!(f, "{} second", sec)); + } else { + try!(write!(f, "{} seconds", sec)); + } + } else if abs.tv_nsec() % 1_000_000 == 0 { + try!(write!(f, "{}.{:03} seconds", sec, abs.tv_nsec() / 1_000_000)); + } else if abs.tv_nsec() % 1_000 == 0 { + try!(write!(f, "{}.{:06} seconds", sec, abs.tv_nsec() / 1_000)); + } else { + try!(write!(f, "{}.{:09} seconds", sec, abs.tv_nsec())); + } + + Ok(()) + } +} + + + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct TimeVal(timeval); + +const MICROS_PER_SEC: i64 = 1_000_000; + +#[cfg(target_pointer_width = "64")] +const TV_MAX_SECONDS: i64 = (::std::i64::MAX / MICROS_PER_SEC) - 1; + +#[cfg(target_pointer_width = "32")] +const TV_MAX_SECONDS: i64 = ::std::isize::MAX as i64; + +const TV_MIN_SECONDS: i64 = -TV_MAX_SECONDS; + +impl AsRef<timeval> for TimeVal { + fn as_ref(&self) -> &timeval { + &self.0 + } +} + +impl fmt::Debug for TimeVal { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt.debug_struct("TimeVal") + .field("tv_sec", &self.tv_sec()) + .field("tv_usec", &self.tv_usec()) + .finish() + } +} + +impl cmp::PartialEq for TimeVal { + // The implementation of cmp is simplified by assuming that the struct is + // normalized. That is, tv_usec must always be within [0, 1_000_000) + fn eq(&self, other: &TimeVal) -> bool { + self.tv_sec() == other.tv_sec() && self.tv_usec() == other.tv_usec() + } +} + +impl cmp::Eq for TimeVal {} + +impl cmp::Ord for TimeVal { + // The implementation of cmp is simplified by assuming that the struct is + // normalized. That is, tv_usec must always be within [0, 1_000_000) + fn cmp(&self, other: &TimeVal) -> cmp::Ordering { + if self.tv_sec() == other.tv_sec() { + self.tv_usec().cmp(&other.tv_usec()) + } else { + self.tv_sec().cmp(&other.tv_sec()) + } + } +} + +impl cmp::PartialOrd for TimeVal { + fn partial_cmp(&self, other: &TimeVal) -> Option<cmp::Ordering> { + Some(self.cmp(other)) + } +} + +impl TimeValLike for TimeVal { #[inline] - pub fn seconds(seconds: i64) -> TimeVal { - assert!(seconds >= MIN_SECONDS && seconds <= MAX_SECONDS, "TimeVal out of bounds; seconds={}", seconds); - TimeVal { tv_sec: seconds as time_t, tv_usec: 0 } + fn seconds(seconds: i64) -> TimeVal { + assert!(seconds >= TV_MIN_SECONDS && seconds <= TV_MAX_SECONDS, + "TimeVal out of bounds; seconds={}", seconds); + TimeVal(timeval {tv_sec: seconds as time_t, tv_usec: 0 }) } #[inline] - pub fn milliseconds(milliseconds: i64) -> TimeVal { + fn milliseconds(milliseconds: i64) -> TimeVal { let microseconds = milliseconds.checked_mul(1_000) .expect("TimeVal::milliseconds out of bounds"); @@ -58,45 +331,65 @@ impl TimeVal { /// Makes a new `TimeVal` with given number of microseconds. #[inline] - pub fn microseconds(microseconds: i64) -> TimeVal { + fn microseconds(microseconds: i64) -> TimeVal { let (secs, micros) = div_mod_floor_64(microseconds, MICROS_PER_SEC); - assert!(secs >= MIN_SECONDS && secs <= MAX_SECONDS, "TimeVal out of bounds"); - TimeVal { tv_sec: secs as time_t, tv_usec: micros as suseconds_t } + assert!(secs >= TV_MIN_SECONDS && secs <= TV_MAX_SECONDS, + "TimeVal out of bounds"); + TimeVal(timeval {tv_sec: secs as time_t, + tv_usec: micros as suseconds_t }) } - pub fn num_hours(&self) -> i64 { - self.num_seconds() / 3600 - } - - pub fn num_minutes(&self) -> i64 { - self.num_seconds() / 60 + /// Makes a new `TimeVal` with given number of nanoseconds. Some precision + /// will be lost + #[inline] + fn nanoseconds(nanoseconds: i64) -> TimeVal { + let microseconds = nanoseconds / 1000; + let (secs, micros) = div_mod_floor_64(microseconds, MICROS_PER_SEC); + assert!(secs >= TV_MIN_SECONDS && secs <= TV_MAX_SECONDS, + "TimeVal out of bounds"); + TimeVal(timeval {tv_sec: secs as time_t, + tv_usec: micros as suseconds_t }) } - pub fn num_seconds(&self) -> i64 { - if self.tv_sec < 0 && self.tv_usec > 0 { - (self.tv_sec + 1) as i64 + fn num_seconds(&self) -> i64 { + if self.tv_sec() < 0 && self.tv_usec() > 0 { + (self.tv_sec() + 1) as i64 } else { - self.tv_sec as i64 + self.tv_sec() as i64 } } - pub fn num_milliseconds(&self) -> i64 { + fn num_milliseconds(&self) -> i64 { self.num_microseconds() / 1_000 } - pub fn num_microseconds(&self) -> i64 { + fn num_microseconds(&self) -> i64 { let secs = self.num_seconds() * 1_000_000; let usec = self.micros_mod_sec(); secs + usec as i64 } + fn num_nanoseconds(&self) -> i64 { + self.num_microseconds() * 1_000 + } +} + +impl TimeVal { fn micros_mod_sec(&self) -> suseconds_t { - if self.tv_sec < 0 && self.tv_usec > 0 { - self.tv_usec - MICROS_PER_SEC as suseconds_t + if self.tv_sec() < 0 && self.tv_usec() > 0 { + self.tv_usec() - MICROS_PER_SEC as suseconds_t } else { - self.tv_usec + self.tv_usec() } } + + pub fn tv_sec(&self) -> time_t { + self.0.tv_sec + } + + pub fn tv_usec(&self) -> suseconds_t { + self.0.tv_usec + } } impl ops::Neg for TimeVal { @@ -147,26 +440,26 @@ impl ops::Div<i32> for TimeVal { impl fmt::Display for TimeVal { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let (abs, sign) = if self.tv_sec < 0 { + let (abs, sign) = if self.tv_sec() < 0 { (-*self, "-") } else { (*self, "") }; - let sec = abs.tv_sec; + let sec = abs.tv_sec(); try!(write!(f, "{}", sign)); - if abs.tv_usec == 0 { - if abs.tv_sec == 1 { + if abs.tv_usec() == 0 { + if abs.tv_sec() == 1 { try!(write!(f, "{} second", sec)); } else { try!(write!(f, "{} seconds", sec)); } - } else if abs.tv_usec % 1000 == 0 { - try!(write!(f, "{}.{:03} seconds", sec, abs.tv_usec / 1000)); + } else if abs.tv_usec() % 1000 == 0 { + try!(write!(f, "{}.{:03} seconds", sec, abs.tv_usec() / 1000)); } else { - try!(write!(f, "{}.{:06} seconds", sec, abs.tv_usec)); + try!(write!(f, "{}.{:06} seconds", sec, abs.tv_usec())); } Ok(()) @@ -203,18 +496,64 @@ fn div_rem_64(this: i64, other: i64) -> (i64, i64) { #[cfg(test)] mod test { - use super::TimeVal; + use super::{TimeSpec, TimeVal, TimeValLike}; #[test] - pub fn test_time_val() { + pub fn test_timespec() { + assert!(TimeSpec::seconds(1) != TimeSpec::zero()); + assert_eq!(TimeSpec::seconds(1) + TimeSpec::seconds(2), + TimeSpec::seconds(3)); + assert_eq!(TimeSpec::minutes(3) + TimeSpec::seconds(2), + TimeSpec::seconds(182)); + } + + #[test] + pub fn test_timespec_neg() { + let a = TimeSpec::seconds(1) + TimeSpec::nanoseconds(123); + let b = TimeSpec::seconds(-1) + TimeSpec::nanoseconds(-123); + + assert_eq!(a, -b); + } + + #[test] + pub fn test_timespec_ord() { + assert!(TimeSpec::seconds(1) == TimeSpec::nanoseconds(1_000_000_000)); + assert!(TimeSpec::seconds(1) < TimeSpec::nanoseconds(1_000_000_001)); + assert!(TimeSpec::seconds(1) > TimeSpec::nanoseconds(999_999_999)); + assert!(TimeSpec::seconds(-1) < TimeSpec::nanoseconds(-999_999_999)); + assert!(TimeSpec::seconds(-1) > TimeSpec::nanoseconds(-1_000_000_001)); + } + + #[test] + pub fn test_timespec_fmt() { + assert_eq!(TimeSpec::zero().to_string(), "0 seconds"); + assert_eq!(TimeSpec::seconds(42).to_string(), "42 seconds"); + assert_eq!(TimeSpec::milliseconds(42).to_string(), "0.042 seconds"); + assert_eq!(TimeSpec::microseconds(42).to_string(), "0.000042 seconds"); + assert_eq!(TimeSpec::nanoseconds(42).to_string(), "0.000000042 seconds"); + assert_eq!(TimeSpec::seconds(-86401).to_string(), "-86401 seconds"); + } + + #[test] + pub fn test_timeval() { assert!(TimeVal::seconds(1) != TimeVal::zero()); - assert_eq!(TimeVal::seconds(1) + TimeVal::seconds(2), TimeVal::seconds(3)); + assert_eq!(TimeVal::seconds(1) + TimeVal::seconds(2), + TimeVal::seconds(3)); assert_eq!(TimeVal::minutes(3) + TimeVal::seconds(2), TimeVal::seconds(182)); } #[test] - pub fn test_time_val_neg() { + pub fn test_timeval_ord() { + assert!(TimeVal::seconds(1) == TimeVal::microseconds(1_000_000)); + assert!(TimeVal::seconds(1) < TimeVal::microseconds(1_000_001)); + assert!(TimeVal::seconds(1) > TimeVal::microseconds(999_999)); + assert!(TimeVal::seconds(-1) < TimeVal::microseconds(-999_999)); + assert!(TimeVal::seconds(-1) > TimeVal::microseconds(-1_000_001)); + } + + #[test] + pub fn test_timeval_neg() { let a = TimeVal::seconds(1) + TimeVal::microseconds(123); let b = TimeVal::seconds(-1) + TimeVal::microseconds(-123); @@ -222,11 +561,12 @@ mod test { } #[test] - pub fn test_time_val_fmt() { + pub fn test_timeval_fmt() { assert_eq!(TimeVal::zero().to_string(), "0 seconds"); assert_eq!(TimeVal::seconds(42).to_string(), "42 seconds"); assert_eq!(TimeVal::milliseconds(42).to_string(), "0.042 seconds"); assert_eq!(TimeVal::microseconds(42).to_string(), "0.000042 seconds"); + assert_eq!(TimeVal::nanoseconds(1402).to_string(), "0.000001 seconds"); assert_eq!(TimeVal::seconds(-86401).to_string(), "-86401 seconds"); } } diff --git a/src/unistd.rs b/src/unistd.rs index 2c4a92c3..187154bd 100644 --- a/src/unistd.rs +++ b/src/unistd.rs @@ -1,12 +1,12 @@ -//! Standard symbolic constants and types -//! +//! Safe wrappers around functions found in libc "unistd.h" header + use {Errno, Error, Result, NixPath}; use fcntl::{fcntl, OFlag, O_CLOEXEC, FD_CLOEXEC}; use fcntl::FcntlArg::F_SETFD; use libc::{self, c_char, c_void, c_int, c_uint, size_t, pid_t, off_t, uid_t, gid_t, mode_t}; use std::mem; -use std::ffi::{CString, CStr, OsString}; -use std::os::unix::ffi::{OsStringExt}; +use std::ffi::{CString, CStr, OsString, OsStr}; +use std::os::unix::ffi::{OsStringExt, OsStrExt}; use std::os::unix::io::RawFd; use std::path::{PathBuf}; use void::Void; @@ -15,15 +15,20 @@ use sys::stat::Mode; #[cfg(any(target_os = "linux", target_os = "android"))] pub use self::linux::*; +/// Represents the successful result of calling `fork` +/// +/// When `fork` is called, the process continues execution in the parent process +/// and in the new child. This return type can be examined to determine whether +/// you are now executing in the parent process or in the child. #[derive(Clone, Copy)] pub enum ForkResult { - Parent { - child: pid_t - }, - Child + Parent { child: pid_t }, + Child, } impl ForkResult { + + /// Return `true` if this is the child process of the `fork()` #[inline] pub fn is_child(&self) -> bool { match *self { @@ -32,12 +37,40 @@ impl ForkResult { } } + /// Returns `true` if this is the parent process of the `fork()` #[inline] pub fn is_parent(&self) -> bool { !self.is_child() } } +/// Create a new child process duplicating the parent process ([see +/// fork(2)](http://man7.org/linux/man-pages/man2/fork.2.html)). +/// +/// After calling the fork system call (successfully) two processes will +/// be created that are identical with the exception of their pid and the +/// return value of this function. As an example: +/// +/// ```no_run +/// use nix::unistd::{fork, ForkResult}; +/// +/// match fork() { +/// Ok(ForkResult::Parent { child, .. }) => { +/// println!("Continuing execution in parent process, new child has pid: {}", child); +/// } +/// Ok(ForkResult::Child) => println!("I'm a new child process"), +/// Err(e) => println!("Fork failed"), +/// } +/// ``` +/// +/// This will print something like the following (order indeterministic). The +/// thing to note is that you end up with two processes continuing execution +/// immediately after the fork call but with different match arms. +/// +/// ```text +/// Continuing execution in parent process, new child has pid: 1234 +/// I'm a new child process +/// ``` #[inline] pub fn fork() -> Result<ForkResult> { use self::ForkResult::*; @@ -45,30 +78,116 @@ pub fn fork() -> Result<ForkResult> { Errno::result(res).map(|res| match res { 0 => Child, - res => Parent { child: res } + res => Parent { child: res }, }) } +/// Get the pid of this process (see +/// [getpid(2)](http://man7.org/linux/man-pages/man2/getpid.2.html)). +/// +/// Since you are running code, there is always a pid to return, so there +/// is no error case that needs to be handled. #[inline] pub fn getpid() -> pid_t { - unsafe { libc::getpid() } // no error handling, according to man page: "These functions are always successful." + unsafe { libc::getpid() } } + +/// Get the pid of this processes' parent (see +/// [getpid(2)](http://man7.org/linux/man-pages/man2/getpid.2.html)). +/// +/// There is always a parent pid to return, so there is no error case that needs +/// to be handled. #[inline] pub fn getppid() -> pid_t { unsafe { libc::getppid() } // no error handling, according to man page: "These functions are always successful." } + +/// Set a process group ID (see +/// [setpgid(2)](http://man7.org/linux/man-pages/man2/setpgid.2.html)). +/// +/// Set the process group id (PGID) of a particular process. If a pid of zero +/// is specified, then the pid of the calling process is used. Process groups +/// may be used to group together a set of processes in order for the OS to +/// apply some operations across the group. +/// +/// `setsid()` may be used to create a new process group. #[inline] pub fn setpgid(pid: pid_t, pgid: pid_t) -> Result<()> { let res = unsafe { libc::setpgid(pid, pgid) }; Errno::result(res).map(drop) } +#[inline] +pub fn getpgid(pid: Option<pid_t>) -> Result<pid_t> { + let res = unsafe { libc::getpgid(pid.unwrap_or(0 as pid_t)) }; + Errno::result(res) +} + +/// Create new session and set process group id (see +/// [setsid(2)](http://man7.org/linux/man-pages/man2/setsid.2.html)). +#[inline] +pub fn setsid() -> Result<pid_t> { + Errno::result(unsafe { libc::setsid() }) +} + + +/// Get the terminal foreground process group (see +/// [tcgetpgrp(3)](http://man7.org/linux/man-pages/man3/tcgetpgrp.3.html)). +/// +/// Get the group process id (GPID) of the foreground process group on the +/// terminal associated to file descriptor (FD). +#[inline] +pub fn tcgetpgrp(fd: c_int) -> Result<pid_t> { + let res = unsafe { libc::tcgetpgrp(fd) }; + Errno::result(res) +} +/// Set the terminal foreground process group (see +/// [tcgetpgrp(3)](http://man7.org/linux/man-pages/man3/tcgetpgrp.3.html)). +/// +/// Get the group process id (PGID) to the foreground process group on the +/// terminal associated to file descriptor (FD). +#[inline] +pub fn tcsetpgrp(fd: c_int, pgrp: pid_t) -> Result<()> { + let res = unsafe { libc::tcsetpgrp(fd, pgrp) }; + Errno::result(res).map(drop) +} + + +/// Get the group id of the calling process (see +///[getpgrp(3)](http://man7.org/linux/man-pages/man3/getpgrp.3p.html)). +/// +/// Get the process group id (PGID) of the calling process. +/// According to the man page it is always successful. +#[inline] +pub fn getpgrp() -> pid_t { + unsafe { libc::getpgrp() } +} +/// Get the caller's thread ID (see +/// [gettid(2)](http://man7.org/linux/man-pages/man2/gettid.2.html). +/// +/// This function is only available on Linux based systems. In a single +/// threaded process, the main thread will have the same ID as the process. In +/// a multithreaded process, each thread will have a unique thread id but the +/// same process ID. +/// +/// No error handling is required as a thread id should always exist for any +/// process, even if threads are not being used. #[cfg(any(target_os = "linux", target_os = "android"))] #[inline] pub fn gettid() -> pid_t { - unsafe { libc::syscall(libc::SYS_gettid) as pid_t } // no error handling, according to man page: "These functions are always successful." + unsafe { libc::syscall(libc::SYS_gettid) as pid_t } } +/// Create a copy of the specified file descriptor (see +/// [dup(2)](http://man7.org/linux/man-pages/man2/dup.2.html)). +/// +/// The new file descriptor will be have a new index but refer to the same +/// resource as the old file descriptor and the old and new file descriptors may +/// be used interchangeably. The new and old file descriptor share the same +/// underlying resource, offset, and file status flags. The actual index used +/// for the file descriptor will be the lowest fd index that is available. +/// +/// The two file descriptors do not share file descriptor flags (e.g. `FD_CLOEXEC`). #[inline] pub fn dup(oldfd: RawFd) -> Result<RawFd> { let res = unsafe { libc::dup(oldfd) }; @@ -76,6 +195,12 @@ pub fn dup(oldfd: RawFd) -> Result<RawFd> { Errno::result(res) } +/// Create a copy of the specified file descriptor using the specified fd (see +/// [dup(2)](http://man7.org/linux/man-pages/man2/dup.2.html)). +/// +/// This function behaves similar to `dup()` except that it will try to use the +/// specified fd instead of allocating a new one. See the man pages for more +/// detail on the exact behavior of this function. #[inline] pub fn dup2(oldfd: RawFd, newfd: RawFd) -> Result<RawFd> { let res = unsafe { libc::dup2(oldfd, newfd) }; @@ -83,6 +208,11 @@ pub fn dup2(oldfd: RawFd, newfd: RawFd) -> Result<RawFd> { Errno::result(res) } +/// Create a new copy of the specified file descriptor using the specified fd +/// and flags (see [dup(2)](http://man7.org/linux/man-pages/man2/dup.2.html)). +/// +/// This function behaves similar to `dup2()` but allows for flags to be +/// specified. pub fn dup3(oldfd: RawFd, newfd: RawFd, flags: OFlag) -> Result<RawFd> { dup3_polyfill(oldfd, newfd, flags) } @@ -105,6 +235,11 @@ fn dup3_polyfill(oldfd: RawFd, newfd: RawFd, flags: OFlag) -> Result<RawFd> { Ok(fd) } +/// Change the current working directory of the calling process (see +/// [chdir(2)](http://man7.org/linux/man-pages/man2/chdir.2.html)). +/// +/// This function may fail in a number of different scenarios. See the man +/// pages for additional details on possible failure cases. #[inline] pub fn chdir<P: ?Sized + NixPath>(path: &P) -> Result<()> { let res = try!(path.with_nix_path(|cstr| { @@ -114,6 +249,19 @@ pub fn chdir<P: ?Sized + NixPath>(path: &P) -> Result<()> { Errno::result(res).map(drop) } +/// Change the current working directory of the process to the one +/// given as an open file descriptor (see +/// [fchdir(2)](http://man7.org/linux/man-pages/man2/fchdir.2.html)). +/// +/// This function may fail in a number of different scenarios. See the man +/// pages for additional details on possible failure cases. +#[inline] +pub fn fchdir(dirfd: RawFd) -> Result<()> { + let res = unsafe { libc::fchdir(dirfd) }; + + Errno::result(res).map(drop) +} + /// Creates new directory `path` with access rights `mode`. /// /// # Errors @@ -138,12 +286,12 @@ pub fn chdir<P: ?Sized + NixPath>(path: &P) -> Result<()> { /// use tempdir::TempDir; /// /// fn main() { -/// let mut tmp_dir = TempDir::new("test_mkdir").unwrap().into_path(); -/// tmp_dir.push("new_dir"); +/// let tmp_dir1 = TempDir::new("test_mkdir").unwrap(); +/// let tmp_dir2 = tmp_dir1.path().join("new_dir"); /// /// // create new directory and give read, write and execute rights to the owner -/// match unistd::mkdir(&tmp_dir, stat::S_IRWXU) { -/// Ok(_) => println!("created {:?}", tmp_dir), +/// match unistd::mkdir(&tmp_dir2, stat::S_IRWXU) { +/// Ok(_) => println!("created {:?}", tmp_dir2), /// Err(err) => println!("Error creating directory: {}", err), /// } /// } @@ -208,6 +356,17 @@ pub fn getcwd() -> Result<PathBuf> { } } +/// Change the ownership of the file at `path` to be owned by the specified +/// `owner` (user) and `group` (see +/// [chown(2)](http://man7.org/linux/man-pages/man2/lchown.2.html)). +/// +/// The owner/group for the provided path name will not be modified if `None` is +/// provided for that argument. Ownership change will be attempted for the path +/// only if `Some` owner/group is provided. +/// +/// This call may fail under a number of different situations. See [the man +/// pages](http://man7.org/linux/man-pages/man2/lchown.2.html#ERRORS) for +/// additional details. #[inline] pub fn chown<P: ?Sized + NixPath>(path: &P, owner: Option<uid_t>, group: Option<gid_t>) -> Result<()> { let res = try!(path.with_nix_path(|cstr| { @@ -231,6 +390,12 @@ fn to_exec_array(args: &[CString]) -> Vec<*const c_char> { args_p } +/// Replace the current process image with a new one (see +/// [exec(3)](http://man7.org/linux/man-pages/man3/exec.3.html)). +/// +/// See the `::nix::unistd::execve` system call for additional details. `execv` +/// performs the same action but does not allow for customization of the +/// environment for the new process. #[inline] pub fn execv(path: &CString, argv: &[CString]) -> Result<Void> { let args_p = to_exec_array(argv); @@ -242,6 +407,24 @@ pub fn execv(path: &CString, argv: &[CString]) -> Result<Void> { Err(Error::Sys(Errno::last())) } + +/// Replace the current process image with a new one (see +/// [execve(2)](http://man7.org/linux/man-pages/man2/execve.2.html)). +/// +/// The execve system call allows for another process to be "called" which will +/// replace the current process image. That is, this process becomes the new +/// command that is run. On success, this function will not return. Instead, +/// the new program will run until it exits. +/// +/// If an error occurs, this function will return with an indication of the +/// cause of failure. See +/// [execve(2)#errors](http://man7.org/linux/man-pages/man2/execve.2.html#ERRORS) +/// for a list of potential problems that maight cause execv to fail. +/// +/// `::nix::unistd::execv` and `::nix::unistd::execve` take as arguments a slice +/// of `::std::ffi::CString`s for `args` and `env` (for `execve`). Each element +/// in the `args` list is an argument to the new process. Each element in the +/// `env` list should be a string in the form "key=value". #[inline] pub fn execve(path: &CString, args: &[CString], env: &[CString]) -> Result<Void> { let args_p = to_exec_array(args); @@ -254,6 +437,15 @@ pub fn execve(path: &CString, args: &[CString], env: &[CString]) -> Result<Void> Err(Error::Sys(Errno::last())) } +/// Replace the current process image with a new one and replicate shell `PATH` +/// searching behavior (see +/// [exec(3)](http://man7.org/linux/man-pages/man3/exec.3.html)). +/// +/// See `::nix::unistd::execve` for additoinal details. `execvp` behaves the +/// same as execv except that it will examine the `PATH` environment variables +/// for file names not specified with a leading slash. For example, `execv` +/// would not work if "bash" was specified for the path argument, but `execvp` +/// would assuming that a bash executable was on the system `PATH`. #[inline] pub fn execvp(filename: &CString, args: &[CString]) -> Result<Void> { let args_p = to_exec_array(args); @@ -265,12 +457,50 @@ pub fn execvp(filename: &CString, args: &[CString]) -> Result<Void> { Err(Error::Sys(Errno::last())) } +/// Daemonize this process by detaching from the controlling terminal (see +/// [daemon(3)](http://man7.org/linux/man-pages/man3/daemon.3.html)). +/// +/// When a process is launched it is typically associated with a parent and it, +/// in turn, by its controlling terminal/process. In order for a process to run +/// in the "background" it must daemonize itself by detaching itself. Under +/// posix, this is done by doing the following: +/// +/// 1. Parent process (this one) forks +/// 2. Parent process exits +/// 3. Child process continues to run. +/// +/// `nochdir`: +/// +/// * `nochdir = true`: The current working directory after daemonizing will +/// be the current working directory. +/// * `nochdir = false`: The current working directory after daemonizing will +/// be the root direcory, `/`. +/// +/// `noclose`: +/// +/// * `noclose = true`: The process' current stdin, stdout, and stderr file +/// descriptors will remain identical after daemonizing. +/// * `noclose = false`: The process' stdin, stdout, and stderr will point to +/// `/dev/null` after daemonizing. +/// +/// The underlying implementation (in libc) calls both +/// [fork(2)](http://man7.org/linux/man-pages/man2/fork.2.html) and +/// [setsid(2)](http://man7.org/linux/man-pages/man2/setsid.2.html) and, as +/// such, error that could be returned by either of those functions could also +/// show up as errors here. pub fn daemon(nochdir: bool, noclose: bool) -> Result<()> { let res = unsafe { libc::daemon(nochdir as c_int, noclose as c_int) }; Errno::result(res).map(drop) } -pub fn sethostname(name: &[u8]) -> Result<()> { +/// Set the system host name (see +/// [gethostname(2)](http://man7.org/linux/man-pages/man2/gethostname.2.html)). +/// +/// Given a name, attempt to update the system host name to the given string. +/// On some systems, the host name is limited to as few as 64 bytes. An error +/// will be return if the name is not valid or the current process does not have +/// permissions to update the host name. +pub fn sethostname<S: AsRef<OsStr>>(name: S) -> Result<()> { // Handle some differences in type of the len arg across platforms. cfg_if! { if #[cfg(any(target_os = "dragonfly", @@ -282,19 +512,42 @@ pub fn sethostname(name: &[u8]) -> Result<()> { type sethostname_len_t = size_t; } } - let ptr = name.as_ptr() as *const c_char; - let len = name.len() as sethostname_len_t; + let ptr = name.as_ref().as_bytes().as_ptr() as *const c_char; + let len = name.as_ref().len() as sethostname_len_t; let res = unsafe { libc::sethostname(ptr, len) }; Errno::result(res).map(drop) } -pub fn gethostname(name: &mut [u8]) -> Result<()> { - let ptr = name.as_mut_ptr() as *mut c_char; - let len = name.len() as size_t; +/// Get the host name and store it in the provided buffer, returning a pointer +/// the CStr in that buffer on success (see +/// [gethostname(2)](http://man7.org/linux/man-pages/man2/gethostname.2.html)). +/// +/// This function call attempts to get the host name for the running system and +/// store it in a provided buffer. The buffer will be populated with bytes up +/// to the length of the provided slice including a NUL terminating byte. If +/// the hostname is longer than the length provided, no error will be provided. +/// The posix specification does not specify whether implementations will +/// null-terminate in this case, but the nix implementation will ensure that the +/// buffer is null terminated in this case. +/// +/// ```no_run +/// use nix::unistd; +/// +/// let mut buf = [0u8; 64]; +/// let hostname_cstr = unistd::gethostname(&mut buf).expect("Failed getting hostname"); +/// let hostname = hostname_cstr.to_str().expect("Hostname wasn't valid UTF-8"); +/// println!("Hostname: {}", hostname); +/// ``` +pub fn gethostname<'a>(buffer: &'a mut [u8]) -> Result<&'a CStr> { + let ptr = buffer.as_mut_ptr() as *mut c_char; + let len = buffer.len() as size_t; let res = unsafe { libc::gethostname(ptr, len) }; - Errno::result(res).map(drop) + Errno::result(res).map(|_| { + buffer[len - 1] = 0; // ensure always null-terminated + unsafe { CStr::from_ptr(buffer.as_ptr() as *const c_char) } + }) } pub fn close(fd: RawFd) -> Result<()> { @@ -568,6 +821,7 @@ pub fn mkstemp<P: ?Sized + NixPath>(template: &P) -> Result<(RawFd, PathBuf)> { #[cfg(any(target_os = "linux", target_os = "android"))] mod linux { + use libc::{self, uid_t, gid_t}; use sys::syscall::{syscall, SYSPIVOTROOT}; use {Errno, Result, NixPath}; @@ -587,6 +841,38 @@ mod linux { Errno::result(res).map(drop) } + /// Sets the real, effective, and saved uid. + /// ([see setresuid(2)](http://man7.org/linux/man-pages/man2/setresuid.2.html)) + /// + /// * `ruid`: real user id + /// * `euid`: effective user id + /// * `suid`: saved user id + /// * returns: Ok or libc error code. + /// + /// Err is returned if the user doesn't have permission to set this UID. + #[inline] + pub fn setresuid(ruid: uid_t, euid: uid_t, suid: uid_t) -> Result<()> { + let res = unsafe { libc::setresuid(ruid, euid, suid) }; + + Errno::result(res).map(drop) + } + + /// Sets the real, effective, and saved gid. + /// ([see setresuid(2)](http://man7.org/linux/man-pages/man2/setresuid.2.html)) + /// + /// * `rgid`: real user id + /// * `egid`: effective user id + /// * `sgid`: saved user id + /// * returns: Ok or libc error code. + /// + /// Err is returned if the user doesn't have permission to set this GID. + #[inline] + pub fn setresgid(rgid: gid_t, egid: gid_t, sgid: gid_t) -> Result<()> { + let res = unsafe { libc::setresgid(rgid, egid, sgid) }; + + Errno::result(res).map(drop) + } + #[inline] #[cfg(feature = "execvpe")] pub fn execvpe(filename: &CString, args: &[CString], env: &[CString]) -> Result<()> { diff --git a/test/sys/mod.rs b/test/sys/mod.rs index a5f3351d..5e5eed41 100644 --- a/test/sys/mod.rs +++ b/test/sys/mod.rs @@ -1,3 +1,7 @@ +mod test_signal; +#[cfg(any(target_os = "freebsd", target_os = "dragonfly", target_os = "ios", + target_os = "netbsd", target_os = "macos", target_os = "linux"))] +mod test_aio; mod test_socket; mod test_sockopt; mod test_termios; @@ -5,3 +9,6 @@ mod test_ioctl; mod test_wait; mod test_select; mod test_uio; + +#[cfg(target_os = "linux")] +mod test_epoll; diff --git a/test/sys/test_aio.rs b/test/sys/test_aio.rs new file mode 100644 index 00000000..825282f1 --- /dev/null +++ b/test/sys/test_aio.rs @@ -0,0 +1,374 @@ +use libc::c_int; +use nix::{Error, Result}; +use nix::errno::*; +use nix::sys::aio::*; +use nix::sys::signal::*; +use nix::sys::time::{TimeSpec, TimeValLike}; +use std::io::{Write, Read, Seek, SeekFrom}; +use std::os::unix::io::AsRawFd; +use std::{thread, time}; +use tempfile::tempfile; + +// Helper that polls an AioCb for completion or error +fn poll_aio(mut aiocb: &mut AioCb) -> Result<()> { + loop { + let err = aio_error(&mut aiocb); + if err != Err(Error::from(Errno::EINPROGRESS)) { return err; }; + thread::sleep(time::Duration::from_millis(10)); + } +} + +// Tests aio_cancel. We aren't trying to test the OS's implementation, only our +// bindings. So it's sufficient to check that aio_cancel returned any +// AioCancelStat value. +#[test] +fn test_aio_cancel() { + let mut wbuf = "CDEF".to_string().into_bytes(); + + let f = tempfile().unwrap(); + let mut aiocb = AioCb::from_mut_slice( f.as_raw_fd(), + 0, //offset + &mut wbuf, + 0, //priority + SigevNotify::SigevNone, + LioOpcode::LIO_NOP); + aio_write(&mut aiocb).unwrap(); + let err = aio_error(&mut aiocb); + assert!(err == Ok(()) || err == Err(Error::from(Errno::EINPROGRESS))); + + let cancelstat = aio_cancel(f.as_raw_fd(), Some(&mut aiocb)); + assert!(cancelstat.is_ok()); + + // Wait for aiocb to complete, but don't care whether it succeeded + let _ = poll_aio(&mut aiocb); + let _ = aio_return(&mut aiocb); +} + +// Tests using aio_cancel for all outstanding IOs. +#[test] +fn test_aio_cancel_all() { + let mut wbuf = "CDEF".to_string().into_bytes(); + + let f = tempfile().unwrap(); + let mut aiocb = AioCb::from_mut_slice( f.as_raw_fd(), + 0, //offset + &mut wbuf, + 0, //priority + SigevNotify::SigevNone, + LioOpcode::LIO_NOP); + aio_write(&mut aiocb).unwrap(); + let err = aio_error(&mut aiocb); + assert!(err == Ok(()) || err == Err(Error::from(Errno::EINPROGRESS))); + + let cancelstat = aio_cancel(f.as_raw_fd(), None); + assert!(cancelstat.is_ok()); + + // Wait for aiocb to complete, but don't care whether it succeeded + let _ = poll_aio(&mut aiocb); + let _ = aio_return(&mut aiocb); +} + +#[test] +fn test_aio_fsync() { + const INITIAL: &'static [u8] = b"abcdef123456"; + let mut f = tempfile().unwrap(); + f.write(INITIAL).unwrap(); + let mut aiocb = AioCb::from_fd( f.as_raw_fd(), + 0, //priority + SigevNotify::SigevNone); + let err = aio_fsync(AioFsyncMode::O_SYNC, &mut aiocb); + assert!(err.is_ok()); + poll_aio(&mut aiocb).unwrap(); + aio_return(&mut aiocb).unwrap(); +} + + +#[test] +fn test_aio_suspend() { + const INITIAL: &'static [u8] = b"abcdef123456"; + const WBUF: &'static [u8] = b"CDEF"; + let timeout = TimeSpec::seconds(10); + let mut rbuf = vec![0; 4]; + let mut f = tempfile().unwrap(); + f.write(INITIAL).unwrap(); + + let mut wcb = unsafe { + AioCb::from_slice( f.as_raw_fd(), + 2, //offset + &mut WBUF, + 0, //priority + SigevNotify::SigevNone, + LioOpcode::LIO_WRITE) + }; + + let mut rcb = AioCb::from_mut_slice( f.as_raw_fd(), + 8, //offset + &mut rbuf, + 0, //priority + SigevNotify::SigevNone, + LioOpcode::LIO_READ); + aio_write(&mut wcb).unwrap(); + aio_read(&mut rcb).unwrap(); + loop { + { + let cbbuf = [&wcb, &rcb]; + assert!(aio_suspend(&cbbuf[..], Some(timeout)).is_ok()); + } + if aio_error(&mut rcb) != Err(Error::from(Errno::EINPROGRESS)) && + aio_error(&mut wcb) != Err(Error::from(Errno::EINPROGRESS)) { + break + } + } + + assert!(aio_return(&mut wcb).unwrap() as usize == WBUF.len()); + assert!(aio_return(&mut rcb).unwrap() as usize == WBUF.len()); +} + +// Test a simple aio operation with no completion notification. We must poll +// for completion +#[test] +fn test_aio_read() { + const INITIAL: &'static [u8] = b"abcdef123456"; + let mut rbuf = vec![0; 4]; + const EXPECT: &'static [u8] = b"cdef"; + let mut f = tempfile().unwrap(); + f.write(INITIAL).unwrap(); + { + let mut aiocb = AioCb::from_mut_slice( f.as_raw_fd(), + 2, //offset + &mut rbuf, + 0, //priority + SigevNotify::SigevNone, + LioOpcode::LIO_NOP); + aio_read(&mut aiocb).unwrap(); + + let err = poll_aio(&mut aiocb); + assert!(err == Ok(())); + assert!(aio_return(&mut aiocb).unwrap() as usize == EXPECT.len()); + } + + assert!(rbuf == EXPECT); +} + +// Test a simple aio operation with no completion notification. We must poll +// for completion. Unlike test_aio_read, this test uses AioCb::from_slice +#[test] +fn test_aio_write() { + const INITIAL: &'static [u8] = b"abcdef123456"; + const WBUF: &'static [u8] = b"CDEF"; //"CDEF".to_string().into_bytes(); + let mut rbuf = Vec::new(); + const EXPECT: &'static [u8] = b"abCDEF123456"; + + let mut f = tempfile().unwrap(); + f.write(INITIAL).unwrap(); + let mut aiocb = unsafe { + AioCb::from_slice( f.as_raw_fd(), + 2, //offset + &WBUF, + 0, //priority + SigevNotify::SigevNone, + LioOpcode::LIO_NOP) + }; + aio_write(&mut aiocb).unwrap(); + + let err = poll_aio(&mut aiocb); + assert!(err == Ok(())); + assert!(aio_return(&mut aiocb).unwrap() as usize == WBUF.len()); + + f.seek(SeekFrom::Start(0)).unwrap(); + let len = f.read_to_end(&mut rbuf).unwrap(); + assert!(len == EXPECT.len()); + assert!(rbuf == EXPECT); +} + +// XXX: should be sig_atomic_t, but rust's libc doesn't define that yet +static mut signaled: i32 = 0; + +extern fn sigfunc(_: c_int) { + // It's a pity that Rust can't understand that static mutable sig_atomic_t + // variables can be safely accessed + unsafe { signaled = 1 }; +} + +// Test an aio operation with completion delivered by a signal +#[test] +fn test_aio_write_sigev_signal() { + let sa = SigAction::new(SigHandler::Handler(sigfunc), + SA_RESETHAND, + SigSet::empty()); + unsafe {signaled = 0 }; + unsafe { sigaction(Signal::SIGUSR2, &sa) }.unwrap(); + + const INITIAL: &'static [u8] = b"abcdef123456"; + const WBUF: &'static [u8] = b"CDEF"; + let mut rbuf = Vec::new(); + const EXPECT: &'static [u8] = b"abCDEF123456"; + + let mut f = tempfile().unwrap(); + f.write(INITIAL).unwrap(); + let mut aiocb = unsafe { + AioCb::from_slice( f.as_raw_fd(), + 2, //offset + &WBUF, + 0, //priority + SigevNotify::SigevSignal { + signal: Signal::SIGUSR2, + si_value: 0 //TODO: validate in sigfunc + }, + LioOpcode::LIO_NOP) + }; + aio_write(&mut aiocb).unwrap(); + while unsafe { signaled == 0 } { + thread::sleep(time::Duration::from_millis(10)); + } + + assert!(aio_return(&mut aiocb).unwrap() as usize == WBUF.len()); + f.seek(SeekFrom::Start(0)).unwrap(); + let len = f.read_to_end(&mut rbuf).unwrap(); + assert!(len == EXPECT.len()); + assert!(rbuf == EXPECT); +} + +// Test lio_listio with LIO_WAIT, so all AIO ops should be complete by the time +// lio_listio returns. +#[test] +#[cfg(not(any(target_os = "ios", target_os = "macos")))] +fn test_lio_listio_wait() { + const INITIAL: &'static [u8] = b"abcdef123456"; + const WBUF: &'static [u8] = b"CDEF"; + let mut rbuf = vec![0; 4]; + let mut rbuf2 = Vec::new(); + const EXPECT: &'static [u8] = b"abCDEF123456"; + let mut f = tempfile().unwrap(); + + f.write(INITIAL).unwrap(); + + { + let mut wcb = unsafe { + AioCb::from_slice( f.as_raw_fd(), + 2, //offset + &WBUF, + 0, //priority + SigevNotify::SigevNone, + LioOpcode::LIO_WRITE) + }; + + let mut rcb = AioCb::from_mut_slice( f.as_raw_fd(), + 8, //offset + &mut rbuf, + 0, //priority + SigevNotify::SigevNone, + LioOpcode::LIO_READ); + let err = lio_listio(LioMode::LIO_WAIT, &[&mut wcb, &mut rcb], SigevNotify::SigevNone); + err.expect("lio_listio failed"); + + assert!(aio_return(&mut wcb).unwrap() as usize == WBUF.len()); + assert!(aio_return(&mut rcb).unwrap() as usize == WBUF.len()); + } + assert!(rbuf == b"3456"); + + f.seek(SeekFrom::Start(0)).unwrap(); + let len = f.read_to_end(&mut rbuf2).unwrap(); + assert!(len == EXPECT.len()); + assert!(rbuf2 == EXPECT); +} + +// Test lio_listio with LIO_NOWAIT and no SigEvent, so we must use some other +// mechanism to check for the individual AioCb's completion. +#[test] +#[cfg(not(any(target_os = "ios", target_os = "macos")))] +fn test_lio_listio_nowait() { + const INITIAL: &'static [u8] = b"abcdef123456"; + const WBUF: &'static [u8] = b"CDEF"; + let mut rbuf = vec![0; 4]; + let mut rbuf2 = Vec::new(); + const EXPECT: &'static [u8] = b"abCDEF123456"; + let mut f = tempfile().unwrap(); + + f.write(INITIAL).unwrap(); + + { + let mut wcb = unsafe { + AioCb::from_slice( f.as_raw_fd(), + 2, //offset + &WBUF, + 0, //priority + SigevNotify::SigevNone, + LioOpcode::LIO_WRITE) + }; + + let mut rcb = AioCb::from_mut_slice( f.as_raw_fd(), + 8, //offset + &mut rbuf, + 0, //priority + SigevNotify::SigevNone, + LioOpcode::LIO_READ); + let err = lio_listio(LioMode::LIO_NOWAIT, &[&mut wcb, &mut rcb], SigevNotify::SigevNone); + err.expect("lio_listio failed"); + + poll_aio(&mut wcb).unwrap(); + poll_aio(&mut rcb).unwrap(); + assert!(aio_return(&mut wcb).unwrap() as usize == WBUF.len()); + assert!(aio_return(&mut rcb).unwrap() as usize == WBUF.len()); + } + assert!(rbuf == b"3456"); + + f.seek(SeekFrom::Start(0)).unwrap(); + let len = f.read_to_end(&mut rbuf2).unwrap(); + assert!(len == EXPECT.len()); + assert!(rbuf2 == EXPECT); +} + +// Test lio_listio with LIO_NOWAIT and a SigEvent to indicate when all AioCb's +// are complete. +#[test] +#[cfg(not(any(target_os = "ios", target_os = "macos")))] +fn test_lio_listio_signal() { + const INITIAL: &'static [u8] = b"abcdef123456"; + const WBUF: &'static [u8] = b"CDEF"; + let mut rbuf = vec![0; 4]; + let mut rbuf2 = Vec::new(); + const EXPECT: &'static [u8] = b"abCDEF123456"; + let mut f = tempfile().unwrap(); + let sa = SigAction::new(SigHandler::Handler(sigfunc), + SA_RESETHAND, + SigSet::empty()); + let sigev_notify = SigevNotify::SigevSignal { signal: Signal::SIGUSR2, + si_value: 0 }; + + f.write(INITIAL).unwrap(); + + { + let mut wcb = unsafe { + AioCb::from_slice( f.as_raw_fd(), + 2, //offset + &WBUF, + 0, //priority + SigevNotify::SigevNone, + LioOpcode::LIO_WRITE) + }; + + let mut rcb = AioCb::from_mut_slice( f.as_raw_fd(), + 8, //offset + &mut rbuf, + 0, //priority + SigevNotify::SigevNone, + LioOpcode::LIO_READ); + unsafe {signaled = 0 }; + unsafe { sigaction(Signal::SIGUSR2, &sa) }.unwrap(); + let err = lio_listio(LioMode::LIO_NOWAIT, &[&mut wcb, &mut rcb], sigev_notify); + err.expect("lio_listio failed"); + while unsafe { signaled == 0 } { + thread::sleep(time::Duration::from_millis(10)); + } + + assert!(aio_return(&mut wcb).unwrap() as usize == WBUF.len()); + assert!(aio_return(&mut rcb).unwrap() as usize == WBUF.len()); + } + assert!(rbuf == b"3456"); + + f.seek(SeekFrom::Start(0)).unwrap(); + let len = f.read_to_end(&mut rbuf2).unwrap(); + assert!(len == EXPECT.len()); + assert!(rbuf2 == EXPECT); +} diff --git a/test/sys/test_epoll.rs b/test/sys/test_epoll.rs new file mode 100644 index 00000000..a73fea6d --- /dev/null +++ b/test/sys/test_epoll.rs @@ -0,0 +1,24 @@ +use nix::sys::epoll::{EpollCreateFlags, EpollOp, EpollEvent}; +use nix::sys::epoll::{EPOLLIN, EPOLLERR}; +use nix::sys::epoll::{epoll_create1, epoll_ctl}; +use nix::{Error, Errno}; + +#[test] +pub fn test_epoll_errno() { + let efd = epoll_create1(EpollCreateFlags::empty()).unwrap(); + let result = epoll_ctl(efd, EpollOp::EpollCtlDel, 1, None); + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), Error::Sys(Errno::ENOENT)); + + let result = epoll_ctl(efd, EpollOp::EpollCtlAdd, 1, None); + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), Error::Sys(Errno::EINVAL)); +} + +#[test] +pub fn test_epoll_ctl() { + let efd = epoll_create1(EpollCreateFlags::empty()).unwrap(); + let mut event = EpollEvent::new(EPOLLIN | EPOLLERR, 1); + epoll_ctl(efd, EpollOp::EpollCtlAdd, 1, &mut event).unwrap(); + epoll_ctl(efd, EpollOp::EpollCtlDel, 1, None).unwrap(); +} diff --git a/test/sys/test_ioctl.rs b/test/sys/test_ioctl.rs index 94eca447..1d9fbcc8 100644 --- a/test/sys/test_ioctl.rs +++ b/test/sys/test_ioctl.rs @@ -1,9 +1,7 @@ #![allow(dead_code)] -#![cfg(target_os = "linux")] // no ioctl support for osx yet - // Simple tests to ensure macro generated fns compile -ioctl!(bad do_bad with 0x1234); +ioctl!(do_bad with 0x1234); ioctl!(none do_none with 0, 0); ioctl!(read read_test with 0, 0; u32); ioctl!(write write_test with 0, 0; u64); @@ -15,44 +13,97 @@ ioctl!(readwrite buf readwritebuf_test with 0, 0; u32); // See C code for source of values for op calculations: // https://gist.github.com/posborne/83ea6880770a1aef332e -#[test] -fn test_op_none() { - assert_eq!(io!(b'q', 10), 0x0000710A); - assert_eq!(io!(b'a', 255), 0x000061FF); -} +#[cfg(any(target_os = "linux", target_os = "android"))] +mod linux { + #[test] + fn test_op_none() { + assert_eq!(io!(b'q', 10), 0x0000710A); + assert_eq!(io!(b'a', 255), 0x000061FF); + } -#[test] -fn test_op_write() { - assert_eq!(iow!(b'z', 10, 1), 0x40017A0A); - assert_eq!(iow!(b'z', 10, 512), 0x42007A0A); -} + #[test] + fn test_op_write() { + assert_eq!(iow!(b'z', 10, 1), 0x40017A0A); + assert_eq!(iow!(b'z', 10, 512), 0x42007A0A); + } -#[cfg(target_pointer_width = "64")] -#[test] -fn test_op_write_64() { - assert_eq!(iow!(b'z', 10, (1 as u64) << 32), 0x40007A0A); -} + #[cfg(target_pointer_width = "64")] + #[test] + fn test_op_write_64() { + assert_eq!(iow!(b'z', 10, (1 as u64) << 32), 0x40007A0A); + } -#[test] -fn test_op_read() { - assert_eq!(ior!(b'z', 10, 1), 0x80017A0A); - assert_eq!(ior!(b'z', 10, 512), 0x82007A0A); -} + #[test] + fn test_op_read() { + assert_eq!(ior!(b'z', 10, 1), 0x80017A0A); + assert_eq!(ior!(b'z', 10, 512), 0x82007A0A); + } -#[cfg(target_pointer_width = "64")] -#[test] -fn test_op_read_64() { - assert_eq!(ior!(b'z', 10, (1 as u64) << 32), 0x80007A0A); -} + #[cfg(target_pointer_width = "64")] + #[test] + fn test_op_read_64() { + assert_eq!(ior!(b'z', 10, (1 as u64) << 32), 0x80007A0A); + } + + #[test] + fn test_op_read_write() { + assert_eq!(iorw!(b'z', 10, 1), 0xC0017A0A); + assert_eq!(iorw!(b'z', 10, 512), 0xC2007A0A); + } -#[test] -fn test_op_read_write() { - assert_eq!(iorw!(b'z', 10, 1), 0xC0017A0A); - assert_eq!(iorw!(b'z', 10, 512), 0xC2007A0A); + #[cfg(target_pointer_width = "64")] + #[test] + fn test_op_read_write_64() { + assert_eq!(iorw!(b'z', 10, (1 as u64) << 32), 0xC0007A0A); + } } -#[cfg(target_pointer_width = "64")] -#[test] -fn test_op_read_write_64() { - assert_eq!(iorw!(b'z', 10, (1 as u64) << 32), 0xC0007A0A); +#[cfg(any(target_os = "macos", + target_os = "ios", + target_os = "netbsd", + target_os = "openbsd", + target_os = "freebsd", + target_os = "dragonfly"))] +mod bsd { + #[test] + fn test_op_none() { + assert_eq!(io!(b'q', 10), 0x2000710A); + assert_eq!(io!(b'a', 255), 0x200061FF); + } + + #[test] + fn test_op_write() { + assert_eq!(iow!(b'z', 10, 1), 0x80017A0A); + assert_eq!(iow!(b'z', 10, 512), 0x82007A0A); + } + + #[cfg(target_pointer_width = "64")] + #[test] + fn test_op_write_64() { + assert_eq!(iow!(b'z', 10, (1 as u64) << 32), 0x80007A0A); + } + + #[test] + fn test_op_read() { + assert_eq!(ior!(b'z', 10, 1), 0x40017A0A); + assert_eq!(ior!(b'z', 10, 512), 0x42007A0A); + } + + #[cfg(target_pointer_width = "64")] + #[test] + fn test_op_read_64() { + assert_eq!(ior!(b'z', 10, (1 as u64) << 32), 0x40007A0A); + } + + #[test] + fn test_op_read_write() { + assert_eq!(iorw!(b'z', 10, 1), 0xC0017A0A); + assert_eq!(iorw!(b'z', 10, 512), 0xC2007A0A); + } + + #[cfg(target_pointer_width = "64")] + #[test] + fn test_op_read_write_64() { + assert_eq!(iorw!(b'z', 10, (1 as u64) << 32), 0xC0007A0A); + } } diff --git a/test/sys/test_select.rs b/test/sys/test_select.rs index 68908670..53990f65 100644 --- a/test/sys/test_select.rs +++ b/test/sys/test_select.rs @@ -1,5 +1,5 @@ use nix::sys::select::{FdSet, FD_SETSIZE, select}; -use nix::sys::time::TimeVal; +use nix::sys::time::{TimeVal, TimeValLike}; use nix::unistd::{write, pipe}; #[test] @@ -41,7 +41,7 @@ fn test_select() { fd_set.insert(r1); fd_set.insert(r2); - let mut timeout = TimeVal::seconds(1); + let mut timeout = TimeVal::seconds(10); assert_eq!(1, select(r2 + 1, Some(&mut fd_set), None, diff --git a/test/sys/test_signal.rs b/test/sys/test_signal.rs new file mode 100644 index 00000000..4084a0da --- /dev/null +++ b/test/sys/test_signal.rs @@ -0,0 +1,7 @@ +use nix::unistd::*; +use nix::sys::signal::*; + +#[test] +fn test_kill_none() { + kill(getpid(), None).ok().expect("Should be able to send signal to myself."); +} diff --git a/test/sys/test_socket.rs b/test/sys/test_socket.rs index bada1120..b5465aa0 100644 --- a/test/sys/test_socket.rs +++ b/test/sys/test_socket.rs @@ -3,8 +3,7 @@ use std::mem; use std::net::{self, Ipv6Addr, SocketAddr, SocketAddrV6}; use std::path::Path; use std::str::FromStr; -use std::os::unix::io::{AsRawFd, RawFd}; -use ports::localhost; +use std::os::unix::io::RawFd; use libc::c_char; #[test] @@ -64,13 +63,18 @@ pub fn test_path_to_sock_addr() { #[test] pub fn test_getsockname() { - use std::net::TcpListener; - - let addr = localhost(); - let sock = TcpListener::bind(&*addr).unwrap(); - let res = getsockname(sock.as_raw_fd()).unwrap(); - - assert_eq!(addr, res.to_str()); + use nix::sys::socket::{socket, AddressFamily, SockType, SockFlag}; + use nix::sys::socket::{bind, SockAddr}; + use tempdir::TempDir; + + let tempdir = TempDir::new("test_getsockname").unwrap(); + let sockname = tempdir.path().join("sock"); + let sock = socket(AddressFamily::Unix, SockType::Stream, SockFlag::empty(), + 0).expect("socket failed"); + let sockaddr = SockAddr::new_unix(&sockname).unwrap(); + bind(sock, &sockaddr).expect("bind failed"); + assert_eq!(sockaddr.to_str(), + getsockname(sock).expect("getsockname failed").to_str()); } #[test] @@ -140,3 +144,56 @@ pub fn test_scm_rights() { close(received_r).unwrap(); close(w).unwrap(); } + +// Test creating and using named unix domain sockets +#[test] +pub fn test_unixdomain() { + use nix::sys::socket::{AddressFamily, SockType, SockFlag}; + use nix::sys::socket::{bind, socket, connect, listen, accept, SockAddr}; + use nix::unistd::{read, write, close}; + use std::thread; + use tempdir::TempDir; + + let tempdir = TempDir::new("test_unixdomain").unwrap(); + let sockname = tempdir.path().join("sock"); + let s1 = socket(AddressFamily::Unix, SockType::Stream, + SockFlag::empty(), 0).expect("socket failed"); + let sockaddr = SockAddr::new_unix(&sockname).unwrap(); + bind(s1, &sockaddr).expect("bind failed"); + listen(s1, 10).expect("listen failed"); + + let thr = thread::spawn(move || { + let s2 = socket(AddressFamily::Unix, SockType::Stream, + SockFlag::empty(), 0).expect("socket failed"); + connect(s2, &sockaddr).expect("connect failed"); + write(s2, b"hello").expect("write failed"); + close(s2).unwrap(); + }); + + let s3 = accept(s1).expect("accept failed"); + + let mut buf = [0;5]; + read(s3, &mut buf).unwrap(); + close(s3).unwrap(); + close(s1).unwrap(); + thr.join().unwrap(); + + assert_eq!(&buf[..], b"hello"); +} + +// Test creating and using named system control sockets +#[cfg(any(target_os = "macos", target_os = "ios"))] +#[test] +pub fn test_syscontrol() { + use nix::{Errno, Error}; + use nix::sys::socket::{AddressFamily, SockType, SockFlag}; + use nix::sys::socket::{socket, SockAddr}; + use nix::sys::socket::SYSPROTO_CONTROL; + + let fd = socket(AddressFamily::System, SockType::Datagram, SockFlag::empty(), SYSPROTO_CONTROL).expect("socket failed"); + let _sockaddr = SockAddr::new_sys_control(fd, "com.apple.net.utun_control", 0).expect("resolving sys_control name failed"); + assert_eq!(SockAddr::new_sys_control(fd, "foo.bar.lol", 0).err(), Some(Error::Sys(Errno::ENOENT))); + + // requires root privileges + // connect(fd, &sockaddr).expect("connect failed"); +} diff --git a/test/sys/test_uio.rs b/test/sys/test_uio.rs index 27de48be..90cda56f 100644 --- a/test/sys/test_uio.rs +++ b/test/sys/test_uio.rs @@ -2,10 +2,11 @@ use nix::sys::uio::*; use nix::unistd::*; use rand::{thread_rng, Rng}; use std::{cmp, iter}; -use std::fs::{OpenOptions, remove_file}; +use std::fs::{OpenOptions}; use std::os::unix::io::AsRawFd; use tempdir::TempDir; +use tempfile::tempfile; #[test] fn test_writev() { @@ -99,9 +100,7 @@ fn test_readv() { fn test_pwrite() { use std::io::Read; - let path = "pwrite_test_file"; - let mut file = OpenOptions::new().write(true).read(true).create(true) - .truncate(true).open(path).unwrap(); + let mut file = tempfile().unwrap(); let buf = [1u8;8]; assert_eq!(Ok(8), pwrite(file.as_raw_fd(), &buf, 8)); let mut file_content = Vec::new(); @@ -109,8 +108,6 @@ fn test_pwrite() { let mut expected = vec![0u8;8]; expected.extend(vec![1;8]); assert_eq!(file_content, expected); - - remove_file(path).unwrap(); } #[test] diff --git a/test/sys/test_wait.rs b/test/sys/test_wait.rs index c2112bac..d8ac94e4 100644 --- a/test/sys/test_wait.rs +++ b/test/sys/test_wait.rs @@ -9,7 +9,7 @@ fn test_wait_signal() { match fork() { Ok(Child) => pause().unwrap_or(()), Ok(Parent { child }) => { - kill(child, SIGKILL).ok().expect("Error: Kill Failed"); + kill(child, Some(SIGKILL)).ok().expect("Error: Kill Failed"); assert_eq!(waitpid(child, None), Ok(WaitStatus::Signaled(child, SIGKILL, false))); }, // panic, fork should never fail unless there is a serious problem with the OS diff --git a/test/test.rs b/test/test.rs index 045f8f18..d10970d6 100644 --- a/test/test.rs +++ b/test/test.rs @@ -22,30 +22,6 @@ mod test_mq; #[cfg(any(target_os = "linux", target_os = "macos"))] mod test_poll; -mod ports { - use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT}; - use std::sync::atomic::Ordering::SeqCst; - - // Helper for getting a unique port for the task run - // TODO: Reuse ports to not spam the system - static mut NEXT_PORT: AtomicUsize = ATOMIC_USIZE_INIT; - const FIRST_PORT: usize = 18080; - - pub fn next_port() -> usize { - unsafe { - // If the atomic was never used, set it to the initial port - NEXT_PORT.compare_and_swap(0, FIRST_PORT, SeqCst); - - // Get and increment the port list - NEXT_PORT.fetch_add(1, SeqCst) - } - } - - pub fn localhost() -> String { - format!("127.0.0.1:{}", next_port()) - } -} - use nixtest::assert_size_of; #[test] diff --git a/test/test_mount.rs b/test/test_mount.rs index 7ddbce9f..42670216 100644 --- a/test/test_mount.rs +++ b/test/test_mount.rs @@ -128,12 +128,6 @@ exit 23"; } pub fn test_mount_bind() { - use std::env; - if env::var("CI").is_ok() && env::var("TRAVIS").is_ok() { - print!("Travis does not allow bind mounts, skipping."); - return; - } - let tempdir = TempDir::new("nix-test_mount") .unwrap_or_else(|e| panic!("tempdir failed: {}", e)); let file_name = "test"; diff --git a/test/test_unistd.rs b/test/test_unistd.rs index 856693f6..76ab442a 100644 --- a/test/test_unistd.rs +++ b/test/test_unistd.rs @@ -6,6 +6,7 @@ use nix::sys::wait::*; use nix::sys::stat; use std::iter; use std::ffi::CString; +use std::fs::File; use std::io::{Write, Read}; use std::os::unix::prelude::*; use std::env::current_dir; @@ -142,20 +143,40 @@ macro_rules! execve_test_factory( ); #[test] +fn test_fchdir() { + let tmpdir = TempDir::new("test_fchdir").unwrap(); + let tmpdir_path = tmpdir.path().canonicalize().unwrap(); + let tmpdir_fd = File::open(&tmpdir_path).unwrap().into_raw_fd(); + let olddir_path = getcwd().unwrap(); + let olddir_fd = File::open(&olddir_path).unwrap().into_raw_fd(); + + assert!(fchdir(tmpdir_fd).is_ok()); + assert_eq!(getcwd().unwrap(), tmpdir_path); + + assert!(fchdir(olddir_fd).is_ok()); + assert_eq!(getcwd().unwrap(), olddir_path); + + assert!(close(olddir_fd).is_ok()); + assert!(close(tmpdir_fd).is_ok()); +} + +#[test] fn test_getcwd() { - let mut tmp_dir = TempDir::new("test_getcwd").unwrap().into_path(); - assert!(chdir(tmp_dir.as_path()).is_ok()); + let tmp_dir = TempDir::new("test_getcwd").unwrap(); + assert!(chdir(tmp_dir.path()).is_ok()); assert_eq!(getcwd().unwrap(), current_dir().unwrap()); // make path 500 chars longer so that buffer doubling in getcwd kicks in. // Note: One path cannot be longer than 255 bytes (NAME_MAX) // whole path cannot be longer than PATH_MAX (usually 4096 on linux, 1024 on macos) + let mut inner_tmp_dir = tmp_dir.path().to_path_buf(); for _ in 0..5 { let newdir = iter::repeat("a").take(100).collect::<String>(); - tmp_dir.push(newdir); - assert!(mkdir(tmp_dir.as_path(), stat::S_IRWXU).is_ok()); + //inner_tmp_dir = inner_tmp_dir.join(newdir).path(); + inner_tmp_dir.push(newdir); + assert!(mkdir(inner_tmp_dir.as_path(), stat::S_IRWXU).is_ok()); } - assert!(chdir(tmp_dir.as_path()).is_ok()); + assert!(chdir(inner_tmp_dir.as_path()).is_ok()); assert_eq!(getcwd().unwrap(), current_dir().unwrap()); } |