From b94ea23b23490c6c231f95b226f4bcebf98343b5 Mon Sep 17 00:00:00 2001 From: Bryant Mairs Date: Tue, 23 Jan 2018 21:45:08 -0800 Subject: [PATCH] Support arbitrary baud rates on BSDs --- CHANGELOG.md | 4 + src/sys/termios.rs | 291 ++++++++++++++++++++++++++++++++------- test/sys/test_termios.rs | 1 + 3 files changed, 245 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c21ef19d6..9f74223369 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -98,6 +98,10 @@ This project adheres to [Semantic Versioning](http://semver.org/). ([#814](https://github.com/nix-rust/nix/pull/814)) - Removed return type from `pause`. ([#829](https://github.com/nix-rust/nix/pull/829)) +- Changed the termios APIs to allow for using a `u32` instead of the `BaudRate` + enum on BSD platforms to support arbitrary baud rates. See the module docs for + `nix::sys::termios` for more details. + ([#843](https://github.com/nix-rust/nix/pull/843)) ### Fixed - Fix compilation and tests for OpenBSD targets diff --git a/src/sys/termios.rs b/src/sys/termios.rs index b768caa8ba..4782e09d34 100644 --- a/src/sys/termios.rs +++ b/src/sys/termios.rs @@ -42,7 +42,125 @@ //! termios.control_flags & ControlFlags::CSIZE == ControlFlags::CS5; //! termios.control_flags |= ControlFlags::CS5; //! ``` - +//! +//! # Baud rates +//! +//! This API is not consistent across platforms when it comes to `BaudRate`: Android and Linux both +//! only support the rates specified by the `BaudRate` enum through their termios API while the BSDs +//! support arbitrary baud rates as the values of the `BaudRate` enum constants are the same integer +//! value of the constant (`B9600` == `9600`). Therefore the `nix::termios` API uses the following +//! conventions: +//! +//! * `cfgetispeed()` - Returns `u32` on BSDs, `BaudRate` on Android/Linux +//! * `cfgetospeed()` - Returns `u32` on BSDs, `BaudRate` on Android/Linux +//! * `cfsetispeed()` - Takes `u32` or `BaudRate` on BSDs, `BaudRate` on Android/Linux +//! * `cfsetospeed()` - Takes `u32` or `BaudRate` on BSDs, `BaudRate` on Android/Linux +//! * `cfsetspeed()` - Takes `u32` or `BaudRate` on BSDs, `BaudRate` on Android/Linux +//! +//! The most common use case of specifying a baud rate using the enum will work the same across +//! platforms: +//! +//! ```rust +//! # #[macro_use] extern crate nix; +//! # use nix::sys::termios::{BaudRate, cfsetispeed, cfsetospeed, cfsetspeed, Termios}; +//! # fn main() { +//! # let mut t = unsafe { Termios::default_uninit() }; +//! cfsetispeed(&mut t, BaudRate::B9600); +//! cfsetospeed(&mut t, BaudRate::B9600); +//! cfsetspeed(&mut t, BaudRate::B9600); +//! # } +//! ``` +//! +//! Additionally round-tripping baud rates is consistent across platforms: +//! +//! ```rust +//! # extern crate nix; +//! # use nix::sys::termios::{BaudRate, cfgetispeed, cfgetospeed, cfsetispeed, cfsetspeed, Termios}; +//! # fn main() { +//! # let mut t = unsafe { Termios::default_uninit() }; +//! # cfsetspeed(&mut t, BaudRate::B9600); +//! let speed = cfgetispeed(&t); +//! assert!(speed == cfgetospeed(&t)); +//! cfsetispeed(&mut t, speed); +//! # } +//! ``` +//! +//! On non-BSDs, `cfgetispeed()` and `cfgetospeed()` both return a `BaudRate`: +//! +// FIXME: Replace `ignore` with `compile_fail` once 1.22 is the minimum support Rust version +#![cfg_attr(any(target_os = "freebsd", target_os = "dragonfly", target_os = "ios", + target_os = "macos", target_os = "netbsd", target_os = "openbsd"), + doc = " ```rust,ignore")] +#![cfg_attr(not(any(target_os = "freebsd", target_os = "dragonfly", target_os = "ios", + target_os = "macos", target_os = "netbsd", target_os = "openbsd")), + doc = " ```rust")] +//! # extern crate nix; +//! # use nix::sys::termios::{BaudRate, cfgetispeed, cfgetospeed, cfsetspeed, Termios}; +//! # fn main() { +//! # let mut t = unsafe { Termios::default_uninit() }; +//! # cfsetspeed(&mut t, BaudRate::B9600); +//! assert!(cfgetispeed(&t) == BaudRate::B9600); +//! assert!(cfgetospeed(&t) == BaudRate::B9600); +//! # } +//! ``` +//! +//! But on the BSDs, `cfgetispeed()` and `cfgetospeed()` both return `u32`s: +//! +// FIXME: Replace `ignore` with `compile_fail` once 1.22 is the minimum support Rust version +#![cfg_attr(any(target_os = "freebsd", target_os = "dragonfly", target_os = "ios", + target_os = "macos", target_os = "netbsd", target_os = "openbsd"), + doc = " ```rust")] +#![cfg_attr(not(any(target_os = "freebsd", target_os = "dragonfly", target_os = "ios", + target_os = "macos", target_os = "netbsd", target_os = "openbsd")), + doc = " ```rust,ignore")] +//! # extern crate nix; +//! # use nix::sys::termios::{BaudRate, cfgetispeed, cfgetospeed, cfsetspeed, Termios}; +//! # fn main() { +//! # let mut t = unsafe { Termios::default_uninit() }; +//! # cfsetspeed(&mut t, 9600u32); +//! assert!(cfgetispeed(&t) == 9600u32); +//! assert!(cfgetospeed(&t) == 9600u32); +//! # } +//! ``` +//! +//! It's trivial to convert from a `BaudRate` to a `u32` on BSDs: +//! +// FIXME: Replace `ignore` with `compile_fail` once 1.22 is the minimum support Rust version +#![cfg_attr(any(target_os = "freebsd", target_os = "dragonfly", target_os = "ios", + target_os = "macos", target_os = "netbsd", target_os = "openbsd"), + doc = " ```rust")] +#![cfg_attr(not(any(target_os = "freebsd", target_os = "dragonfly", target_os = "ios", + target_os = "macos", target_os = "netbsd", target_os = "openbsd")), + doc = " ```rust,ignore")] +//! # extern crate nix; +//! # use nix::sys::termios::{BaudRate, cfgetispeed, cfsetspeed, Termios}; +//! # fn main() { +//! # let mut t = unsafe { Termios::default_uninit() }; +//! # cfsetspeed(&mut t, 9600u32); +//! assert!(cfgetispeed(&t) == BaudRate::B9600.into()); +//! assert!(u32::from(BaudRate::B9600) == 9600u32); +//! # } +//! ``` +//! +//! And on BSDs you can specify arbitrary baud rates (**note** this depends on hardware support) +//! by specifying baud rates directly using `u32`s: +//! +// FIXME: Replace `ignore` with `compile_fail` once 1.22 is the minimum support Rust version +#![cfg_attr(any(target_os = "freebsd", target_os = "dragonfly", target_os = "ios", + target_os = "macos", target_os = "netbsd", target_os = "openbsd"), + doc = " ```rust")] +#![cfg_attr(not(any(target_os = "freebsd", target_os = "dragonfly", target_os = "ios", + target_os = "macos", target_os = "netbsd", target_os = "openbsd")), + doc = " ```rust,ignore")] +//! # extern crate nix; +//! # use nix::sys::termios::{cfsetispeed, cfsetospeed, cfsetspeed, Termios}; +//! # fn main() { +//! # let mut t = unsafe { Termios::default_uninit() }; +//! cfsetispeed(&mut t, 9600u32); +//! cfsetospeed(&mut t, 9600u32); +//! cfsetspeed(&mut t, 9600u32); +//! # } +//! ``` use Result; use errno::Errno; use libc::{self, c_int, tcflag_t}; @@ -173,7 +291,10 @@ impl From for libc::termios { } libc_enum!{ - /// Baud rates supported by the system + /// Baud rates supported by the system. + /// + /// For the BSDs, arbitrary baud rates can be specified by using `u32`s directly instead of this + /// enum. /// /// B0 is special and will disable the port. #[cfg_attr(all(any(target_os = "ios", target_os = "macos"), target_pointer_width = "64"), repr(u64))] @@ -354,6 +475,13 @@ impl From for BaudRate { } } +// TODO: Include `TryFrom for BaudRate` once that API stabilizes +impl From for u32 { + fn from(b: BaudRate) -> u32 { + b as u32 + } +} + // TODO: Add TCSASOFT, which will require treating this as a bitfield. libc_enum! { /// Specify when a port configuration change should occur. @@ -761,22 +889,117 @@ libc_bitflags! { } } -/// 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 { - let inner_termios = termios.get_libc_termios(); - unsafe { libc::cfgetispeed(&*inner_termios) }.into() -} +cfg_if!{ + if #[cfg(any(target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] { + /// 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) -> u32 { + let inner_termios = termios.get_libc_termios(); + unsafe { libc::cfgetispeed(&*inner_termios) as u32 } + } -/// 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() + /// 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) -> u32 { + let inner_termios = termios.get_libc_termios(); + unsafe { libc::cfgetospeed(&*inner_termios) as u32 } + } + + /// 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: T) -> Result<()> { + let inner_termios = unsafe { termios.get_libc_termios_mut() }; + let res = unsafe { libc::cfsetispeed(inner_termios, baud.into() 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: T) -> Result<()> { + let inner_termios = unsafe { termios.get_libc_termios_mut() }; + let res = unsafe { libc::cfsetospeed(inner_termios, baud.into() 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: T) -> Result<()> { + let inner_termios = unsafe { termios.get_libc_termios_mut() }; + let res = unsafe { libc::cfsetspeed(inner_termios, baud.into() as libc::speed_t) }; + termios.update_wrapper(); + Errno::result(res).map(drop) + } + } else { + /// 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 { + 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() + } + + /// 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<()> { + 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<()> { + 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) + } + } } /// Configures the port to something like the "raw" mode of the old Version 7 terminal driver (see @@ -793,40 +1016,6 @@ pub fn cfmakeraw(termios: &mut 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<()> { - 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<()> { - 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)). /// diff --git a/test/sys/test_termios.rs b/test/sys/test_termios.rs index 903a53531b..831fc18b10 100644 --- a/test/sys/test_termios.rs +++ b/test/sys/test_termios.rs @@ -27,6 +27,7 @@ fn test_tcgetattr_pty() { close(pty.master).expect("closing the master failed"); close(pty.slave).expect("closing the slave failed"); } + // Test tcgetattr on something that isn't a terminal #[test] fn test_tcgetattr_enotty() {