diff options
-rw-r--r-- | .travis.yml | 36 | ||||
-rw-r--r-- | CHANGELOG.md | 26 | ||||
-rw-r--r-- | Cargo.toml | 4 | ||||
-rw-r--r-- | README.md | 32 | ||||
-rw-r--r-- | src/macros.rs | 178 | ||||
-rw-r--r-- | src/pty.rs | 86 | ||||
-rw-r--r-- | src/sys/mod.rs | 1 | ||||
-rw-r--r-- | src/sys/socket/mod.rs | 18 | ||||
-rw-r--r-- | src/sys/termios.rs | 1416 | ||||
-rw-r--r-- | src/unistd.rs | 654 | ||||
-rw-r--r-- | test/sys/test_socket.rs | 39 | ||||
-rw-r--r-- | test/sys/test_termios.rs | 139 | ||||
-rw-r--r-- | test/test_pty.rs | 19 | ||||
-rw-r--r-- | test/test_ptymaster_drop.rs | 21 | ||||
-rw-r--r-- | test/test_unistd.rs | 32 |
15 files changed, 2006 insertions, 695 deletions
diff --git a/.travis.yml b/.travis.yml index f0e94c67..92234237 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,6 @@ dist: trusty language: rust services: docker -sudo: required env: global: @@ -96,35 +95,8 @@ matrix: # Testing beta on main targets - env: TARGET=x86_64-unknown-linux-gnu rust: beta - - env: TARGET=x86_64-apple-darwin - os: osx - rust: beta - - # Testing nightly on main targets (allowed to fail) - - env: TARGET=x86_64-unknown-linux-gnu - rust: nightly - - env: TARGET=x86_64-apple-darwin - os: osx - rust: nightly allow_failures: - # iOS is still being worked on, so for now don't block on compilation failures - - env: TARGET=aarch64-apple-ios DISABLE_TESTS=1 - rust: 1.13.0 - os: osx - - env: TARGET=armv7-apple-ios DISABLE_TESTS=1 - rust: 1.13.0 - os: osx - - env: TARGET=armv7s-apple-ios DISABLE_TESTS=1 - rust: 1.13.0 - os: osx - - env: TARGET=i386-apple-ios DISABLE_TESTS=1 - rust: 1.13.0 - os: osx - - env: TARGET=x86_64-apple-ios DISABLE_TESTS=1 - rust: 1.13.0 - os: osx - # Planning to add these targets, but they can fail for now - env: TARGET=mips64-unknown-linux-gnuabi64 rust: 1.13.0 @@ -135,14 +107,6 @@ matrix: - env: TARGET=s390x-unknown-linux-gnu rust: 1.13.0 - # Failures for nightlies may be because of compiler bugs, so don't fail the - # build if these fail. - - env: TARGET=x86_64-unknown-linux-gnu - rust: nightly - - env: TARGET=x86_64-apple-darwin - os: osx - rust: nightly - before_install: set -e install: diff --git a/CHANGELOG.md b/CHANGELOG.md index 15f42a45..8179de26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added +- Added `sysconf`, `pathconf`, and `fpathconf` + ([#630](https://github.com/nix-rust/nix/pull/630) - Added `sys::signal::SigAction::{ flags, mask, handler}` ([#611](https://github.com/nix-rust/nix/pull/609) - Added `nix::sys::pthread::pthread_self` @@ -21,30 +23,32 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Added `nix::ptrace::{ptrace_get_data, ptrace_getsiginfo, ptrace_setsiginfo and nix::Error::UnsupportedOperation}` ([#614](https://github.com/nix-rust/nix/pull/614)) +- Added `cfmakeraw`, `cfsetspeed`, and `tcgetsid`. ([#527](https://github.com/nix-rust/nix/pull/527)) ### Changed -- Changed ioctl! write to take argument by value instead as pointer. - If you need a pointer as argument, use ioctl! write buf. +- Changed `ioctl!(write ...)` to take argument by value instead as pointer. + If you need a pointer as argument, use `ioctl!(write buf ...)`. ([#626](https://github.com/nix-rust/nix/pull/626)) - Marked `sys::mman::{ mmap, munmap, madvise, munlock, msync }` as unsafe. ([#559](https://github.com/nix-rust/nix/pull/559)) -- Minimum supported Rust version is now 1.13 +- Minimum supported Rust version is now 1.13. - Removed `revents` argument from `PollFd::new()` as it's an output argument and will be overwritten regardless of value. ([#542](https://github.com/nix-rust/nix/pull/542)) - Changed type signature of `sys::select::FdSet::contains` to make `self` immutable ([#564](https://github.com/nix-rust/nix/pull/564)) -- Changed type of `sched::sched_setaffinity`'s `pid` argument to `pid_t` -- Introduced wrapper types for gid_t, pid_t, and uid_t as Gid, Pid, and Uid +- Introduced wrapper types for `gid_t`, `pid_t`, and `uid_t` as `Gid`, `Pid`, and `Uid` respectively. Various functions have been changed to use these new types as arguments. ([#629](https://github.com/nix-rust/nix/pull/629)) -- Promoted all Android targets to Tier 2 support +- Fixed compilation on all Android and iOS targets ([#527](https://github.com/nix-rust/nix/pull/527)) + and promoted them to Tier 2 support. - `nix::sys::statfs::{statfs,fstatfs}` uses statfs definition from `libc::statfs` instead of own linux specific type `nix::sys::Statfs`. Also file system type constants like `nix::sys::statfs::ADFS_SUPER_MAGIC` were removed in favor of the libc equivalent. ([#561](https://github.com/nix-rust/nix/pull/561)) +- Revised the termios API including additional tests and documentation and exposed it on iOS. ([#527](https://github.com/nix-rust/nix/pull/527)) ### Removed -- Removed io::Error from nix::Error and conversion from nix::Error to Errno +- Removed `io::Error` from `nix::Error` and the conversion from `nix::Error` to `Errno` ([#614](https://github.com/nix-rust/nix/pull/614)) ### Fixed @@ -60,6 +64,10 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Added `nix::ptrace` on all Linux-kernel-based platforms [#624](https://github.com/nix-rust/nix/pull/624). Previously it was only available on x86, x86-64, and ARM, and also not on Android. +- Fixed `sys::socket::sendmsg` with zero entry `cmsgs` parameter. + ([#623](https://github.com/nix-rust/nix/pull/623)) +- Multiple constants related to the termios API have now been properly defined for + all supported platforms. ([#527](https://github.com/nix-rust/nix/pull/527)) ## [0.8.1] 2017-04-16 @@ -73,7 +81,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Added `::nix::sys::termios::BaudRate` enum to provide portable baudrate values. ([#518](https://github.com/nix-rust/nix/pull/518)) - Added a new `WaitStatus::PtraceEvent` to support ptrace events on Linux - and Android ([([#438](https://github.com/nix-rust/nix/pull/438)) + and Android ([#438](https://github.com/nix-rust/nix/pull/438)) - Added support for POSIX AIO ([#483](https://github.com/nix-rust/nix/pull/483)) ([#506](https://github.com/nix-rust/nix/pull/506)) @@ -109,7 +117,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Added `ppoll` in `::nix::poll` ([#520](https://github.com/nix-rust/nix/pull/520)) - Added support for getting and setting pipe size with fcntl(2) on Linux - ([#540](https://github.com/nix-rust/nix/pull/540) + ([#540](https://github.com/nix-rust/nix/pull/540)) ### Changed - `::nix::sys::termios::{cfgetispeed, cfsetispeed, cfgetospeed, cfsetospeed}` @@ -48,3 +48,7 @@ test = true name = "test-mount" path = "test/test_mount.rs" harness = false + +[[test]] +name = "test-ptymaster-drop" +path = "test/test_ptymaster_drop.rs" @@ -50,41 +50,41 @@ The following targets are all supported by nix on Rust 1.13.0 or newer (unless otherwise noted): Tier 1: - * i686-unknown-linux-gnu - * x86_64-unknown-linux-gnu - * i686-apple-darwin - * x86_64-apple-darwin * aarch64-unknown-linux-gnu - * armv7-unknown-linux-gnueabihf * arm-unknown-linux-gnueabi - * x86_64-unknown-freebsd + * armv7-unknown-linux-gnueabihf + * i686-apple-darwin + * i686-unknown-linux-gnu + * i686-unknown-linux-musl + * mips-unknown-linux-gnu + * mipsel-unknown-linux-gnu * powerpc-unknown-linux-gnu * powerpc64-unknown-linux-gnu * powerpc64le-unknown-linux-gnu - * mips-unknown-linux-gnu - * mipsel-unknown-linux-gnu - * i686-unknown-linux-musl + * x86_64-apple-darwin + * x86_64-unknown-freebsd + * x86_64-unknown-linux-gnu * x86_64-unknown-linux-musl Tier 2: - * i686-unknown-freebsd - * x86_64-unknown-netbsd + * aarch64-apple-ios * aarch64-linux-android * arm-linux-androideabi + * armv7-apple-ios * armv7-linux-androideabi + * armv7s-apple-ios + * i386-apple-ios * i686-linux-android (requires Rust >= 1.18) + * i686-unknown-freebsd + * x86_64-apple-ios * x86_64-linux-android (requires Rust >= 1.18) + * x86_64-unknown-netbsd Tier 3: - * aarch64-apple-ios * arm-unknown-linux-musleabi (requires Rust >= 1.14) - * armv7-apple-ios - * armv7s-apple-ios - * i386-apple-ios * mips64-unknown-linux-gnuabi64 * mips64el-unknown-linux-gnuabi64 * s390x-unknown-linux-gnu - * x86_64-apple-ios ## Usage diff --git a/src/macros.rs b/src/macros.rs index b8707d0b..d8284f71 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -239,6 +239,184 @@ macro_rules! libc_bitflags { }; } +/// The `libc_enum!` macro helps with a common use case of defining an enum exclusively using +/// values from the `libc` crate. This macro supports both `pub` and private `enum`s. +/// +/// The `libc` crate must be in scope with the name `libc`. +/// +/// # Example +/// ``` +/// libc_enum!{ +/// pub enum ProtFlags { +/// PROT_NONE, +/// PROT_READ, +/// PROT_WRITE, +/// PROT_EXEC, +/// #[cfg(any(target_os = "linux", target_os = "android"))] +/// PROT_GROWSDOWN, +/// #[cfg(any(target_os = "linux", target_os = "android"))] +/// PROT_GROWSUP, +/// } +/// } +/// ``` +macro_rules! libc_enum { + // (non-pub) Exit rule. + (@make_enum + { + name: $BitFlags:ident, + attrs: [$($attrs:tt)*], + entries: [$($entries:tt)*], + } + ) => { + $($attrs)* + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + enum $BitFlags { + $($entries)* + } + }; + + // (pub) Exit rule. + (@make_enum + { + pub, + name: $BitFlags:ident, + attrs: [$($attrs:tt)*], + entries: [$($entries:tt)*], + } + ) => { + $($attrs)* + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + pub enum $BitFlags { + $($entries)* + } + }; + + // (non-pub) Done accumulating. + (@accumulate_entries + { + name: $BitFlags:ident, + attrs: $attrs:tt, + }, + $entries:tt; + ) => { + libc_enum! { + @make_enum + { + name: $BitFlags, + attrs: $attrs, + entries: $entries, + } + } + }; + + // (pub) Done accumulating. + (@accumulate_entries + { + pub, + name: $BitFlags:ident, + attrs: $attrs:tt, + }, + $entries:tt; + ) => { + libc_enum! { + @make_enum + { + pub, + name: $BitFlags, + attrs: $attrs, + entries: $entries, + } + } + }; + + // Munch an attr. + (@accumulate_entries + $prefix:tt, + [$($entries:tt)*]; + #[$attr:meta] $($tail:tt)* + ) => { + libc_enum! { + @accumulate_entries + $prefix, + [ + $($entries)* + #[$attr] + ]; + $($tail)* + } + }; + + // Munch last ident if not followed by a comma. + (@accumulate_entries + $prefix:tt, + [$($entries:tt)*]; + $entry:ident + ) => { + libc_enum! { + @accumulate_entries + $prefix, + [ + $($entries)* + $entry = libc::$entry, + ]; + } + }; + + // Munch an ident; covers terminating comma case. + (@accumulate_entries + $prefix:tt, + [$($entries:tt)*]; + $entry:ident, $($tail:tt)* + ) => { + libc_enum! { + @accumulate_entries + $prefix, + [ + $($entries)* + $entry = libc::$entry, + ]; + $($tail)* + } + }; + + // (non-pub) Entry rule. + ( + $(#[$attr:meta])* + enum $BitFlags:ident { + $($vals:tt)* + } + ) => { + libc_enum! { + @accumulate_entries + { + name: $BitFlags, + attrs: [$(#[$attr])*], + }, + []; + $($vals)* + } + }; + + // (pub) Entry rule. + ( + $(#[$attr:meta])* + pub enum $BitFlags:ident { + $($vals:tt)* + } + ) => { + libc_enum! { + @accumulate_entries + { + pub, + name: $BitFlags, + attrs: [$(#[$attr])*], + }, + []; + $($vals)* + } + }; +} + /// A Rust version of the familiar C `offset_of` macro. It returns the byte /// offset of `field` within struct `ty` macro_rules! offset_of { @@ -14,7 +14,8 @@ use {Errno, Result, Error, fcntl}; /// Representation of a master/slave pty pair /// -/// This is returned by `openpty` +/// This is returned by `openpty`. Note that this type does *not* implement `Drop`, so the user +/// must manually close the file descriptors. pub struct OpenptyResult { pub master: RawFd, pub slave: RawFd, @@ -45,10 +46,17 @@ impl IntoRawFd for PtyMaster { impl Drop for PtyMaster { fn drop(&mut self) { - // Errors when closing are ignored because we don't actually know if the file descriptor - // was closed. If we retried, it's possible that descriptor was reallocated in the mean - // time and the wrong file descriptor could be closed. - let _ = ::unistd::close(self.0); + // On drop, we ignore errors like EINTR and EIO because there's no clear + // way to handle them, we can't return anything, and (on FreeBSD at + // least) the file descriptor is deallocated in these cases. However, + // we must panic on EBADF, because it is always an error to close an + // invalid file descriptor. That frequently indicates a double-close + // condition, which can cause confusing errors for future I/O + // operations. + let e = ::unistd::close(self.0); + if e == Err(Error::Sys(Errno::EBADF)) { + panic!("Closing an invalid file descriptor!"); + }; } } @@ -188,23 +196,57 @@ pub fn unlockpt(fd: &PtyMaster) -> Result<()> { pub fn openpty<'a, 'b, T: Into<Option<&'a Winsize>>, U: Into<Option<&'b Termios>>>(winsize: T, termios: U) -> Result<OpenptyResult> { use std::ptr; - let mut slave: libc::c_int = -1; - let mut master: libc::c_int = -1; - let c_termios = match termios.into() { - Some(termios) => termios as *const Termios, - None => ptr::null() as *const Termios, - }; - let c_winsize = match winsize.into() { - Some(ws) => ws as *const Winsize, - None => ptr::null() as *const Winsize, - }; - let ret = unsafe { - libc::openpty( - &mut master as *mut libc::c_int, - &mut slave as *mut libc::c_int, - ptr::null_mut(), - c_termios as *mut libc::termios, - c_winsize as *mut Winsize) + let mut slave: libc::c_int = unsafe { mem::uninitialized() }; + let mut master: libc::c_int = unsafe { mem::uninitialized() }; + let ret = { + match (termios.into(), winsize.into()) { + (Some(termios), Some(winsize)) => { + let inner_termios = termios.get_libc_termios(); + unsafe { + libc::openpty( + &mut master, + &mut slave, + ptr::null_mut(), + &*inner_termios as *const libc::termios as *mut _, + winsize as *const Winsize as *mut _, + ) + } + } + (None, Some(winsize)) => { + unsafe { + libc::openpty( + &mut master, + &mut slave, + ptr::null_mut(), + ptr::null_mut(), + winsize as *const Winsize as *mut _, + ) + } + } + (Some(termios), None) => { + let inner_termios = termios.get_libc_termios(); + unsafe { + libc::openpty( + &mut master, + &mut slave, + ptr::null_mut(), + &*inner_termios as *const libc::termios as *mut _, + ptr::null_mut(), + ) + } + } + (None, None) => { + unsafe { + libc::openpty( + &mut master, + &mut slave, + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + ) + } + } + } }; Errno::result(ret)?; diff --git a/src/sys/mod.rs b/src/sys/mod.rs index 783b8364..627f7697 100644 --- a/src/sys/mod.rs +++ b/src/sys/mod.rs @@ -38,7 +38,6 @@ pub mod syscall; #[cfg(any(target_os = "linux"))] pub mod reboot; -#[cfg(not(target_os = "ios"))] pub mod termios; #[cfg(any(target_os = "linux", target_os = "android"))] diff --git a/src/sys/socket/mod.rs b/src/sys/socket/mod.rs index a126f8b4..c11b5367 100644 --- a/src/sys/socket/mod.rs +++ b/src/sys/socket/mod.rs @@ -258,14 +258,12 @@ pub fn sendmsg<'a>(fd: RawFd, iov: &[IoVec<&'a [u8]>], cmsgs: &[ControlMessage<' len += cmsg.len(); capacity += cmsg.space(); } - // Alignment hackery. Note that capacity is guaranteed to be a - // multiple of size_t. Note also that the resulting vector claims - // to have length == capacity, so it's presently uninitialized. + // Note that the resulting vector claims to have length == capacity, + // so it's presently uninitialized. let mut cmsg_buffer = unsafe { let mut vec = Vec::<u8>::with_capacity(len); - let ptr = vec.as_mut_ptr(); - mem::forget(vec); - Vec::<u8>::from_raw_parts(ptr as *mut _, len, len) + vec.set_len(len); + vec }; { let mut ptr = &mut cmsg_buffer[..]; @@ -279,12 +277,18 @@ pub fn sendmsg<'a>(fd: RawFd, iov: &[IoVec<&'a [u8]>], cmsgs: &[ControlMessage<' None => (0 as *const _, 0), }; + let cmsg_ptr = if capacity > 0 { + cmsg_buffer.as_ptr() as *const c_void + } else { + ptr::null() + }; + let mhdr = msghdr { msg_name: name as *const c_void, msg_namelen: namelen, msg_iov: iov.as_ptr(), msg_iovlen: iov.len() as size_t, - msg_control: cmsg_buffer.as_ptr() as *const c_void, + msg_control: cmsg_ptr, msg_controllen: capacity as size_t, msg_flags: 0, }; diff --git a/src/sys/termios.rs b/src/sys/termios.rs index 0f8bd6c9..8651a84d 100644 --- a/src/sys/termios.rs +++ b/src/sys/termios.rs @@ -1,647 +1,895 @@ +//! An interface for controlling asynchronous communication ports +//! +//! This interface provides a safe wrapper around the termios subsystem defined by POSIX. The +//! underlying types are all implemented in libc for most platforms and either wrapped in safer +//! types here or exported directly. +//! +//! If you are unfamiliar with the `termios` API, you should first read the +//! [API documentation](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/termios.h.html) and +//! then come back to understand how `nix` safely wraps it. +//! +//! It should be noted that this API incurs some runtime overhead above the base `libc` definitions. +//! As this interface is not used with high-bandwidth information, this should be fine in most +//! cases. The primary cost when using this API is that the `Termios` datatype here duplicates the +//! standard fields of the underlying `termios` struct and uses safe type wrappers for those fields. +//! This means that when crossing the FFI interface to the underlying C library, data is first +//! copied into the underlying `termios` struct, then the operation is done, and the data is copied +//! back (with additional sanity checking) into the safe wrapper types. The `termios` struct is +//! relatively small across all platforms (on the order of 32-64 bytes). +//! +//! The following examples highlight some of the API use cases such that users coming from using C +//! or reading the standard documentation will understand how to use the safe API exposed here. +//! +//! Example disabling processing of the end-of-file control character: +//! +//! ``` +//! # use self::nix::sys::termios::SpecialCharacterIndices::VEOF; +//! # use self::nix::sys::termios::{_POSIX_VDISABLE, Termios}; +//! # let mut termios = unsafe { Termios::default_uninit() }; +//! termios.control_chars[VEOF as usize] = _POSIX_VDISABLE; +//! ``` +//! +//! The flags within `Termios` are defined as bitfields using the `bitflags` crate. This provides +//! an interface for working with bitfields that is similar to working with the raw unsigned +//! integer types but offers type safety because of the internal checking that values will always +//! be a valid combination of the defined flags. +//! +//! An example showing some of the basic operations for interacting with the control flags: +//! +//! ``` +//! # use self::nix::sys::termios::{CS5, CSIZE, Termios}; +//! # let mut termios = unsafe { Termios::default_uninit() }; +//! termios.control_flags & CSIZE == CS5; +//! termios.control_flags |= CS5; +//! ``` + use {Errno, Result}; -use libc::c_int; +use libc::{self, c_int, tcflag_t}; +use std::cell::{Ref, RefCell}; +use std::convert::From; use std::mem; use std::os::unix::io::RawFd; -pub use self::ffi::consts::*; -pub use self::ffi::consts::SetArg::*; -pub use self::ffi::consts::FlushArg::*; -pub use self::ffi::consts::FlowArg::*; - -mod ffi { - pub use self::consts::*; - - #[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "dragonfly", target_os = "openbsd", target_os = "netbsd", target_os = "linux"))] - mod non_android { - use super::consts::*; - use libc::c_int; - - // `Termios` contains bitflags which are not considered - // `foreign-function-safe` by the compiler. - #[allow(improper_ctypes)] - extern { - pub fn cfgetispeed(termios: *const Termios) -> speed_t; - pub fn cfgetospeed(termios: *const Termios) -> speed_t; - pub fn cfsetispeed(termios: *mut Termios, speed: speed_t) -> c_int; - pub fn cfsetospeed(termios: *mut Termios, speed: speed_t) -> c_int; - pub fn tcgetattr(fd: c_int, termios: *mut Termios) -> c_int; - pub fn tcsetattr(fd: c_int, - optional_actions: c_int, - termios: *const Termios) -> c_int; - pub fn tcdrain(fd: c_int) -> c_int; - pub fn tcflow(fd: c_int, action: c_int) -> c_int; - pub fn tcflush(fd: c_int, action: c_int) -> c_int; - pub fn tcsendbreak(fd: c_int, duration: c_int) -> c_int; - } - } - - #[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "dragonfly", target_os = "openbsd", target_os = "netbsd", target_os = "linux"))] - pub use self::non_android::*; - - // On Android before 5.0, Bionic directly inline these to ioctl() calls. - #[cfg(all(target_os = "android", not(target_arch = "mips")))] - mod android { - use libc; - use libc::c_int; - use super::consts::*; - - const TCGETS: c_int = 0x5401; - const TCSBRK: c_int = 0x5409; - const TCXONC: c_int = 0x540a; - const TCFLSH: c_int = 0x540b; - const TCSBRKP: c_int = 0x5425; +use ::unistd::Pid; + +/// Stores settings for the termios API +/// +/// This is a wrapper around the `libc::termios` struct that provides a safe interface for the +/// standard fields. The only safe way to obtain an instance of this struct is to extract it from +/// an open port using `tcgetattr()`. +#[derive(Clone)] +pub struct Termios { + inner: RefCell<libc::termios>, + /// Input mode flags (see `termios.c_iflag` documentation) + pub input_flags: InputFlags, + /// Output mode flags (see `termios.c_oflag` documentation) + pub output_flags: OutputFlags, + /// Control mode flags (see `termios.c_cflag` documentation) + pub control_flags: ControlFlags, + /// Local mode flags (see `termios.c_lflag` documentation) + pub local_flags: LocalFlags, + /// Control characters (see `termios.c_cc` documentation) + pub control_chars: [libc::cc_t; libc::NCCS], +} - pub unsafe fn cfgetispeed(termios: *const Termios) -> speed_t { - ((*termios).c_cflag & CBAUD).bits() as speed_t - } - pub unsafe fn cfgetospeed(termios: *const Termios) -> speed_t { - ((*termios).c_cflag & CBAUD).bits() as speed_t - } - pub unsafe fn cfsetispeed(termios: *mut Termios, speed: speed_t) -> c_int { - (*termios).c_cflag.remove(CBAUD); - (*termios).c_cflag.insert(ControlFlags::from_bits_truncate(speed) & CBAUD); - 0 - } - pub unsafe fn cfsetospeed(termios: *mut Termios, speed: speed_t) -> c_int { - (*termios).c_cflag.remove(CBAUD); - (*termios).c_cflag.insert(ControlFlags::from_bits_truncate(speed) & CBAUD); - 0 - } - pub unsafe fn tcgetattr(fd: c_int, termios: *mut Termios) -> c_int { - libc::ioctl(fd, TCGETS, termios) - } - pub unsafe fn tcsetattr(fd: c_int, - optional_actions: c_int, - termios: *const Termios) -> c_int { - libc::ioctl(fd, optional_actions, termios) - } - pub unsafe fn tcdrain(fd: c_int) -> c_int { - libc::ioctl(fd, TCSBRK, 1) - } - pub unsafe fn tcflow(fd: c_int, action: c_int) -> c_int { - libc::ioctl(fd, TCXONC, action) - } - pub unsafe fn tcflush(fd: c_int, action: c_int) -> c_int { - libc::ioctl(fd, TCFLSH, action) - } - pub unsafe fn tcsendbreak(fd: c_int, duration: c_int) -> c_int { - libc::ioctl(fd, TCSBRKP, duration) +impl Termios { + /// Exposes an immutable reference to the underlying `libc::termios` data structure. + /// + /// This can be used for interfacing with other FFI functions like: + /// + /// ```rust + /// # extern crate libc; + /// # extern crate nix; + /// # fn main() { + /// # use nix::sys::termios::Termios; + /// # let mut termios = unsafe { Termios::default_uninit() }; + /// let inner_termios = termios.get_libc_termios(); + /// unsafe { libc::cfgetispeed(&*inner_termios) }; + /// # } + /// ``` + /// + /// There is no public API exposed for functions that modify the underlying `libc::termios` + /// data because it requires additional work to maintain type safety. + // FIXME: Switch this over to use pub(crate) + #[doc(hidden)] + pub fn get_libc_termios(&self) -> Ref<libc::termios> { + { + let mut termios = self.inner.borrow_mut(); + termios.c_iflag = self.input_flags.bits(); + termios.c_oflag = self.output_flags.bits(); + termios.c_cflag = self.control_flags.bits(); + termios.c_lflag = self.local_flags.bits(); + termios.c_cc = self.control_chars; } + self.inner.borrow() } - #[cfg(target_os = "android")] - pub use self::android::*; - - - #[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "dragonfly", target_os = "openbsd", target_os = "netbsd"))] - pub mod consts { - - use libc; - - use libc::c_int; - - pub type tcflag_t = libc::tcflag_t; - pub type cc_t = libc::cc_t; - pub type speed_t = libc::speed_t; - - #[repr(C)] - #[derive(Clone, Copy)] - pub struct Termios { - pub c_iflag: InputFlags, - pub c_oflag: OutputFlags, - pub c_cflag: ControlFlags, - pub c_lflag: LocalFlags, - pub c_cc: [cc_t; NCCS], - pub c_ispeed: speed_t, - pub c_ospeed: speed_t - } - - #[derive(Clone, Copy, Debug, PartialEq)] - pub enum BaudRate { - B0, - B50, - B75, - B110, - B134, - B150, - B200, - B300, - B600, - B1200, - B1800, - B2400, - B4800, - B9600, - B19200, - B38400, - B7200, - B14400, - B28800, - B57600, - B76800, - B115200, - B230400, - #[cfg(any(target_os = "netbsd", target_os = "freebsd"))] - B460800, - #[cfg(any(target_os = "netbsd", target_os = "freebsd"))] - B921600, - } - - impl From<speed_t> for BaudRate { - fn from(s: speed_t) -> BaudRate { - - use libc::{ - B0, B50, B75, B110, B134, B150, - B200, B300, B600, B1200, B1800, B2400, - B4800, B9600, B19200, B38400, - B7200, B14400, B28800, B57600, - B76800, B115200, B230400}; - - #[cfg(target_os = "freebsd")] - use libc::{B460800, B921600}; - - match s { - B0 => BaudRate::B0, - B50 => BaudRate::B50, - B75 => BaudRate::B75, - B110 => BaudRate::B110, - B134 => BaudRate::B134, - B150 => BaudRate::B150, - B200 => BaudRate::B200, - B300 => BaudRate::B300, - B600 => BaudRate::B600, - B1200 => BaudRate::B1200, - B1800 => BaudRate::B1800, - B2400 => BaudRate::B2400, - B4800 => BaudRate::B4800, - B9600 => BaudRate::B9600, - B19200 => BaudRate::B19200, - B38400 => BaudRate::B38400, - B7200 => BaudRate::B7200, - B14400 => BaudRate::B14400, - B28800 => BaudRate::B28800, - B57600 => BaudRate::B57600, - B76800 => BaudRate::B76800, - B115200 => BaudRate::B115200, - B230400 => BaudRate::B230400, - #[cfg(target_os = "freebsd")] - B460800 => BaudRate::B460800, - #[cfg(target_os = "freebsd")] - B921600 => BaudRate::B921600, - b @ _ => unreachable!("Invalid baud constant: {}", b), - } - } - } - - pub const VEOF: usize = 0; - pub const VEOL: usize = 1; - pub const VEOL2: usize = 2; - pub const VERASE: usize = 3; - pub const VWERASE: usize = 4; - pub const VKILL: usize = 5; - pub const VREPRINT: usize = 6; - pub const VINTR: usize = 8; - pub const VQUIT: usize = 9; - pub const VSUSP: usize = 10; - pub const VDSUSP: usize = 11; - pub const VSTART: usize = 12; - pub const VSTOP: usize = 13; - pub const VLNEXT: usize = 14; - pub const VDISCARD: usize = 15; - pub const VMIN: usize = 16; - pub const VTIME: usize = 17; - pub const VSTATUS: usize = 18; - pub const NCCS: usize = 20; - - bitflags! { - pub struct InputFlags: tcflag_t { - const IGNBRK = 0x00000001; - const BRKINT = 0x00000002; - const IGNPAR = 0x00000004; - const PARMRK = 0x00000008; - const INPCK = 0x00000010; - const ISTRIP = 0x00000020; - const INLCR = 0x00000040; - const IGNCR = 0x00000080; - const ICRNL = 0x00000100; - const IXON = 0x00000200; - const IXOFF = 0x00000400; - const IXANY = 0x00000800; - const IMAXBEL = 0x00002000; - #[cfg(not(target_os = "dragonfly"))] - const IUTF8 = 0x00004000; - } - } - - bitflags! { - pub struct OutputFlags: tcflag_t { - const OPOST = 0x00000001; - const ONLCR = 0x00000002; - const OXTABS = 0x00000004; - const ONOEOT = 0x00000008; - } - } - - bitflags! { - pub struct ControlFlags: tcflag_t { - const CIGNORE = 0x00000001; - const CSIZE = 0x00000300; - const CS5 = 0x00000000; - const CS6 = 0x00000100; - const CS7 = 0x00000200; - const CS8 = 0x00000300; - const CSTOPB = 0x00000400; - const CREAD = 0x00000800; - const PARENB = 0x00001000; - const PARODD = 0x00002000; - const HUPCL = 0x00004000; - const CLOCAL = 0x00008000; - const CCTS_OFLOW = 0x00010000; - const CRTSCTS = 0x00030000; - const CRTS_IFLOW = 0x00020000; - const CDTR_IFLOW = 0x00040000; - const CDSR_OFLOW = 0x00080000; - const CCAR_OFLOW = 0x00100000; - const MDMBUF = 0x00100000; - } + /// Exposes the inner `libc::termios` datastore within `Termios`. + /// + /// This is unsafe because if this is used to modify the inner libc::termios struct, it will not + /// automatically update the safe wrapper type around it. Therefore we disable docs to + /// effectively limit its use to nix internals. In this case it should also be paired with a + /// call to `update_wrapper()` so that the wrapper-type and internal representation stay + /// consistent. + unsafe fn get_libc_termios_mut(&mut self) -> *mut libc::termios { + { + let mut termios = self.inner.borrow_mut(); + termios.c_iflag = self.input_flags.bits(); + termios.c_oflag = self.output_flags.bits(); + termios.c_cflag = self.control_flags.bits(); + termios.c_lflag = self.local_flags.bits(); + termios.c_cc = self.control_chars; } + self.inner.as_ptr() + } - bitflags! { - pub struct LocalFlags: tcflag_t { - const ECHOKE = 0x00000001; - const ECHOE = 0x00000002; - const ECHOK = 0x00000004; - const ECHO = 0x00000008; - const ECHONL = 0x00000010; - const ECHOPRT = 0x00000020; - const ECHOCTL = 0x00000040; - const ISIG = 0x00000080; - const ICANON = 0x00000100; - const ALTWERASE = 0x00000200; - const IEXTEN = 0x00000400; - const EXTPROC = 0x00000800; - const TOSTOP = 0x00400000; - const FLUSHO = 0x00800000; - const NOKERNINFO = 0x02000000; - const PENDIN = 0x20000000; - const NOFLSH = 0x80000000; - } + /// Allows for easily creating new Termios structs that will be overwritten with real data. + /// + /// This should only be used when the inner libc::termios struct will be overwritten before it's + /// read. + // FIXME: Switch this over to use pub(crate) + #[doc(hidden)] + pub unsafe fn default_uninit() -> Self { + Termios { + inner: RefCell::new(mem::uninitialized()), + input_flags: InputFlags::empty(), + output_flags: OutputFlags::empty(), + control_flags: ControlFlags::empty(), + local_flags: LocalFlags::empty(), + control_chars: [0 as libc::cc_t; NCCS], } + } - pub const NL0: c_int = 0x00000000; - pub const NL1: c_int = 0x00000100; - pub const NL2: c_int = 0x00000200; - pub const NL3: c_int = 0x00000300; - pub const TAB0: c_int = 0x00000000; - pub const TAB1: c_int = 0x00000400; - pub const TAB2: c_int = 0x00000800; - pub const TAB3: c_int = 0x00000004; - pub const CR0: c_int = 0x00000000; - pub const CR1: c_int = 0x00001000; - pub const CR2: c_int = 0x00002000; - pub const CR3: c_int = 0x00003000; - pub const FF0: c_int = 0x00000000; - pub const FF1: c_int = 0x00004000; - pub const BS0: c_int = 0x00000000; - pub const BS1: c_int = 0x00008000; - pub const VT0: c_int = 0x00000000; - pub const VT1: c_int = 0x00010000; - - // XXX: We're using `repr(C)` because `c_int` doesn't work here. - // See https://github.com/rust-lang/rust/issues/10374. - #[derive(Clone, Copy)] - #[repr(C)] - pub enum SetArg { - TCSANOW = 0, - TCSADRAIN = 1, - TCSAFLUSH = 2, - TCSASOFT = 16, - } + /// Updates the wrapper values from the internal `libc::termios` data structure. + #[doc(hidden)] + pub fn update_wrapper(&mut self) { + let termios = *self.inner.borrow_mut(); + self.input_flags = InputFlags::from_bits_truncate(termios.c_iflag); + self.output_flags = OutputFlags::from_bits_truncate(termios.c_oflag); + self.control_flags = ControlFlags::from_bits_truncate(termios.c_cflag); + self.local_flags = LocalFlags::from_bits_truncate(termios.c_lflag); + self.control_chars = termios.c_cc; + } +} - // XXX: We're using `repr(C)` because `c_int` doesn't work here. - // See https://github.com/rust-lang/rust/issues/10374. - #[derive(Clone, Copy)] - #[repr(C)] - pub enum FlushArg { - TCIFLUSH = 1, - TCOFLUSH = 2, - TCIOFLUSH = 3, +impl From<libc::termios> for Termios { + fn from(termios: libc::termios) -> Self { + Termios { + inner: RefCell::new(termios), + input_flags: InputFlags::from_bits_truncate(termios.c_iflag), + output_flags: OutputFlags::from_bits_truncate(termios.c_oflag), + control_flags: ControlFlags::from_bits_truncate(termios.c_cflag), + local_flags: LocalFlags::from_bits_truncate(termios.c_lflag), + control_chars: termios.c_cc, } + } +} - // XXX: We're using `repr(C)` because `c_int` doesn't work here. - // See https://github.com/rust-lang/rust/issues/10374. - #[derive(Clone, Copy)] - #[repr(C)] - pub enum FlowArg { - TCOOFF = 1, - TCOON = 2, - TCIOFF = 3, - TCION = 4, - } +impl From<Termios> for libc::termios { + fn from(termios: Termios) -> Self { + termios.inner.into_inner() } +} - #[cfg(any(target_os = "linux", target_os = "android"))] - pub mod consts { - use libc::{c_int, c_uint, c_uchar}; - - pub type tcflag_t = c_uint; - pub type cc_t = c_uchar; - pub type speed_t = c_uint; - - #[repr(C)] - #[derive(Clone, Copy)] - pub struct Termios { - pub c_iflag: InputFlags, - pub c_oflag: OutputFlags, - pub c_cflag: ControlFlags, - pub c_lflag: LocalFlags, - pub c_line: cc_t, - pub c_cc: [cc_t; NCCS], - pub c_ispeed: speed_t, - pub c_ospeed: speed_t - } +libc_enum!{ + /// Baud rates supported by the system + /// + /// B0 is special and will disable the port. + #[cfg_attr(all(any(target_os = "ios", target_os = "macos"), target_pointer_width = "64"), repr(u64))] + #[cfg_attr(not(all(any(target_os = "ios", target_os = "macos"), target_pointer_width = "64")), repr(u32))] + pub enum BaudRate { + B0, + B50, + B75, + B110, + B134, + B150, + B200, + B300, + B600, + B1200, + B1800, + B2400, + B4800, + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] + B7200, + B9600, + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] + B14400, + B19200, + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] + B28800, + B38400, + B57600, + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] + B76800, + B115200, + B230400, + #[cfg(any(target_os = "android", + target_os = "freebsd", + target_os = "linux", + taget_os = "netbsd"))] + B460800, + #[cfg(any(target_os = "android", target_os = "linux"))] + B500000, + #[cfg(any(target_os = "android", target_os = "linux"))] + B576000, + #[cfg(any(target_os = "android", + target_os = "freebsd", + target_os = "linux", + taget_os = "netbsd"))] + B921600, + #[cfg(any(target_os = "android", target_os = "linux"))] + B1000000, + #[cfg(any(target_os = "android", target_os = "linux"))] + B1152000, + #[cfg(any(target_os = "android", target_os = "linux"))] + B1500000, + #[cfg(any(target_os = "android", target_os = "linux"))] + B2000000, + #[cfg(any(target_os = "android", target_os = "linux"))] + B2500000, + #[cfg(any(target_os = "android", target_os = "linux"))] + B3000000, + #[cfg(any(target_os = "android", target_os = "linux"))] + B3500000, + #[cfg(any(target_os = "android", target_os = "linux"))] + B4000000, + } +} - #[derive(Clone, Copy, Debug, PartialEq)] - pub enum BaudRate { - B0, - B50, - B75, - B110, - B134, - B150, - B200, - B300, - B600, - B1200, - B1800, - B2400, - B4800, - B9600, - B19200, - B38400, - B57600, - B115200, - B230400, - B460800, - B500000, - B576000, - B921600, - B1000000, - B1152000, - B1500000, - B2000000, - B2500000, - B3000000, - B3500000, - B4000000, +impl From<libc::speed_t> for BaudRate { + fn from(s: libc::speed_t) -> BaudRate { + + use libc::{B0, B50, B75, B110, B134, B150, B200, B300, B600, B1200, B1800, B2400, B4800, + B9600, B19200, B38400, B57600, B115200, B230400}; + #[cfg(any(target_os = "android", target_os = "linux"))] + use libc::{B500000, B576000, B1000000, B1152000, B1500000, B2000000, B2500000, B3000000, + B3500000, B4000000}; + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] + use libc::{B7200, B14400, B28800, B76800}; + #[cfg(any(target_os = "android", + target_os = "freebsd", + target_os = "linux", + target_os = "netbsd"))] + use libc::{B460800, B921600}; + + match s { + B0 => BaudRate::B0, + B50 => BaudRate::B50, + B75 => BaudRate::B75, + B110 => BaudRate::B110, + B134 => BaudRate::B134, + B150 => BaudRate::B150, + B200 => BaudRate::B200, + B300 => BaudRate::B300, + B600 => BaudRate::B600, + B1200 => BaudRate::B1200, + B1800 => BaudRate::B1800, + B2400 => BaudRate::B2400, + B4800 => BaudRate::B4800, + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] + B7200 => BaudRate::B7200, + B9600 => BaudRate::B9600, + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] + B14400 => BaudRate::B14400, + B19200 => BaudRate::B19200, + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] + B28800 => BaudRate::B28800, + B38400 => BaudRate::B38400, + B57600 => BaudRate::B57600, + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] + B76800 => BaudRate::B76800, + B115200 => BaudRate::B115200, + B230400 => BaudRate::B230400, + #[cfg(any(target_os = "android", + target_os = "freebsd", + target_os = "linux", + taget_os = "netbsd"))] + B460800 => BaudRate::B460800, + #[cfg(any(target_os = "android", target_os = "linux"))] + B500000 => BaudRate::B500000, + #[cfg(any(target_os = "android", target_os = "linux"))] + B576000 => BaudRate::B576000, + #[cfg(any(target_os = "android", + target_os = "freebsd", + target_os = "linux", + taget_os = "netbsd"))] + B921600 => BaudRate::B921600, + #[cfg(any(target_os = "android", target_os = "linux"))] + B1000000 => BaudRate::B1000000, + #[cfg(any(target_os = "android", target_os = "linux"))] + B1152000 => BaudRate::B1152000, + #[cfg(any(target_os = "android", target_os = "linux"))] + B1500000 => BaudRate::B1500000, + #[cfg(any(target_os = "android", target_os = "linux"))] + B2000000 => BaudRate::B2000000, + #[cfg(any(target_os = "android", target_os = "linux"))] + B2500000 => BaudRate::B2500000, + #[cfg(any(target_os = "android", target_os = "linux"))] + B3000000 => BaudRate::B3000000, + #[cfg(any(target_os = "android", target_os = "linux"))] + B3500000 => BaudRate::B3500000, + #[cfg(any(target_os = "android", target_os = "linux"))] + B4000000 => BaudRate::B4000000, + b @ _ => unreachable!("Invalid baud constant: {}", b), } + } +} - impl From<speed_t> for BaudRate { - fn from(s: speed_t) -> BaudRate { - - use libc::{ - B0, B50, B75, B110, B134, B150, - B200, B300, B600, B1200, B1800, B2400, - B4800, B9600, B19200, B38400, B57600, - B115200, B230400, B460800, B500000, - B576000, B921600, B1000000, B1152000, - B1500000, B2000000, B2500000, B3000000, - B3500000, B4000000}; - - match s { - B0 => BaudRate::B0, - B50 => BaudRate::B50, - B75 => BaudRate::B75, - B110 => BaudRate::B110, - B134 => BaudRate::B134, - B150 => BaudRate::B150, - B200 => BaudRate::B200, - B300 => BaudRate::B300, - B600 => BaudRate::B600, - B1200 => BaudRate::B1200, - B1800 => BaudRate::B1800, - B2400 => BaudRate::B2400, - B4800 => BaudRate::B4800, - B9600 => BaudRate::B9600, - B19200 => BaudRate::B19200, - B38400 => BaudRate::B38400, - B57600 => BaudRate::B57600, - B115200 => BaudRate::B115200, - B230400 => BaudRate::B230400, - B460800 => BaudRate::B460800, - B500000 => BaudRate::B500000, - B576000 => BaudRate::B576000, - B921600 => BaudRate::B921600, - B1000000 => BaudRate::B1000000, - B1152000 => BaudRate::B1152000, - B1500000 => BaudRate::B1500000, - B2000000 => BaudRate::B2000000, - B2500000 => BaudRate::B2500000, - B3000000 => BaudRate::B3000000, - B3500000 => BaudRate::B3500000, - B4000000 => BaudRate::B4000000, - b @ _ => unreachable!("Invalid baud constant: {}", b), - } - } - } +// TODO: Add TCSASOFT, which will require treating this as a bitfield. +libc_enum! { + /// Specify when a port configuration change should occur. + /// + /// Used as an argument to `tcsetattr()` + #[repr(i32)] + pub enum SetArg { + /// The change will occur immediately + TCSANOW, + /// The change occurs after all output has been written + TCSADRAIN, + /// Same as `TCSADRAIN`, but will also flush the input buffer + TCSAFLUSH, + } +} - pub const VEOF: usize = 4; - pub const VEOL: usize = 11; - pub const VEOL2: usize = 16; - pub const VERASE: usize = 2; - pub const VWERASE: usize = 14; - pub const VKILL: usize = 3; - pub const VREPRINT: usize = 12; - pub const VINTR: usize = 0; - pub const VQUIT: usize = 1; - pub const VSUSP: usize = 10; - pub const VSTART: usize = 8; - pub const VSTOP: usize = 9; - pub const VLNEXT: usize = 15; - pub const VDISCARD: usize = 13; - pub const VMIN: usize = 6; - pub const VTIME: usize = 5; - pub const NCCS: usize = 32; - - bitflags! { - pub struct InputFlags: tcflag_t { - const IGNBRK = 0x00000001; - const BRKINT = 0x00000002; - const IGNPAR = 0x00000004; - const PARMRK = 0x00000008; - const INPCK = 0x00000010; - const ISTRIP = 0x00000020; - const INLCR = 0x00000040; - const IGNCR = 0x00000080; - const ICRNL = 0x00000100; - const IXON = 0x00000400; - const IXOFF = 0x00001000; - const IXANY = 0x00000800; - const IMAXBEL = 0x00002000; - const IUTF8 = 0x00004000; - } - } +libc_enum! { + /// Specify a combination of the input and output buffers to flush + /// + /// Used as an argument to `tcflush()`. + #[repr(i32)] + pub enum FlushArg { + /// Flush data that was received but not read + TCIFLUSH, + /// Flush data written but not transmitted + TCOFLUSH, + /// Flush both received data not read and written data not transmitted + TCIOFLUSH, + } +} - bitflags! { - pub struct OutputFlags: tcflag_t { - const OPOST = 0x00000001; - const ONLCR = 0x00000004; - } - } +libc_enum! { + /// Specify how transmission flow should be altered + /// + /// Used as an argument to `tcflow()`. + #[repr(i32)] + pub enum FlowArg { + /// Suspend transmission + TCOOFF, + /// Resume transmission + TCOON, + /// Transmit a STOP character, which should disable a connected terminal device + TCIOFF, + /// Transmit a START character, which should re-enable a connected terminal device + TCION, + } +} - bitflags! { - pub struct ControlFlags: tcflag_t { - const CSIZE = 0x00000030; - const CS5 = 0x00000000; - const CS6 = 0x00000010; - const CS7 = 0x00000020; - const CS8 = 0x00000030; - const CSTOPB = 0x00000040; - const CREAD = 0x00000080; - const PARENB = 0x00000100; - const PARODD = 0x00000200; - const HUPCL = 0x00000400; - const CLOCAL = 0x00000800; - const CRTSCTS = 0x80000000; - #[cfg(target_os = "android")] - const CBAUD = 0o0010017; - } - } +// TODO: Make this usable directly as a slice index. +libc_enum! { + /// Indices into the `termios.c_cc` array for special characters. + #[repr(usize)] + pub enum SpecialCharacterIndices { + VDISCARD, + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] + VDSUSP, + VEOF, + VEOL, + VEOL2, + VERASE, + #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] + VERASE2, + VINTR, + VKILL, + VLNEXT, + VMIN, + VQUIT, + VREPRINT, + VSTART, + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] + VSTATUS, + VSTOP, + VSUSP, + #[cfg(target_os = "linux")] + VSWTC, + #[cfg(target_os = "haiku")] + VSWTCH, + VTIME, + VWERASE, + #[cfg(target_os = "dragonfly")] + VCHECKPT, + } +} - bitflags! { - pub struct LocalFlags: tcflag_t { - const ECHOKE = 0x00000800; - const ECHOE = 0x00000010; - const ECHOK = 0x00000020; - const ECHO = 0x00000008; - const ECHONL = 0x00000040; - const ECHOPRT = 0x00000400; - const ECHOCTL = 0x00000200; - const ISIG = 0x00000001; - const ICANON = 0x00000002; - const IEXTEN = 0x00008000; - const EXTPROC = 0x00010000; - const TOSTOP = 0x00000100; - const FLUSHO = 0x00001000; - const PENDIN = 0x00004000; - const NOFLSH = 0x00000080; - } - } +pub use libc::NCCS; +#[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] +pub use libc::_POSIX_VDISABLE; + +libc_bitflags! { + /// Flags for configuring the input mode of a terminal + pub flags InputFlags: tcflag_t { + IGNBRK, + BRKINT, + IGNPAR, + PARMRK, + INPCK, + ISTRIP, + INLCR, + IGNCR, + ICRNL, + IXON, + IXOFF, + IXANY, + IMAXBEL, + #[cfg(any(target_os = "android", target_os = "linux", target_os = "macos"))] + IUTF8, + } +} - pub const NL0: c_int = 0x00000000; - pub const NL1: c_int = 0x00000100; - pub const TAB0: c_int = 0x00000000; - pub const TAB1: c_int = 0x00000800; - pub const TAB2: c_int = 0x00001000; - pub const TAB3: c_int = 0x00001800; - pub const CR0: c_int = 0x00000000; - pub const CR1: c_int = 0x00000200; - pub const CR2: c_int = 0x00000400; - pub const CR3: c_int = 0x00000600; - pub const FF0: c_int = 0x00000000; - pub const FF1: c_int = 0x00008000; - pub const BS0: c_int = 0x00000000; - pub const BS1: c_int = 0x00002000; - pub const VT0: c_int = 0x00000000; - pub const VT1: c_int = 0x00004000; - - // XXX: We're using `repr(C)` because `c_int` doesn't work here. - // See https://github.com/rust-lang/rust/issues/10374. - #[derive(Clone, Copy)] - #[repr(C)] - pub enum SetArg { - TCSANOW = 0, - TCSADRAIN = 1, - TCSAFLUSH = 2, - } +libc_bitflags! { + /// Flags for configuring the output mode of a terminal + pub flags OutputFlags: tcflag_t { + OPOST, + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "linux", + target_os = "openbsd"))] + OLCUC, + ONLCR, + OCRNL as tcflag_t, + ONOCR as tcflag_t, + ONLRET as tcflag_t, + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + OFILL as tcflag_t, + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + OFDEL as tcflag_t, + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + NL0 as tcflag_t, + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + NL1 as tcflag_t, + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + CR0 as tcflag_t, + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + CR1 as tcflag_t, + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + CR2 as tcflag_t, + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + CR3 as tcflag_t, + #[cfg(any(target_os = "android", + target_os = "freebsd", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + TAB0 as tcflag_t, + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + TAB1 as tcflag_t, + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + TAB2 as tcflag_t, + #[cfg(any(target_os = "android", + target_os = "freebsd", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + TAB3 as tcflag_t, + #[cfg(any(target_os = "android", target_os = "linux"))] + XTABS, + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + BS0 as tcflag_t, + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + BS1 as tcflag_t, + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + VT0 as tcflag_t, + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + VT1 as tcflag_t, + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + FF0 as tcflag_t, + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + FF1 as tcflag_t, + #[cfg(any(target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] + OXTABS, + #[cfg(any(target_os = "freebsd", + target_os = "dragonfly", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] + ONOEOT as tcflag_t, + + // Bitmasks for use with OutputFlags to select specific settings + // These should be moved to be a mask once https://github.com/rust-lang-nursery/bitflags/issues/110 + // is resolved. + + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + NLDLY as tcflag_t, // FIXME: Datatype needs to be corrected in libc for mac + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + CRDLY as tcflag_t, + #[cfg(any(target_os = "android", + target_os = "freebsd", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + TABDLY as tcflag_t, + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + BSDLY as tcflag_t, + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + VTDLY as tcflag_t, + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + FFDLY as tcflag_t, + } +} - // XXX: We're using `repr(C)` because `c_int` doesn't work here. - // See https://github.com/rust-lang/rust/issues/10374. - #[derive(Clone, Copy)] - #[repr(C)] - pub enum FlushArg { - TCIFLUSH = 0, - TCOFLUSH = 1, - TCIOFLUSH = 2, - } +libc_bitflags! { + /// Flags for setting the control mode of a terminal + pub flags ControlFlags: tcflag_t { + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] + CIGNORE, + CS5, + CS6, + CS7, + CS8, + CSTOPB, + CREAD, + PARENB, + PARODD, + HUPCL, + CLOCAL, + CRTSCTS, + #[cfg(any(target_os = "android", target_os = "linux"))] + CBAUD, + #[cfg(any(target_os = "android", all(target_os = "linux", not(target_arch = "mips"))))] + CMSPAR, + #[cfg(any(target_os = "android", + all(target_os = "linux", + not(any(target_arch = "powerpc", target_arch = "powerpc64")))))] + CIBAUD, + #[cfg(any(target_os = "android", target_os = "linux"))] + CBAUDEX, + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] + MDMBUF, + #[cfg(any(target_os = "netbsd", target_os = "openbsd"))] + CHWFLOW, + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd"))] + CCTS_OFLOW, + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd"))] + CRTS_IFLOW, + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd"))] + CDTR_IFLOW, + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd"))] + CDSR_OFLOW, + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd"))] + CCAR_OFLOW, + + // Bitmasks for use with ControlFlags to select specific settings + // These should be moved to be a mask once https://github.com/rust-lang-nursery/bitflags/issues/110 + // is resolved. + + CSIZE, + } +} - // XXX: We're using `repr(C)` because `c_int` doesn't work here. - // See https://github.com/rust-lang/rust/issues/10374. - #[derive(Clone, Copy)] - #[repr(C)] - pub enum FlowArg { - TCOOFF = 0, - TCOON = 1, - TCIOFF = 2, - TCION = 3, - } +libc_bitflags! { + /// Flags for setting any local modes + pub flags LocalFlags: tcflag_t { + ECHOKE, + ECHOE, + ECHOK, + ECHO, + ECHONL, + ECHOPRT, + ECHOCTL, + ISIG, + ICANON, + #[cfg(any(target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] + ALTWERASE, + IEXTEN, + EXTPROC, + TOSTOP, + FLUSHO, + #[cfg(any(target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] + NOKERNINFO, + PENDIN, + NOFLSH, } } +/// Get input baud rate (see +/// [cfgetispeed(3p)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/cfgetispeed.html)). +/// +/// `cfgetispeed()` extracts the input baud rate from the given Termios structure. pub fn cfgetispeed(termios: &Termios) -> BaudRate { - unsafe { - ffi::cfgetispeed(termios).into() - } + let inner_termios = termios.get_libc_termios(); + unsafe { libc::cfgetispeed(&*inner_termios) }.into() } +/// Get output baud rate (see +/// [cfgetospeed(3p)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/cfgetospeed.html)). +/// +/// `cfgetospeed()` extracts the output baud rate from the given Termios structure. pub fn cfgetospeed(termios: &Termios) -> BaudRate { + let inner_termios = termios.get_libc_termios(); + unsafe { libc::cfgetospeed(&*inner_termios) }.into() +} + +/// Configures the port to something like the "raw" mode of the old Version 7 terminal driver (see +/// [termios(3)](http://man7.org/linux/man-pages/man3/termios.3.html)). +/// +/// `cfmakeraw()` configures the termios structure such that input is available character-by- +/// character, echoing is disabled, and all special input and output processing is disabled. Note +/// that this is a non-standard function, but is available on Linux and BSDs. +pub fn cfmakeraw(termios: &mut Termios) { + let inner_termios = unsafe { termios.get_libc_termios_mut() }; unsafe { - ffi::cfgetospeed(termios).into() + libc::cfmakeraw(inner_termios); } + termios.update_wrapper(); } +/// Set input baud rate (see +/// [cfsetispeed(3p)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/cfsetispeed.html)). +/// +/// `cfsetispeed()` sets the intput baud rate in the given Termios structure. pub fn cfsetispeed(termios: &mut Termios, baud: BaudRate) -> Result<()> { - Errno::result(unsafe { - ffi::cfsetispeed(termios, baud as speed_t) - }).map(drop) + let inner_termios = unsafe { termios.get_libc_termios_mut() }; + let res = unsafe { libc::cfsetispeed(inner_termios, baud as libc::speed_t) }; + termios.update_wrapper(); + Errno::result(res).map(drop) } +/// Set output baud rate (see +/// [cfsetospeed(3p)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/cfsetospeed.html)). +/// +/// `cfsetospeed()` sets the output baud rate in the given termios structure. pub fn cfsetospeed(termios: &mut Termios, baud: BaudRate) -> Result<()> { - Errno::result(unsafe { - ffi::cfsetospeed(termios, baud as speed_t) - }).map(drop) + let inner_termios = unsafe { termios.get_libc_termios_mut() }; + let res = unsafe { libc::cfsetospeed(inner_termios, baud as libc::speed_t) }; + termios.update_wrapper(); + Errno::result(res).map(drop) } +/// Set both the input and output baud rates (see +/// [termios(3)](http://man7.org/linux/man-pages/man3/termios.3.html)). +/// +/// `cfsetspeed()` sets the input and output baud rate in the given termios structure. Note that +/// this is part of the 4.4BSD standard and not part of POSIX. +pub fn cfsetspeed(termios: &mut Termios, baud: BaudRate) -> Result<()> { + let inner_termios = unsafe { termios.get_libc_termios_mut() }; + let res = unsafe { libc::cfsetspeed(inner_termios, baud as libc::speed_t) }; + termios.update_wrapper(); + Errno::result(res).map(drop) +} + +/// Return the configuration of a port +/// [tcgetattr(3p)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/tcgetattr.html)). +/// +/// `tcgetattr()` returns a Termios structure with the current configuration for a port. Modifying +/// this structure *will not* reconfigure the port, instead the modifications should be done to +/// the `Termios` structure and then the port should be reconfigured using `tcsetattr()`. pub fn tcgetattr(fd: RawFd) -> Result<Termios> { - let mut termios = unsafe { mem::uninitialized() }; + let mut termios: libc::termios = unsafe { mem::uninitialized() }; - let res = unsafe { - ffi::tcgetattr(fd, &mut termios) - }; + let res = unsafe { libc::tcgetattr(fd, &mut termios) }; try!(Errno::result(res)); - Ok(termios) + Ok(termios.into()) } -pub fn tcsetattr(fd: RawFd, - actions: SetArg, - termios: &Termios) -> Result<()> { - Errno::result(unsafe { - ffi::tcsetattr(fd, actions as c_int, termios) - }).map(drop) +/// Set the configuration for a terminal (see +/// [tcsetattr(3p)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/tcsetattr.html)). +/// +/// `tcsetattr()` reconfigures the given port based on a given `Termios` structure. This change +/// takes affect at a time specified by `actions`. Note that this function may return success if +/// *any* of the parameters were successfully set, not only if all were set successfully. +pub fn tcsetattr(fd: RawFd, actions: SetArg, termios: &Termios) -> Result<()> { + let inner_termios = termios.get_libc_termios(); + Errno::result(unsafe { libc::tcsetattr(fd, actions as c_int, &*inner_termios) }).map(drop) } +/// Block until all output data is written (see +/// [tcdrain(3p)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/tcdrain.html)). pub fn tcdrain(fd: RawFd) -> Result<()> { - Errno::result(unsafe { - ffi::tcdrain(fd) - }).map(drop) + Errno::result(unsafe { libc::tcdrain(fd) }).map(drop) } +/// Suspend or resume the transmission or reception of data (see +/// [tcflow(3p)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/tcflow.html)). +/// +/// `tcflow()` suspends of resumes the transmission or reception of data for the given port +/// depending on the value of `action`. pub fn tcflow(fd: RawFd, action: FlowArg) -> Result<()> { - Errno::result(unsafe { - ffi::tcflow(fd, action as c_int) - }).map(drop) + Errno::result(unsafe { libc::tcflow(fd, action as c_int) }).map(drop) } +/// Discard data in the output or input queue (see +/// [tcflush(3p)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/tcflush.html)). +/// +/// `tcflush()` will discard data for a terminal port in the input queue, output queue, or both +/// depending on the value of `action`. pub fn tcflush(fd: RawFd, action: FlushArg) -> Result<()> { - Errno::result(unsafe { - ffi::tcflush(fd, action as c_int) - }).map(drop) + Errno::result(unsafe { libc::tcflush(fd, action as c_int) }).map(drop) +} + +/// Send a break for a specific duration (see +/// [tcsendbreak(3p)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/tcsendbreak.html)). +/// +/// When using asynchronous data transmission `tcsendbreak()` will transmit a continuous stream +/// of zero-valued bits for an implementation-defined duration. +pub fn tcsendbreak(fd: RawFd, duration: c_int) -> Result<()> { + Errno::result(unsafe { libc::tcsendbreak(fd, duration) }).map(drop) } -pub fn tcsendbreak(fd: RawFd, action: c_int) -> Result<()> { - Errno::result(unsafe { - ffi::tcsendbreak(fd, action) - }).map(drop) +/// Get the session controlled by the given terminal (see +/// [tcgetsid(3)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/tcgetsid.html)). +pub fn tcgetsid(fd: RawFd) -> Result<Pid> { + let res = unsafe { libc::tcgetsid(fd) }; + + Errno::result(res).map(Pid::from_raw) } diff --git a/src/unistd.rs b/src/unistd.rs index 2925f41c..196232b6 100644 --- a/src/unistd.rs +++ b/src/unistd.rs @@ -1,9 +1,11 @@ //! Safe wrappers around functions found in libc "unistd.h" header +use errno; 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 libc::{self, c_char, c_void, c_int, c_long, c_uint, size_t, pid_t, off_t, + uid_t, gid_t, mode_t}; use std::mem; use std::ffi::{CString, CStr, OsString, OsStr}; use std::os::unix::ffi::{OsStringExt, OsStrExt}; @@ -668,6 +670,40 @@ pub fn gethostname<'a>(buffer: &'a mut [u8]) -> Result<&'a CStr> { }) } +/// Close a raw file descriptor +/// +/// Be aware that many Rust types implicitly close-on-drop, including +/// `std::fs::File`. Explicitly closing them with this method too can result in +/// a double-close condition, which can cause confusing `EBADF` errors in +/// seemingly unrelated code. Caveat programmer. +/// +/// # Examples +/// +/// ```no_run +/// extern crate tempfile; +/// extern crate nix; +/// +/// use std::os::unix::io::AsRawFd; +/// use nix::unistd::close; +/// +/// fn main() { +/// let mut f = tempfile::tempfile().unwrap(); +/// close(f.as_raw_fd()).unwrap(); // Bad! f will also close on drop! +/// } +/// ``` +/// +/// ```rust +/// extern crate tempfile; +/// extern crate nix; +/// +/// use std::os::unix::io::IntoRawFd; +/// use nix::unistd::close; +/// +/// fn main() { +/// let mut f = tempfile::tempfile().unwrap(); +/// close(f.into_raw_fd()).unwrap(); // Good. into_raw_fd consumes f +/// } +/// ``` pub fn close(fd: RawFd) -> Result<()> { let res = unsafe { libc::close(fd) }; Errno::result(res).map(drop) @@ -937,6 +973,622 @@ pub fn mkstemp<P: ?Sized + NixPath>(template: &P) -> Result<(RawFd, PathBuf)> { Ok((fd, PathBuf::from(pathname))) } +/// Variable names for `pathconf` +/// +/// Nix uses the same naming convention for these variables as the +/// [getconf(1)](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/getconf.html) utility. +/// That is, `PathconfVar` variables have the same name as the abstract +/// variables shown in the `pathconf(2)` man page. Usually, it's the same as +/// the C variable name without the leading `_PC_`. +/// +/// POSIX 1003.1-2008 standardizes all of these variables, but some OSes choose +/// not to implement variables that cannot change at runtime. +/// +/// # References +/// +/// - [pathconf(2)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/pathconf.html) +/// - [limits.h](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/limits.h.html) +/// - [unistd.h](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/unistd.h.html) +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[repr(i32)] +pub enum PathconfVar { + #[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "linux", + target_os = "netbsd", target_os = "openbsd"))] + /// Minimum number of bits needed to represent, as a signed integer value, + /// the maximum size of a regular file allowed in the specified directory. + FILESIZEBITS = libc::_PC_FILESIZEBITS, + /// Maximum number of links to a single file. + LINK_MAX = libc::_PC_LINK_MAX, + /// Maximum number of bytes in a terminal canonical input line. + MAX_CANON = libc::_PC_MAX_CANON, + /// Minimum number of bytes for which space is available in a terminal input + /// queue; therefore, the maximum number of bytes a conforming application + /// may require to be typed as input before reading them. + MAX_INPUT = libc::_PC_MAX_INPUT, + /// Maximum number of bytes in a filename (not including the terminating + /// null of a filename string). + NAME_MAX = libc::_PC_NAME_MAX, + /// Maximum number of bytes the implementation will store as a pathname in a + /// user-supplied buffer of unspecified size, including the terminating null + /// character. Minimum number the implementation will accept as the maximum + /// number of bytes in a pathname. + PATH_MAX = libc::_PC_PATH_MAX, + /// Maximum number of bytes that is guaranteed to be atomic when writing to + /// a pipe. + PIPE_BUF = libc::_PC_PIPE_BUF, + #[cfg(any(target_os = "android", target_os = "dragonfly", target_os = "linux", + target_os = "netbsd", target_os = "openbsd"))] + /// Symbolic links can be created. + POSIX2_SYMLINKS = libc::_PC_2_SYMLINKS, + #[cfg(any(target_os = "android", target_os = "dragonfly", target_os = "freebsd", + target_os = "linux", target_os = "openbsd"))] + /// Minimum number of bytes of storage actually allocated for any portion of + /// a file. + POSIX_ALLOC_SIZE_MIN = libc::_PC_ALLOC_SIZE_MIN, + #[cfg(any(target_os = "android", target_os = "dragonfly", target_os = "freebsd", + target_os = "linux", target_os = "openbsd"))] + /// Recommended increment for file transfer sizes between the + /// `POSIX_REC_MIN_XFER_SIZE` and `POSIX_REC_MAX_XFER_SIZE` values. + POSIX_REC_INCR_XFER_SIZE = libc::_PC_REC_INCR_XFER_SIZE, + #[cfg(any(target_os = "android", target_os = "dragonfly", target_os = "freebsd", + target_os = "linux", target_os = "openbsd"))] + /// Maximum recommended file transfer size. + POSIX_REC_MAX_XFER_SIZE = libc::_PC_REC_MAX_XFER_SIZE, + #[cfg(any(target_os = "android", target_os = "dragonfly", target_os = "freebsd", + target_os = "linux", target_os = "openbsd"))] + /// Minimum recommended file transfer size. + POSIX_REC_MIN_XFER_SIZE = libc::_PC_REC_MIN_XFER_SIZE, + #[cfg(any(target_os = "android", target_os = "dragonfly", target_os = "freebsd", + target_os = "linux", target_os = "openbsd"))] + /// Recommended file transfer buffer alignment. + POSIX_REC_XFER_ALIGN = libc::_PC_REC_XFER_ALIGN, + #[cfg(any(target_os = "android", target_os = "dragonfly", target_os = "freebsd", + target_os = "linux", target_os = "netbsd", target_os = "openbsd"))] + /// Maximum number of bytes in a symbolic link. + SYMLINK_MAX = libc::_PC_SYMLINK_MAX, + /// The use of `chown` and `fchown` is restricted to a process with + /// appropriate privileges, and to changing the group ID of a file only to + /// the effective group ID of the process or to one of its supplementary + /// group IDs. + _POSIX_CHOWN_RESTRICTED = libc::_PC_CHOWN_RESTRICTED, + /// Pathname components longer than {NAME_MAX} generate an error. + _POSIX_NO_TRUNC = libc::_PC_NO_TRUNC, + /// This symbol shall be defined to be the value of a character that shall + /// disable terminal special character handling. + _POSIX_VDISABLE = libc::_PC_VDISABLE, + #[cfg(any(target_os = "android", target_os = "dragonfly", target_os = "freebsd", + target_os = "linux", target_os = "openbsd"))] + /// Asynchronous input or output operations may be performed for the + /// associated file. + _POSIX_ASYNC_IO = libc::_PC_ASYNC_IO, + #[cfg(any(target_os = "android", target_os = "dragonfly", target_os = "freebsd", + target_os = "linux", target_os = "openbsd"))] + /// Prioritized input or output operations may be performed for the + /// associated file. + _POSIX_PRIO_IO = libc::_PC_PRIO_IO, + #[cfg(any(target_os = "android", target_os = "dragonfly", target_os = "freebsd", + target_os = "linux", target_os = "netbsd", target_os = "openbsd"))] + /// Synchronized input or output operations may be performed for the + /// associated file. + _POSIX_SYNC_IO = libc::_PC_SYNC_IO, + #[cfg(any(target_os = "dragonfly", target_os = "openbsd"))] + /// The resolution in nanoseconds for all file timestamps. + _POSIX_TIMESTAMP_RESOLUTION = libc::_PC_TIMESTAMP_RESOLUTION +} + +/// Like `pathconf`, but works with file descriptors instead of paths (see +/// [fpathconf(2)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/pathconf.html)) +/// +/// # Parameters +/// +/// - `fd`: The file descriptor whose variable should be interrogated +/// - `var`: The pathconf variable to lookup +/// +/// # Returns +/// +/// - `Ok(Some(x))`: the variable's limit (for limit variables) or its +/// implementation level (for option variables). Implementation levels are +/// usually a decimal-coded date, such as 200112 for POSIX 2001.12 +/// - `Ok(None)`: the variable has no limit (for limit variables) or is +/// unsupported (for option variables) +/// - `Err(x)`: an error occurred +pub fn fpathconf(fd: RawFd, var: PathconfVar) -> Result<Option<c_long>> { + let raw = unsafe { + Errno::clear(); + libc::fpathconf(fd, var as c_int) + }; + if raw == -1 { + if errno::errno() == 0 { + Ok(None) + } else { + Err(Error::Sys(Errno::last())) + } + } else { + Ok(Some(raw)) + } +} + +/// Get path-dependent configurable system variables (see +/// [pathconf(2)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/pathconf.html)) +/// +/// Returns the value of a path-dependent configurable system variable. Most +/// supported variables also have associated compile-time constants, but POSIX +/// allows their values to change at runtime. There are generally two types of +/// `pathconf` variables: options and limits. See [pathconf(2)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/pathconf.html) for more details. +/// +/// # Parameters +/// +/// - `path`: Lookup the value of `var` for this file or directory +/// - `var`: The `pathconf` variable to lookup +/// +/// # Returns +/// +/// - `Ok(Some(x))`: the variable's limit (for limit variables) or its +/// implementation level (for option variables). Implementation levels are +/// usually a decimal-coded date, such as 200112 for POSIX 2001.12 +/// - `Ok(None)`: the variable has no limit (for limit variables) or is +/// unsupported (for option variables) +/// - `Err(x)`: an error occurred +pub fn pathconf<P: ?Sized + NixPath>(path: &P, var: PathconfVar) -> Result<Option<c_long>> { + let raw = try!(path.with_nix_path(|cstr| { + unsafe { + Errno::clear(); + libc::pathconf(cstr.as_ptr(), var as c_int) + } + })); + if raw == -1 { + if errno::errno() == 0 { + Ok(None) + } else { + Err(Error::Sys(Errno::last())) + } + } else { + Ok(Some(raw)) + } +} + +/// Variable names for `sysconf` +/// +/// Nix uses the same naming convention for these variables as the +/// [getconf(1)](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/getconf.html) utility. +/// That is, `SysconfVar` variables have the same name as the abstract variables +/// shown in the `sysconf(3)` man page. Usually, it's the same as the C +/// variable name without the leading `_SC_`. +/// +/// All of these symbols are standardized by POSIX 1003.1-2008, but haven't been +/// implemented by all platforms. +/// +/// # References +/// +/// - [sysconf(3)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/sysconf.html) +/// - [unistd.h](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/unistd.h.html) +/// - [limits.h](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/limits.h.html) +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[repr(i32)] +pub enum SysconfVar { + /// Maximum number of I/O operations in a single list I/O call supported by + /// the implementation. + AIO_LISTIO_MAX = libc::_SC_AIO_LISTIO_MAX, + /// Maximum number of outstanding asynchronous I/O operations supported by + /// the implementation. + AIO_MAX = libc::_SC_AIO_MAX, + #[cfg(any(target_os="android", target_os="dragonfly", target_os="freebsd", + target_os = "ios", target_os="linux", target_os = "macos", + target_os="openbsd"))] + /// The maximum amount by which a process can decrease its asynchronous I/O + /// priority level from its own scheduling priority. + AIO_PRIO_DELTA_MAX = libc::_SC_AIO_PRIO_DELTA_MAX, + /// Maximum length of argument to the exec functions including environment data. + ARG_MAX = libc::_SC_ARG_MAX, + /// Maximum number of functions that may be registered with `atexit`. + ATEXIT_MAX = libc::_SC_ATEXIT_MAX, + /// Maximum obase values allowed by the bc utility. + BC_BASE_MAX = libc::_SC_BC_BASE_MAX, + /// Maximum number of elements permitted in an array by the bc utility. + BC_DIM_MAX = libc::_SC_BC_DIM_MAX, + /// Maximum scale value allowed by the bc utility. + BC_SCALE_MAX = libc::_SC_BC_SCALE_MAX, + /// Maximum length of a string constant accepted by the bc utility. + BC_STRING_MAX = libc::_SC_BC_STRING_MAX, + /// Maximum number of simultaneous processes per real user ID. + CHILD_MAX = libc::_SC_CHILD_MAX, + // _SC_CLK_TCK is obsolete + /// Maximum number of weights that can be assigned to an entry of the + /// LC_COLLATE order keyword in the locale definition file + COLL_WEIGHTS_MAX = libc::_SC_COLL_WEIGHTS_MAX, + /// Maximum number of timer expiration overruns. + DELAYTIMER_MAX = libc::_SC_DELAYTIMER_MAX, + /// Maximum number of expressions that can be nested within parentheses by + /// the expr utility. + EXPR_NEST_MAX = libc::_SC_EXPR_NEST_MAX, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="netbsd", + target_os="openbsd"))] + /// Maximum length of a host name (not including the terminating null) as + /// returned from the `gethostname` function + HOST_NAME_MAX = libc::_SC_HOST_NAME_MAX, + /// Maximum number of iovec structures that one process has available for + /// use with `readv` or `writev`. + IOV_MAX = libc::_SC_IOV_MAX, + /// Unless otherwise noted, the maximum length, in bytes, of a utility's + /// input line (either standard input or another file), when the utility is + /// described as processing text files. The length includes room for the + /// trailing <newline>. + LINE_MAX = libc::_SC_LINE_MAX, + /// Maximum length of a login name. + LOGIN_NAME_MAX = libc::_SC_LOGIN_NAME_MAX, + /// Maximum number of simultaneous supplementary group IDs per process. + NGROUPS_MAX = libc::_SC_NGROUPS_MAX, + /// Initial size of `getgrgid_r` and `getgrnam_r` data buffers + GETGR_R_SIZE_MAX = libc::_SC_GETGR_R_SIZE_MAX, + /// Initial size of `getpwuid_r` and `getpwnam_r` data buffers + GETPW_R_SIZE_MAX = libc::_SC_GETPW_R_SIZE_MAX, + /// The maximum number of open message queue descriptors a process may hold. + MQ_OPEN_MAX = libc::_SC_MQ_OPEN_MAX, + /// The maximum number of message priorities supported by the implementation. + MQ_PRIO_MAX = libc::_SC_MQ_PRIO_MAX, + /// A value one greater than the maximum value that the system may assign to + /// a newly-created file descriptor. + OPEN_MAX = libc::_SC_OPEN_MAX, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="openbsd"))] + /// The implementation supports the Advisory Information option. + _POSIX_ADVISORY_INFO = libc::_SC_ADVISORY_INFO, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="netbsd", + target_os="openbsd"))] + /// The implementation supports barriers. + _POSIX_BARRIERS = libc::_SC_BARRIERS, + /// The implementation supports asynchronous input and output. + _POSIX_ASYNCHRONOUS_IO = libc::_SC_ASYNCHRONOUS_IO, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="netbsd", + target_os="openbsd"))] + /// The implementation supports clock selection. + _POSIX_CLOCK_SELECTION = libc::_SC_CLOCK_SELECTION, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="netbsd", + target_os="openbsd"))] + /// The implementation supports the Process CPU-Time Clocks option. + _POSIX_CPUTIME = libc::_SC_CPUTIME, + /// The implementation supports the File Synchronization option. + _POSIX_FSYNC = libc::_SC_FSYNC, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="openbsd"))] + /// The implementation supports the IPv6 option. + _POSIX_IPV6 = libc::_SC_IPV6, + /// The implementation supports job control. + _POSIX_JOB_CONTROL = libc::_SC_JOB_CONTROL, + /// The implementation supports memory mapped Files. + _POSIX_MAPPED_FILES = libc::_SC_MAPPED_FILES, + /// The implementation supports the Process Memory Locking option. + _POSIX_MEMLOCK = libc::_SC_MEMLOCK, + /// The implementation supports the Range Memory Locking option. + _POSIX_MEMLOCK_RANGE = libc::_SC_MEMLOCK_RANGE, + /// The implementation supports memory protection. + _POSIX_MEMORY_PROTECTION = libc::_SC_MEMORY_PROTECTION, + /// The implementation supports the Message Passing option. + _POSIX_MESSAGE_PASSING = libc::_SC_MESSAGE_PASSING, + /// The implementation supports the Monotonic Clock option. + _POSIX_MONOTONIC_CLOCK = libc::_SC_MONOTONIC_CLOCK, + #[cfg(any(target_os="android", target_os="dragonfly", target_os="freebsd", + target_os = "ios", target_os="linux", target_os = "macos", + target_os="openbsd"))] + /// The implementation supports the Prioritized Input and Output option. + _POSIX_PRIORITIZED_IO = libc::_SC_PRIORITIZED_IO, + /// The implementation supports the Process Scheduling option. + _POSIX_PRIORITY_SCHEDULING = libc::_SC_PRIORITY_SCHEDULING, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="openbsd"))] + /// The implementation supports the Raw Sockets option. + _POSIX_RAW_SOCKETS = libc::_SC_RAW_SOCKETS, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="netbsd", + target_os="openbsd"))] + /// The implementation supports read-write locks. + _POSIX_READER_WRITER_LOCKS = libc::_SC_READER_WRITER_LOCKS, + #[cfg(any(target_os = "android", target_os="dragonfly", target_os="freebsd", + target_os = "ios", target_os="linux", target_os = "macos", + target_os = "openbsd"))] + /// The implementation supports realtime signals. + _POSIX_REALTIME_SIGNALS = libc::_SC_REALTIME_SIGNALS, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="netbsd", + target_os="openbsd"))] + /// The implementation supports the Regular Expression Handling option. + _POSIX_REGEXP = libc::_SC_REGEXP, + /// Each process has a saved set-user-ID and a saved set-group-ID. + _POSIX_SAVED_IDS = libc::_SC_SAVED_IDS, + /// The implementation supports semaphores. + _POSIX_SEMAPHORES = libc::_SC_SEMAPHORES, + /// The implementation supports the Shared Memory Objects option. + _POSIX_SHARED_MEMORY_OBJECTS = libc::_SC_SHARED_MEMORY_OBJECTS, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="netbsd", + target_os="openbsd"))] + /// The implementation supports the POSIX shell. + _POSIX_SHELL = libc::_SC_SHELL, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="netbsd", + target_os="openbsd"))] + /// The implementation supports the Spawn option. + _POSIX_SPAWN = libc::_SC_SPAWN, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="netbsd", + target_os="openbsd"))] + /// The implementation supports spin locks. + _POSIX_SPIN_LOCKS = libc::_SC_SPIN_LOCKS, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="openbsd"))] + /// The implementation supports the Process Sporadic Server option. + _POSIX_SPORADIC_SERVER = libc::_SC_SPORADIC_SERVER, + #[cfg(any(target_os = "ios", target_os="linux", target_os = "macos", + target_os="openbsd"))] + _POSIX_SS_REPL_MAX = libc::_SC_SS_REPL_MAX, + /// The implementation supports the Synchronized Input and Output option. + _POSIX_SYNCHRONIZED_IO = libc::_SC_SYNCHRONIZED_IO, + /// The implementation supports the Thread Stack Address Attribute option. + _POSIX_THREAD_ATTR_STACKADDR = libc::_SC_THREAD_ATTR_STACKADDR, + /// The implementation supports the Thread Stack Size Attribute option. + _POSIX_THREAD_ATTR_STACKSIZE = libc::_SC_THREAD_ATTR_STACKSIZE, + #[cfg(any(target_os = "ios", target_os="linux", target_os = "macos", + target_os="netbsd", target_os="openbsd"))] + /// The implementation supports the Thread CPU-Time Clocks option. + _POSIX_THREAD_CPUTIME = libc::_SC_THREAD_CPUTIME, + /// The implementation supports the Non-Robust Mutex Priority Inheritance + /// option. + _POSIX_THREAD_PRIO_INHERIT = libc::_SC_THREAD_PRIO_INHERIT, + /// The implementation supports the Non-Robust Mutex Priority Protection option. + _POSIX_THREAD_PRIO_PROTECT = libc::_SC_THREAD_PRIO_PROTECT, + /// The implementation supports the Thread Execution Scheduling option. + _POSIX_THREAD_PRIORITY_SCHEDULING = libc::_SC_THREAD_PRIORITY_SCHEDULING, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="netbsd", + target_os="openbsd"))] + /// The implementation supports the Thread Process-Shared Synchronization + /// option. + _POSIX_THREAD_PROCESS_SHARED = libc::_SC_THREAD_PROCESS_SHARED, + #[cfg(any(target_os="dragonfly", target_os="linux", target_os="openbsd"))] + /// The implementation supports the Robust Mutex Priority Inheritance option. + _POSIX_THREAD_ROBUST_PRIO_INHERIT = libc::_SC_THREAD_ROBUST_PRIO_INHERIT, + #[cfg(any(target_os="dragonfly", target_os="linux", target_os="openbsd"))] + /// The implementation supports the Robust Mutex Priority Protection option. + _POSIX_THREAD_ROBUST_PRIO_PROTECT = libc::_SC_THREAD_ROBUST_PRIO_PROTECT, + /// The implementation supports thread-safe functions. + _POSIX_THREAD_SAFE_FUNCTIONS = libc::_SC_THREAD_SAFE_FUNCTIONS, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="openbsd"))] + /// The implementation supports the Thread Sporadic Server option. + _POSIX_THREAD_SPORADIC_SERVER = libc::_SC_THREAD_SPORADIC_SERVER, + /// The implementation supports threads. + _POSIX_THREADS = libc::_SC_THREADS, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="openbsd"))] + /// The implementation supports timeouts. + _POSIX_TIMEOUTS = libc::_SC_TIMEOUTS, + /// The implementation supports timers. + _POSIX_TIMERS = libc::_SC_TIMERS, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="openbsd"))] + /// The implementation supports the Trace option. + _POSIX_TRACE = libc::_SC_TRACE, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="openbsd"))] + /// The implementation supports the Trace Event Filter option. + _POSIX_TRACE_EVENT_FILTER = libc::_SC_TRACE_EVENT_FILTER, + #[cfg(any(target_os = "ios", target_os="linux", target_os = "macos", + target_os="openbsd"))] + _POSIX_TRACE_EVENT_NAME_MAX = libc::_SC_TRACE_EVENT_NAME_MAX, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="openbsd"))] + /// The implementation supports the Trace Inherit option. + _POSIX_TRACE_INHERIT = libc::_SC_TRACE_INHERIT, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="openbsd"))] + /// The implementation supports the Trace Log option. + _POSIX_TRACE_LOG = libc::_SC_TRACE_LOG, + #[cfg(any(target_os = "ios", target_os="linux", target_os = "macos", + target_os="openbsd"))] + _POSIX_TRACE_NAME_MAX = libc::_SC_TRACE_NAME_MAX, + #[cfg(any(target_os = "ios", target_os="linux", target_os = "macos", + target_os="openbsd"))] + _POSIX_TRACE_SYS_MAX = libc::_SC_TRACE_SYS_MAX, + #[cfg(any(target_os = "ios", target_os="linux", target_os = "macos", + target_os="openbsd"))] + _POSIX_TRACE_USER_EVENT_MAX = libc::_SC_TRACE_USER_EVENT_MAX, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="openbsd"))] + /// The implementation supports the Typed Memory Objects option. + _POSIX_TYPED_MEMORY_OBJECTS = libc::_SC_TYPED_MEMORY_OBJECTS, + /// Integer value indicating version of this standard (C-language binding) + /// to which the implementation conforms. For implementations conforming to + /// POSIX.1-2008, the value shall be 200809L. + _POSIX_VERSION = libc::_SC_VERSION, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="netbsd", + target_os="openbsd"))] + /// The implementation provides a C-language compilation environment with + /// 32-bit `int`, `long`, `pointer`, and `off_t` types. + _POSIX_V6_ILP32_OFF32 = libc::_SC_V6_ILP32_OFF32, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="netbsd", + target_os="openbsd"))] + /// The implementation provides a C-language compilation environment with + /// 32-bit `int`, `long`, and pointer types and an `off_t` type using at + /// least 64 bits. + _POSIX_V6_ILP32_OFFBIG = libc::_SC_V6_ILP32_OFFBIG, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="netbsd", + target_os="openbsd"))] + /// The implementation provides a C-language compilation environment with + /// 32-bit `int` and 64-bit `long`, `pointer`, and `off_t` types. + _POSIX_V6_LP64_OFF64 = libc::_SC_V6_LP64_OFF64, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="netbsd", + target_os="openbsd"))] + /// The implementation provides a C-language compilation environment with an + /// `int` type using at least 32 bits and `long`, pointer, and `off_t` types + /// using at least 64 bits. + _POSIX_V6_LPBIG_OFFBIG = libc::_SC_V6_LPBIG_OFFBIG, + /// The implementation supports the C-Language Binding option. + _POSIX2_C_BIND = libc::_SC_2_C_BIND, + /// The implementation supports the C-Language Development Utilities option. + _POSIX2_C_DEV = libc::_SC_2_C_DEV, + /// The implementation supports the Terminal Characteristics option. + _POSIX2_CHAR_TERM = libc::_SC_2_CHAR_TERM, + /// The implementation supports the FORTRAN Development Utilities option. + _POSIX2_FORT_DEV = libc::_SC_2_FORT_DEV, + /// The implementation supports the FORTRAN Runtime Utilities option. + _POSIX2_FORT_RUN = libc::_SC_2_FORT_RUN, + /// The implementation supports the creation of locales by the localedef + /// utility. + _POSIX2_LOCALEDEF = libc::_SC_2_LOCALEDEF, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="netbsd", + target_os="openbsd"))] + /// The implementation supports the Batch Environment Services and Utilities + /// option. + _POSIX2_PBS = libc::_SC_2_PBS, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="netbsd", + target_os="openbsd"))] + /// The implementation supports the Batch Accounting option. + _POSIX2_PBS_ACCOUNTING = libc::_SC_2_PBS_ACCOUNTING, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="netbsd", + target_os="openbsd"))] + /// The implementation supports the Batch Checkpoint/Restart option. + _POSIX2_PBS_CHECKPOINT = libc::_SC_2_PBS_CHECKPOINT, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="netbsd", + target_os="openbsd"))] + /// The implementation supports the Locate Batch Job Request option. + _POSIX2_PBS_LOCATE = libc::_SC_2_PBS_LOCATE, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="netbsd", + target_os="openbsd"))] + /// The implementation supports the Batch Job Message Request option. + _POSIX2_PBS_MESSAGE = libc::_SC_2_PBS_MESSAGE, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="netbsd", + target_os="openbsd"))] + /// The implementation supports the Track Batch Job Request option. + _POSIX2_PBS_TRACK = libc::_SC_2_PBS_TRACK, + /// The implementation supports the Software Development Utilities option. + _POSIX2_SW_DEV = libc::_SC_2_SW_DEV, + /// The implementation supports the User Portability Utilities option. + _POSIX2_UPE = libc::_SC_2_UPE, + /// Integer value indicating version of the Shell and Utilities volume of + /// POSIX.1 to which the implementation conforms. + _POSIX2_VERSION = libc::_SC_2_VERSION, + /// The size of a system page in bytes. + /// + /// POSIX also defines an alias named `PAGESIZE`, but Rust does not allow two + /// enum constants to have the same value, so nix omits `PAGESIZE`. + PAGE_SIZE = libc::_SC_PAGE_SIZE, + PTHREAD_DESTRUCTOR_ITERATIONS = libc::_SC_THREAD_DESTRUCTOR_ITERATIONS, + PTHREAD_KEYS_MAX = libc::_SC_THREAD_KEYS_MAX, + PTHREAD_STACK_MIN = libc::_SC_THREAD_STACK_MIN, + PTHREAD_THREADS_MAX = libc::_SC_THREAD_THREADS_MAX, + RE_DUP_MAX = libc::_SC_RE_DUP_MAX, + #[cfg(any(target_os="android", target_os="dragonfly", target_os="freebsd", + target_os = "ios", target_os="linux", target_os = "macos", + target_os="openbsd"))] + RTSIG_MAX = libc::_SC_RTSIG_MAX, + SEM_NSEMS_MAX = libc::_SC_SEM_NSEMS_MAX, + #[cfg(any(target_os="android", target_os="dragonfly", target_os="freebsd", + target_os = "ios", target_os="linux", target_os = "macos", + target_os="openbsd"))] + SEM_VALUE_MAX = libc::_SC_SEM_VALUE_MAX, + #[cfg(any(target_os = "android", target_os="dragonfly", target_os="freebsd", + target_os = "ios", target_os="linux", target_os = "macos", + target_os = "openbsd"))] + SIGQUEUE_MAX = libc::_SC_SIGQUEUE_MAX, + STREAM_MAX = libc::_SC_STREAM_MAX, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="netbsd", + target_os="openbsd"))] + SYMLOOP_MAX = libc::_SC_SYMLOOP_MAX, + TIMER_MAX = libc::_SC_TIMER_MAX, + TTY_NAME_MAX = libc::_SC_TTY_NAME_MAX, + TZNAME_MAX = libc::_SC_TZNAME_MAX, + #[cfg(any(target_os="android", target_os="dragonfly", target_os="freebsd", + target_os = "ios", target_os="linux", target_os = "macos", + target_os="openbsd"))] + /// The implementation supports the X/Open Encryption Option Group. + _XOPEN_CRYPT = libc::_SC_XOPEN_CRYPT, + #[cfg(any(target_os="android", target_os="dragonfly", target_os="freebsd", + target_os = "ios", target_os="linux", target_os = "macos", + target_os="openbsd"))] + /// The implementation supports the Issue 4, Version 2 Enhanced + /// Internationalization Option Group. + _XOPEN_ENH_I18N = libc::_SC_XOPEN_ENH_I18N, + #[cfg(any(target_os="android", target_os="dragonfly", target_os="freebsd", + target_os = "ios", target_os="linux", target_os = "macos", + target_os="openbsd"))] + _XOPEN_LEGACY = libc::_SC_XOPEN_LEGACY, + #[cfg(any(target_os="android", target_os="dragonfly", target_os="freebsd", + target_os = "ios", target_os="linux", target_os = "macos", + target_os="openbsd"))] + /// The implementation supports the X/Open Realtime Option Group. + _XOPEN_REALTIME = libc::_SC_XOPEN_REALTIME, + #[cfg(any(target_os="android", target_os="dragonfly", target_os="freebsd", + target_os = "ios", target_os="linux", target_os = "macos", + target_os="openbsd"))] + /// The implementation supports the X/Open Realtime Threads Option Group. + _XOPEN_REALTIME_THREADS = libc::_SC_XOPEN_REALTIME_THREADS, + /// The implementation supports the Issue 4, Version 2 Shared Memory Option + /// Group. + _XOPEN_SHM = libc::_SC_XOPEN_SHM, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="openbsd"))] + /// The implementation supports the XSI STREAMS Option Group. + _XOPEN_STREAMS = libc::_SC_XOPEN_STREAMS, + #[cfg(any(target_os="android", target_os="dragonfly", target_os="freebsd", + target_os = "ios", target_os="linux", target_os = "macos", + target_os="openbsd"))] + /// The implementation supports the XSI option + _XOPEN_UNIX = libc::_SC_XOPEN_UNIX, + #[cfg(any(target_os="android", target_os="dragonfly", target_os="freebsd", + target_os = "ios", target_os="linux", target_os = "macos", + target_os="openbsd"))] + /// Integer value indicating version of the X/Open Portability Guide to + /// which the implementation conforms. + _XOPEN_VERSION = libc::_SC_XOPEN_VERSION, +} + +/// Get configurable system variables (see +/// [sysconf(3)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/sysconf.html)) +/// +/// Returns the value of a configurable system variable. Most supported +/// variables also have associated compile-time constants, but POSIX +/// allows their values to change at runtime. There are generally two types of +/// sysconf variables: options and limits. See sysconf(3) for more details. +/// +/// # Returns +/// +/// - `Ok(Some(x))`: the variable's limit (for limit variables) or its +/// implementation level (for option variables). Implementation levels are +/// usually a decimal-coded date, such as 200112 for POSIX 2001.12 +/// - `Ok(None)`: the variable has no limit (for limit variables) or is +/// unsupported (for option variables) +/// - `Err(x)`: an error occurred +pub fn sysconf(var: SysconfVar) -> Result<Option<c_long>> { + let raw = unsafe { + Errno::clear(); + libc::sysconf(var as c_int) + }; + if raw == -1 { + if errno::errno() == 0 { + Ok(None) + } else { + Err(Error::Sys(Errno::last())) + } + } else { + Ok(Some(raw)) + } +} + #[cfg(any(target_os = "linux", target_os = "android"))] mod linux { use libc; diff --git a/test/sys/test_socket.rs b/test/sys/test_socket.rs index b5465aa0..9f9c9bf0 100644 --- a/test/sys/test_socket.rs +++ b/test/sys/test_socket.rs @@ -131,7 +131,7 @@ pub fn test_scm_rights() { panic!("unexpected cmsg"); } } - assert_eq!(msg.flags & (MSG_TRUNC | MSG_CTRUNC), MsgFlags::empty()); + assert!(!msg.flags.intersects(MSG_TRUNC | MSG_CTRUNC)); close(fd2).unwrap(); } @@ -145,6 +145,43 @@ pub fn test_scm_rights() { close(w).unwrap(); } +// Verify `sendmsg` builds a valid `msghdr` when passing an empty +// `cmsgs` argument. This should result in a msghdr with a nullptr +// msg_control field and a msg_controllen of 0 when calling into the +// raw `sendmsg`. +#[test] +pub fn test_sendmsg_empty_cmsgs() { + use nix::sys::uio::IoVec; + use nix::unistd::close; + use nix::sys::socket::{socketpair, sendmsg, recvmsg, + AddressFamily, SockType, SockFlag, + CmsgSpace, MsgFlags, + MSG_TRUNC, MSG_CTRUNC}; + + let (fd1, fd2) = socketpair(AddressFamily::Unix, SockType::Stream, 0, + SockFlag::empty()) + .unwrap(); + + { + let iov = [IoVec::from_slice(b"hello")]; + assert_eq!(sendmsg(fd1, &iov, &[], MsgFlags::empty(), None).unwrap(), 5); + close(fd1).unwrap(); + } + + { + let mut buf = [0u8; 5]; + let iov = [IoVec::from_mut_slice(&mut buf[..])]; + let mut cmsgspace: CmsgSpace<[RawFd; 1]> = CmsgSpace::new(); + let msg = recvmsg(fd2, &iov, Some(&mut cmsgspace), MsgFlags::empty()).unwrap(); + + for _ in msg.cmsgs() { + panic!("unexpected cmsg"); + } + assert!(!msg.flags.intersects(MSG_TRUNC | MSG_CTRUNC)); + close(fd2).unwrap(); + } +} + // Test creating and using named unix domain sockets #[test] pub fn test_unixdomain() { diff --git a/test/sys/test_termios.rs b/test/sys/test_termios.rs index a41304d7..c6572052 100644 --- a/test/sys/test_termios.rs +++ b/test/sys/test_termios.rs @@ -1,21 +1,126 @@ +use std::os::unix::prelude::*; +use tempfile::tempfile; + +use nix::{Error, fcntl}; use nix::errno::Errno; -use nix::sys::termios; -use nix::{Error, unistd}; +use nix::pty::openpty; +use nix::sys::termios::{self, ECHO, OPOST, OCRNL, Termios, tcgetattr}; +use nix::unistd::{read, write, close}; -#[test] -fn test_tcgetattr() { - for fd in 0..5 { - let termios = termios::tcgetattr(fd); - match unistd::isatty(fd) { - // If `fd` is a TTY, tcgetattr must succeed. - Ok(true) => assert!(termios.is_ok()), - // If it's an invalid file descriptor, tcgetattr should also return - // the same error - Err(Error::Sys(Errno::EBADF)) => { - assert_eq!(termios.err(), Some(Error::Sys(Errno::EBADF))); - }, - // Otherwise it should return any error - _ => assert!(termios.is_err()) - } +/// Helper function analogous to std::io::Write::write_all, but for `RawFd`s +fn write_all(f: RawFd, buf: &[u8]) { + let mut len = 0; + while len < buf.len() { + len += write(f, &buf[len..]).unwrap(); } } + +// Test tcgetattr on a terminal +#[test] +fn test_tcgetattr_pty() { + let pty = openpty(None, None).unwrap(); + assert!(termios::tcgetattr(pty.master).is_ok()); + close(pty.master).unwrap(); + close(pty.slave).unwrap(); +} +// Test tcgetattr on something that isn't a terminal +#[test] +fn test_tcgetattr_enotty() { + let file = tempfile().unwrap(); + assert_eq!(termios::tcgetattr(file.as_raw_fd()).err(), + Some(Error::Sys(Errno::ENOTTY))); +} + +// Test tcgetattr on an invalid file descriptor +#[test] +fn test_tcgetattr_ebadf() { + assert_eq!(termios::tcgetattr(-1).err(), + Some(Error::Sys(Errno::EBADF))); +} + +// Test modifying output flags +#[test] +fn test_output_flags() { + // Open one pty to get attributes for the second one + let mut termios = { + let pty = openpty(None, None).unwrap(); + assert!(pty.master > 0); + assert!(pty.slave > 0); + let termios = tcgetattr(pty.master).unwrap(); + close(pty.master).unwrap(); + close(pty.slave).unwrap(); + termios + }; + + // Make sure postprocessing '\r' isn't specified by default or this test is useless. + assert!(!termios.output_flags.contains(OPOST | OCRNL)); + + // Specify that '\r' characters should be transformed to '\n' + // OPOST is specified to enable post-processing + termios.output_flags.insert(OPOST | OCRNL); + + // Open a pty + let pty = openpty(None, &termios).unwrap(); + assert!(pty.master > 0); + assert!(pty.slave > 0); + + // Write into the master + let string = "foofoofoo\r"; + write_all(pty.master, string.as_bytes()); + + // Read from the slave verifying that the output has been properly transformed + let mut buf = [0u8; 10]; + ::read_exact(pty.slave, &mut buf); + let transformed_string = "foofoofoo\n"; + close(pty.master).unwrap(); + close(pty.slave).unwrap(); + assert_eq!(&buf, transformed_string.as_bytes()); +} + +// Test modifying local flags +#[test] +fn test_local_flags() { + // Open one pty to get attributes for the second one + let mut termios = { + let pty = openpty(None, None).unwrap(); + assert!(pty.master > 0); + assert!(pty.slave > 0); + let termios = tcgetattr(pty.master).unwrap(); + close(pty.master).unwrap(); + close(pty.slave).unwrap(); + termios + }; + + // Make sure echo is specified by default or this test is useless. + assert!(termios.local_flags.contains(ECHO)); + + // Disable local echo + termios.local_flags.remove(ECHO); + + // Open a new pty with our modified termios settings + let pty = openpty(None, &termios).unwrap(); + assert!(pty.master > 0); + assert!(pty.slave > 0); + + // Set the master is in nonblocking mode or reading will never return. + let flags = fcntl::fcntl(pty.master, fcntl::F_GETFL).unwrap(); + let new_flags = fcntl::OFlag::from_bits(flags).unwrap() | fcntl::O_NONBLOCK; + fcntl::fcntl(pty.master, fcntl::F_SETFL(new_flags)).unwrap(); + + // Write into the master + let string = "foofoofoo\r"; + write_all(pty.master, string.as_bytes()); + + // Try to read from the master, which should not have anything as echoing was disabled. + let mut buf = [0u8; 10]; + let read = read(pty.master, &mut buf).unwrap_err(); + close(pty.master).unwrap(); + close(pty.slave).unwrap(); + assert_eq!(read, Error::Sys(Errno::EAGAIN)); +} + +#[test] +fn test_cfmakeraw() { + let mut termios = unsafe { Termios::default_uninit() }; + termios::cfmakeraw(&mut termios); +} diff --git a/test/test_pty.rs b/test/test_pty.rs index 8528caf7..55316ab4 100644 --- a/test/test_pty.rs +++ b/test/test_pty.rs @@ -1,5 +1,7 @@ +use std::io::Write; use std::path::Path; use std::os::unix::prelude::*; +use tempfile::tempfile; use nix::fcntl::{O_RDWR, open}; use nix::pty::*; @@ -7,6 +9,20 @@ use nix::sys::stat; use nix::sys::termios::*; use nix::unistd::{write, close}; +/// Regression test for Issue #659 +/// This is the correct way to explicitly close a PtyMaster +#[test] +fn test_explicit_close() { + let mut f = { + let m = posix_openpt(O_RDWR).unwrap(); + close(m.into_raw_fd()).unwrap(); + tempfile().unwrap() + }; + // This should work. But if there's been a double close, then it will + // return EBADF + f.write(b"whatever").unwrap(); +} + /// Test equivalence of `ptsname` and `ptsname_r` #[test] #[cfg(any(target_os = "android", target_os = "linux"))] @@ -139,7 +155,8 @@ fn test_openpty_with_termios() { close(pty.slave).unwrap(); termios }; - termios.c_oflag &= !ONLCR; + // Make sure newlines are not transformed so the data is preserved when sent. + termios.output_flags.remove(ONLCR); let pty = openpty(None, &termios).unwrap(); // Must be valid file descriptors diff --git a/test/test_ptymaster_drop.rs b/test/test_ptymaster_drop.rs new file mode 100644 index 00000000..664a4dd6 --- /dev/null +++ b/test/test_ptymaster_drop.rs @@ -0,0 +1,21 @@ +extern crate nix; + +use nix::fcntl::O_RDWR; +use nix::pty::*; +use nix::unistd::close; +use std::os::unix::io::AsRawFd; + +/// Regression test for Issue #659 +/// PtyMaster should panic rather than double close the file descriptor +/// This must run in its own test process because it deliberately creates a race +/// condition. +#[test] +#[should_panic(expected = "Closing an invalid file descriptor!")] +// In Travis on i686-unknown-linux-musl, this test gets SIGABRT. I don't know +// why. It doesn't happen on any other target, and it doesn't happen on my PC. +#[cfg_attr(all(target_env = "musl", target_arch = "x86"), ignore)] +fn test_double_close() { + let m = posix_openpt(O_RDWR).unwrap(); + close(m.as_raw_fd()).unwrap(); + drop(m); // should panic here +} diff --git a/test/test_unistd.rs b/test/test_unistd.rs index d5e58fd5..e6e7b5f2 100644 --- a/test/test_unistd.rs +++ b/test/test_unistd.rs @@ -230,3 +230,35 @@ execve_test_factory!(test_execve, execve, b"/bin/sh", b"/system/bin/sh"); #[cfg(any(target_os = "linux", target_os = "android"))] #[cfg(feature = "execvpe")] execve_test_factory!(test_execvpe, execvpe, b"sh", b"sh"); + +#[test] +fn test_fpathconf_limited() { + let f = tempfile().unwrap(); + // AFAIK, PATH_MAX is limited on all platforms, so it makes a good test + let path_max = fpathconf(f.as_raw_fd(), PathconfVar::PATH_MAX); + assert!(path_max.expect("fpathconf failed").expect("PATH_MAX is unlimited") > 0); +} + +#[test] +fn test_pathconf_limited() { + // AFAIK, PATH_MAX is limited on all platforms, so it makes a good test + let path_max = pathconf(".", PathconfVar::PATH_MAX); + assert!(path_max.expect("pathconf failed").expect("PATH_MAX is unlimited") > 0); +} + +#[test] +fn test_sysconf_limited() { + // AFAIK, OPEN_MAX is limited on all platforms, so it makes a good test + let open_max = sysconf(SysconfVar::OPEN_MAX); + assert!(open_max.expect("sysconf failed").expect("OPEN_MAX is unlimited") > 0); +} + +#[cfg(target_os = "freebsd")] +#[test] +fn test_sysconf_unsupported() { + // I know of no sysconf variables that are unsupported everywhere, but + // _XOPEN_CRYPT is unsupported on FreeBSD 11.0, which is one of the platforms + // we test. + let open_max = sysconf(SysconfVar::_XOPEN_CRYPT); + assert!(open_max.expect("sysconf failed").is_none()) +} |