diff --git a/.travis.yml b/.travis.yml index f0e94c67c3..aa23815797 100644 --- a/.travis.yml +++ b/.travis.yml @@ -108,23 +108,6 @@ matrix: 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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ceae9c275..b2f82ef651 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,30 +21,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 @@ -62,6 +64,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). 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 @@ -75,7 +79,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)) @@ -111,7 +115,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}` diff --git a/README.md b/README.md index 8cda561a1a..4ccc47857e 100644 --- a/README.md +++ b/README.md @@ -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 b8707d0b7e..d8284f71ec 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 { diff --git a/src/pty.rs b/src/pty.rs index c450e0588b..88c9cc14e2 100644 --- a/src/pty.rs +++ b/src/pty.rs @@ -188,23 +188,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 783b83642b..627f769718 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/termios.rs b/src/sys/termios.rs index 0f8bd6c9e2..8651a84da6 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/test/sys/test_termios.rs b/test/sys/test_termios.rs index a41304d73f..308cf62c2b 100644 --- a/test/sys/test_termios.rs +++ b/test/sys/test_termios.rs @@ -1,6 +1,18 @@ +use std::os::unix::prelude::*; + +use nix::{Error, fcntl, unistd}; 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}; + +/// 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] fn test_tcgetattr() { @@ -19,3 +31,82 @@ fn test_tcgetattr() { } } } + +// 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(); + 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"; + 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(); + 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(); + 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 8528caf75a..53e94724ac 100644 --- a/test/test_pty.rs +++ b/test/test_pty.rs @@ -139,7 +139,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