diff options
author | Joel Galenson <jgalenson@google.com> | 2021-08-17 21:09:42 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2021-08-17 21:09:42 +0000 |
commit | 1dc99aca939fc87b42e711ead2185a70df0441a1 (patch) | |
tree | fc4e4c5fce5873e157076a945055babb79b88d61 | |
parent | fef73fda591b8826ae639760855b678f567774fe (diff) | |
parent | 981b40ad24a6d181cf843903a983f37305526484 (diff) | |
download | nix-1dc99aca939fc87b42e711ead2185a70df0441a1.tar.gz |
Upgrade rust/crates/nix to 0.22.0 am: 981b40ad24
Original change: https://android-review.googlesource.com/c/platform/external/rust/crates/nix/+/1790951
Change-Id: I0b831c18e290a0aadcc68341daa0cc5fa7216521
55 files changed, 1317 insertions, 314 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json index 4893485..3433190 100644 --- a/.cargo_vcs_info.json +++ b/.cargo_vcs_info.json @@ -1,5 +1,5 @@ { "git": { - "sha1": "db2af196c9c279f8bb4856c5eff1e2b658fcf2ff" + "sha1": "5dd14c39b88fb6eb25ad670435d1a79ba294b21e" } } @@ -44,5 +44,5 @@ rust_library { // autocfg-1.0.1 // bitflags-1.2.1 "default" // cfg-if-1.0.0 -// libc-0.2.97 "default,extra_traits,std" +// libc-0.2.98 "default,extra_traits,std" // memoffset-0.6.4 "default" diff --git a/CHANGELOG.md b/CHANGELOG.md index 91364a6..21cb4b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,34 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Fixed ### Removed +## [0.22.0] - 9 July 2021 +### Added +- Added `if_nameindex` (#[1445](https://github.com/nix-rust/nix/pull/1445)) +- Added `nmount` for FreeBSD. + (#[1453](https://github.com/nix-rust/nix/pull/1453)) +- Added `IpFreebind` socket option (sockopt) on Linux, Fuchsia and Android. + (#[1456](https://github.com/nix-rust/nix/pull/1456)) +- Added `TcpUserTimeout` socket option (sockopt) on Linux and Fuchsia. + (#[1457](https://github.com/nix-rust/nix/pull/1457)) +- Added `renameat2` for Linux + (#[1458](https://github.com/nix-rust/nix/pull/1458)) +- Added `RxqOvfl` support on Linux, Fuchsia and Android. + (#[1455](https://github.com/nix-rust/nix/pull/1455)) + +### Changed +- `ptsname_r` now returns a lossily-converted string in the event of bad UTF, + just like `ptsname`. + ([#1446](https://github.com/nix-rust/nix/pull/1446)) +- Nix's error type is now a simple wrapper around the platform's Errno. This + means it is now `Into<std::io::Error>`. It's also `Clone`, `Copy`, `Eq`, and + has a small fixed size. It also requires less typing. For example, the old + enum variant `nix::Error::Sys(nix::errno::Errno::EINVAL)` is now simply + `nix::Error::EINVAL`. + ([#1446](https://github.com/nix-rust/nix/pull/1446)) + +### Fixed +### Removed + ## [0.21.0] - 31 May 2021 ### Added - Added `getresuid` and `getresgid` @@ -38,12 +66,17 @@ This project adheres to [Semantic Versioning](https://semver.org/). (#[1440](https://github.com/nix-rust/nix/pull/1440)) - Minimum supported Rust version is now 1.41.0. ([#1440](https://github.com/nix-rust/nix/pull/1440)) +- Errno aliases are now associated consts on `Errno`, instead of consts in the + `errno` module. + (#[1452](https://github.com/nix-rust/nix/pull/1452)) ### Fixed - Allow `sockaddr_ll` size, as reported by the Linux kernel, to be smaller then it's definition (#[1395](https://github.com/nix-rust/nix/pull/1395)) - Fix spurious errors using `sendmmsg` with multiple cmsgs (#[1414](https://github.com/nix-rust/nix/pull/1414)) +- Added `Errno::EOPNOTSUPP` to FreeBSD, where it was missing. + (#[1452](https://github.com/nix-rust/nix/pull/1452)) ### Removed @@ -55,6 +88,8 @@ This project adheres to [Semantic Versioning](https://semver.org/). longer be needed now that async/await are available. `AioCb`s now work exclusively with borrowed buffers, not owned ones. (#[1440](https://github.com/nix-rust/nix/pull/1440)) +- Removed some Errno values from platforms where they aren't actually defined. + (#[1452](https://github.com/nix-rust/nix/pull/1452)) ## [0.20.0] - 20 February 2021 ### Added @@ -13,7 +13,7 @@ [package] edition = "2018" name = "nix" -version = "0.21.0" +version = "0.22.0" authors = ["The nix-rust Project Developers"] exclude = ["/.gitignore", "/.cirrus.yml", "/ci/*", "/Cross.toml", "/RELEASE_PROCEDURE.md", "/bors.toml"] description = "Rust friendly bindings to *nix APIs" @@ -54,7 +54,7 @@ version = "1.1" version = "1.0" [dependencies.libc] -version = "0.2.95" +version = "0.2.98" features = ["extra_traits"] [dev-dependencies.assert-impl] version = "0.1" diff --git a/Cargo.toml.orig b/Cargo.toml.orig index 843c5c1..c5cdd08 100644 --- a/Cargo.toml.orig +++ b/Cargo.toml.orig @@ -2,7 +2,7 @@ name = "nix" description = "Rust friendly bindings to *nix APIs" edition = "2018" -version = "0.21.0" +version = "0.22.0" authors = ["The nix-rust Project Developers"] repository = "https://github.com/nix-rust/nix" license = "MIT" @@ -32,7 +32,7 @@ targets = [ ] [dependencies] -libc = { version = "0.2.95", features = [ "extra_traits" ] } +libc = { version = "0.2.98", features = [ "extra_traits" ] } bitflags = "1.1" cfg-if = "1.0" @@ -7,13 +7,13 @@ third_party { } url { type: ARCHIVE - value: "https://static.crates.io/crates/nix/nix-0.21.0.crate" + value: "https://static.crates.io/crates/nix/nix-0.22.0.crate" } - version: "0.21.0" + version: "0.22.0" license_type: NOTICE last_upgrade_date { year: 2021 - month: 6 - day: 21 + month: 8 + day: 9 } } @@ -93,7 +93,7 @@ To use `nix`, add this to your `Cargo.toml`: ```toml [dependencies] -nix = "0.21.0" +nix = "0.22.0" ``` ## Contributing @@ -84,7 +84,7 @@ impl AsRawFd for Dir { impl Drop for Dir { fn drop(&mut self) { let e = Errno::result(unsafe { libc::closedir(self.0.as_ptr()) }); - if !std::thread::panicking() && e == Err(Error::Sys(Errno::EBADF)) { + if !std::thread::panicking() && e == Err(Error::from(Errno::EBADF)) { panic!("Closing an invalid file descriptor!"); }; } @@ -1,12 +1,24 @@ use cfg_if::cfg_if; -use crate::{Error, Result}; +use std::fmt; + +/// Indicates that [`clearenv`] failed for some unknown reason +#[derive(Clone, Copy, Debug)] +pub struct ClearEnvError; + +impl fmt::Display for ClearEnvError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "clearenv failed") + } +} + +impl std::error::Error for ClearEnvError {} /// Clear the environment of all name-value pairs. /// /// On platforms where libc provides `clearenv()`, it will be used. libc's /// `clearenv()` is documented to return an error code but not set errno; if the /// return value indicates a failure, this function will return -/// `Error::UnsupportedOperation`. +/// [`ClearEnvError`]. /// /// On platforms where libc does not provide `clearenv()`, a fallback /// implementation will be used that iterates over all environment variables and @@ -25,7 +37,7 @@ use crate::{Error, Result}; /// `environ` is currently held. The latter is not an issue if the only other /// environment access in the program is via `std::env`, but the requirement on /// thread safety must still be upheld. -pub unsafe fn clearenv() -> Result<()> { +pub unsafe fn clearenv() -> std::result::Result<(), ClearEnvError> { let ret; cfg_if! { if #[cfg(any(target_os = "fuchsia", @@ -48,6 +60,6 @@ pub unsafe fn clearenv() -> Result<()> { if ret == 0 { Ok(()) } else { - Err(Error::UnsupportedOperation) + Err(ClearEnvError) } } diff --git a/src/errno.rs b/src/errno.rs index 9275feb..00e2014 100644 --- a/src/errno.rs +++ b/src/errno.rs @@ -1,5 +1,6 @@ use cfg_if::cfg_if; use libc::{c_int, c_void}; +use std::convert::TryFrom; use std::{fmt, io, error}; use crate::{Error, Result}; @@ -48,6 +49,42 @@ pub fn errno() -> i32 { } impl Errno { + /// Convert this `Error` to an [`Errno`](enum.Errno.html). + /// + /// # Example + /// + /// ``` + /// # use nix::Error; + /// # use nix::errno::Errno; + /// let e = Error::from(Errno::EPERM); + /// assert_eq!(Some(Errno::EPERM), e.as_errno()); + /// ``` + #[deprecated( + since = "0.22.0", + note = "It's a no-op now; just delete it." + )] + pub fn as_errno(self) -> Option<Self> { + Some(self) + } + + /// Create a nix Error from a given errno + #[deprecated( + since = "0.22.0", + note = "It's a no-op now; just delete it." + )] + pub fn from_errno(errno: Errno) -> Error { + Error::from(errno) + } + + /// Create a new invalid argument error (`EINVAL`) + #[deprecated( + since = "0.22.0", + note = "Use Errno::EINVAL instead" + )] + pub fn invalid_argument() -> Error { + Errno::EINVAL + } + pub fn last() -> Self { last() } @@ -68,11 +105,26 @@ impl Errno { /// should not be used when `-1` is not the errno sentinel value. pub fn result<S: ErrnoSentinel + PartialEq<S>>(value: S) -> Result<S> { if value == S::sentinel() { - Err(Error::Sys(Self::last())) + Err(Self::last()) } else { Ok(value) } } + + /// Backwards compatibility hack for Nix <= 0.21.0 users + /// + /// In older versions of Nix, `Error::Sys` was an enum variant. Now it's a + /// function, which is compatible with most of the former use cases of the + /// enum variant. But you should use `Error(Errno::...)` instead. + #[deprecated( + since = "0.22.0", + note = "Use Errno::... instead" + )] + #[allow(non_snake_case)] + #[inline] + pub fn Sys(errno: Errno) -> Error { + errno + } } /// The sentinel value indicates that a function failed and more detailed @@ -115,6 +167,16 @@ impl From<Errno> for io::Error { } } +impl TryFrom<io::Error> for Errno { + type Error = io::Error; + + fn try_from(ioerror: io::Error) -> std::result::Result<Self, io::Error> { + ioerror.raw_os_error() + .map(Errno::from_i32) + .ok_or(ioerror) + } +} + fn last() -> Errno { Errno::from_i32(errno()) } @@ -843,9 +905,11 @@ mod consts { EHWPOISON = libc::EHWPOISON, } - pub const EWOULDBLOCK: Errno = Errno::EAGAIN; - pub const EDEADLOCK: Errno = Errno::EDEADLK; - pub const ENOTSUP: Errno = Errno::EOPNOTSUPP; + impl Errno { + pub const EWOULDBLOCK: Errno = Errno::EAGAIN; + pub const EDEADLOCK: Errno = Errno::EDEADLK; + pub const ENOTSUP: Errno = Errno::EOPNOTSUPP; + } pub fn from_i32(e: i32) -> Errno { use self::Errno::*; @@ -1103,11 +1167,11 @@ mod consts { EQFULL = libc::EQFULL, } - pub const ELAST: Errno = Errno::EQFULL; - pub const EWOULDBLOCK: Errno = Errno::EAGAIN; - pub const EDEADLOCK: Errno = Errno::EDEADLK; - - pub const EL2NSYNC: Errno = Errno::UnknownErrno; + impl Errno { + pub const ELAST: Errno = Errno::EQFULL; + pub const EWOULDBLOCK: Errno = Errno::EAGAIN; + pub const EDEADLOCK: Errno = Errno::EDEADLK; + } pub fn from_i32(e: i32) -> Errno { use self::Errno::*; @@ -1328,11 +1392,12 @@ mod consts { EOWNERDEAD = libc::EOWNERDEAD, } - pub const ELAST: Errno = Errno::EOWNERDEAD; - pub const EWOULDBLOCK: Errno = Errno::EAGAIN; - pub const EDEADLOCK: Errno = Errno::EDEADLK; - - pub const EL2NSYNC: Errno = Errno::UnknownErrno; + impl Errno { + pub const ELAST: Errno = Errno::EOWNERDEAD; + pub const EWOULDBLOCK: Errno = Errno::EAGAIN; + pub const EDEADLOCK: Errno = Errno::EDEADLK; + pub const EOPNOTSUPP: Errno = Errno::ENOTSUP; + } pub fn from_i32(e: i32) -> Errno { use self::Errno::*; @@ -1542,12 +1607,12 @@ mod consts { EASYNC = libc::EASYNC, } - pub const ELAST: Errno = Errno::EASYNC; - pub const EWOULDBLOCK: Errno = Errno::EAGAIN; - pub const EDEADLOCK: Errno = Errno::EDEADLK; - pub const EOPNOTSUPP: Errno = Errno::ENOTSUP; - - pub const EL2NSYNC: Errno = Errno::UnknownErrno; + impl Errno { + pub const ELAST: Errno = Errno::EASYNC; + pub const EWOULDBLOCK: Errno = Errno::EAGAIN; + pub const EDEADLOCK: Errno = Errno::EDEADLK; + pub const EOPNOTSUPP: Errno = Errno::ENOTSUP; + } pub fn from_i32(e: i32) -> Errno { use self::Errno::*; @@ -1756,10 +1821,10 @@ mod consts { EPROTO = libc::EPROTO, } - pub const ELAST: Errno = Errno::ENOTSUP; - pub const EWOULDBLOCK: Errno = Errno::EAGAIN; - - pub const EL2NSYNC: Errno = Errno::UnknownErrno; + impl Errno { + pub const ELAST: Errno = Errno::ENOTSUP; + pub const EWOULDBLOCK: Errno = Errno::EAGAIN; + } pub fn from_i32(e: i32) -> Errno { use self::Errno::*; @@ -1969,10 +2034,10 @@ mod consts { EPROTO = libc::EPROTO, } - pub const ELAST: Errno = Errno::ENOTSUP; - pub const EWOULDBLOCK: Errno = Errno::EAGAIN; - - pub const EL2NSYNC: Errno = Errno::UnknownErrno; + impl Errno { + pub const ELAST: Errno = Errno::ENOTSUP; + pub const EWOULDBLOCK: Errno = Errno::EAGAIN; + } pub fn from_i32(e: i32) -> Errno { use self::Errno::*; @@ -2172,10 +2237,9 @@ mod consts { EPROTO = libc::EPROTO, } - pub const ELAST: Errno = Errno::UnknownErrno; - pub const EWOULDBLOCK: Errno = Errno::EAGAIN; - - pub const EL2NSYNC: Errno = Errno::UnknownErrno; + impl Errno { + pub const EWOULDBLOCK: Errno = Errno::EAGAIN; + } pub fn from_i32(e: i32) -> Errno { use self::Errno::*; @@ -2400,8 +2464,10 @@ mod consts { ESTALE = libc::ESTALE, } - pub const ELAST: Errno = Errno::ESTALE; - pub const EWOULDBLOCK: Errno = Errno::EAGAIN; + impl Errno { + pub const ELAST: Errno = Errno::ESTALE; + pub const EWOULDBLOCK: Errno = Errno::EAGAIN; + } pub fn from_i32(e: i32) -> Errno { use self::Errno::*; diff --git a/src/fcntl.rs b/src/fcntl.rs index ce30b7d..f8f1372 100644 --- a/src/fcntl.rs +++ b/src/fcntl.rs @@ -210,6 +210,43 @@ pub fn renameat<P1: ?Sized + NixPath, P2: ?Sized + NixPath>( Errno::result(res).map(drop) } +#[cfg(all( + target_os = "linux", + target_env = "gnu", +))] +libc_bitflags! { + pub struct RenameFlags: u32 { + RENAME_EXCHANGE; + RENAME_NOREPLACE; + RENAME_WHITEOUT; + } +} + +#[cfg(all( + target_os = "linux", + target_env = "gnu", +))] +pub fn renameat2<P1: ?Sized + NixPath, P2: ?Sized + NixPath>( + old_dirfd: Option<RawFd>, + old_path: &P1, + new_dirfd: Option<RawFd>, + new_path: &P2, + flags: RenameFlags, +) -> Result<()> { + let res = old_path.with_nix_path(|old_cstr| { + new_path.with_nix_path(|new_cstr| unsafe { + libc::renameat2( + at_rawfd(old_dirfd), + old_cstr.as_ptr(), + at_rawfd(new_dirfd), + new_cstr.as_ptr(), + flags.bits(), + ) + }) + })??; + Errno::result(res).map(drop) +} + fn wrap_readlink_result(mut v: Vec<u8>, len: ssize_t) -> Result<OsString> { unsafe { v.set_len(len as usize) } v.shrink_to_fit(); @@ -288,7 +325,7 @@ fn inner_readlink<P: ?Sized + NixPath>(dirfd: Option<RawFd>, path: &P) -> Result Some(next_size) => try_size = next_size, // It's absurd that this would happen, but handle it sanely // anyway. - None => break Err(super::Error::Sys(Errno::ENAMETOOLONG)), + None => break Err(super::Error::from(Errno::ENAMETOOLONG)), } } } @@ -646,6 +683,6 @@ pub fn posix_fallocate(fd: RawFd, offset: libc::off_t, len: libc::off_t) -> Resu match Errno::result(res) { Err(err) => Err(err), Ok(0) => Ok(()), - Ok(errno) => Err(crate::Error::Sys(Errno::from_i32(errno))), + Ok(errno) => Err(crate::Error::from(Errno::from_i32(errno))), } } @@ -44,6 +44,7 @@ pub mod ifaddrs; target_os = "linux"))] pub mod kmod; #[cfg(any(target_os = "android", + target_os = "freebsd", target_os = "linux"))] pub mod mount; #[cfg(any(target_os = "dragonfly", @@ -78,7 +79,7 @@ pub mod unistd; use libc::{c_char, PATH_MAX}; -use std::{error, fmt, ptr, result}; +use std::{ptr, result}; use std::ffi::{CStr, OsStr}; use std::os::unix::ffi::OsStrExt; use std::path::{Path, PathBuf}; @@ -86,83 +87,19 @@ use std::path::{Path, PathBuf}; use errno::Errno; /// Nix Result Type -pub type Result<T> = result::Result<T, Error>; +pub type Result<T> = result::Result<T, Errno>; -/// Nix Error Type +/// Nix's main error type. /// -/// The nix error type provides a common way of dealing with -/// various system system/libc calls that might fail. Each -/// error has a corresponding errno (usually the one from the -/// underlying OS) to which it can be mapped in addition to -/// implementing other common traits. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum Error { - Sys(Errno), - InvalidPath, - /// The operation involved a conversion to Rust's native String type, which failed because the - /// string did not contain all valid UTF-8. - InvalidUtf8, - /// The operation is not supported by Nix, in this instance either use the libc bindings or - /// consult the module documentation to see if there is a more appropriate interface available. - UnsupportedOperation, -} - -impl Error { - /// Convert this `Error` to an [`Errno`](enum.Errno.html). - /// - /// # Example - /// - /// ``` - /// # use nix::Error; - /// # use nix::errno::Errno; - /// let e = Error::from(Errno::EPERM); - /// assert_eq!(Some(Errno::EPERM), e.as_errno()); - /// ``` - pub fn as_errno(self) -> Option<Errno> { - if let Error::Sys(e) = self { - Some(e) - } else { - None - } - } - - /// Create a nix Error from a given errno - pub fn from_errno(errno: Errno) -> Error { - Error::Sys(errno) - } - - /// Get the current errno and convert it to a nix Error - pub fn last() -> Error { - Error::Sys(Errno::last()) - } - - /// Create a new invalid argument error (`EINVAL`) - pub fn invalid_argument() -> Error { - Error::Sys(Errno::EINVAL) - } - -} - -impl From<Errno> for Error { - fn from(errno: Errno) -> Error { Error::from_errno(errno) } -} - -impl From<std::string::FromUtf8Error> for Error { - fn from(_: std::string::FromUtf8Error) -> Error { Error::InvalidUtf8 } -} - -impl error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Error::InvalidPath => write!(f, "Invalid path"), - Error::InvalidUtf8 => write!(f, "Invalid UTF-8 string"), - Error::UnsupportedOperation => write!(f, "Unsupported Operation"), - Error::Sys(errno) => write!(f, "{:?}: {}", errno, errno.desc()), - } - } -} +/// It's a wrapper around Errno. As such, it's very interoperable with +/// [`std::io::Error`], but it has the advantages of: +/// * `Clone` +/// * `Copy` +/// * `Eq` +/// * Small size +/// * Represents all of the system's errnos, instead of just the most common +/// ones. +pub type Error = Errno; pub trait NixPath { fn is_empty(&self) -> bool; @@ -216,7 +153,7 @@ impl NixPath for CStr { where F: FnOnce(&CStr) -> T { // Equivalence with the [u8] impl. if self.len() >= PATH_MAX as usize { - return Err(Error::InvalidPath); + return Err(Error::from(Errno::ENAMETOOLONG)) } Ok(f(self)) @@ -237,11 +174,11 @@ impl NixPath for [u8] { let mut buf = [0u8; PATH_MAX as usize]; if self.len() >= PATH_MAX as usize { - return Err(Error::InvalidPath); + return Err(Error::from(Errno::ENAMETOOLONG)) } match self.iter().position(|b| *b == 0) { - Some(_) => Err(Error::InvalidPath), + Some(_) => Err(Error::from(Errno::EINVAL)), None => { unsafe { // TODO: Replace with bytes::copy_memory. rust-lang/rust#24028 diff --git a/src/mount/bsd.rs b/src/mount/bsd.rs new file mode 100644 index 0000000..0144908 --- /dev/null +++ b/src/mount/bsd.rs @@ -0,0 +1,425 @@ +use crate::{ + Error, + Errno, + NixPath, + Result, + sys::uio::IoVec +}; +use libc::{c_char, c_int, c_uint, c_void}; +use std::{ + borrow::Cow, + ffi::{CString, CStr}, + fmt, + io, + ptr +}; + + +libc_bitflags!( + /// Used with [`Nmount::nmount`]. + pub struct MntFlags: c_int { + /// ACL support enabled. + #[cfg(any(target_os = "netbsd", target_os = "freebsd"))] + MNT_ACLS; + /// All I/O to the file system should be done asynchronously. + MNT_ASYNC; + /// dir should instead be a file system ID encoded as “FSID:val0:val1”. + #[cfg(target_os = "freebsd")] + MNT_BYFSID; + /// Force a read-write mount even if the file system appears to be + /// unclean. + MNT_FORCE; + /// GEOM journal support enabled. + #[cfg(target_os = "freebsd")] + MNT_GJOURNAL; + /// MAC support for objects. + #[cfg(any(target_os = "macos", target_os = "freebsd"))] + MNT_MULTILABEL; + /// Disable read clustering. + #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] + MNT_NOCLUSTERR; + /// Disable write clustering. + #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] + MNT_NOCLUSTERW; + /// Enable NFS version 4 ACLs. + #[cfg(target_os = "freebsd")] + MNT_NFS4ACLS; + /// Do not update access times. + MNT_NOATIME; + /// Disallow program execution. + MNT_NOEXEC; + /// Do not honor setuid or setgid bits on files when executing them. + MNT_NOSUID; + /// Do not follow symlinks. + #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] + MNT_NOSYMFOLLOW; + /// Mount read-only. + MNT_RDONLY; + /// Causes the vfs subsystem to update its data structures pertaining to + /// the specified already mounted file system. + MNT_RELOAD; + /// Create a snapshot of the file system. + /// + /// See [mksnap_ffs(8)](https://www.freebsd.org/cgi/man.cgi?query=mksnap_ffs) + #[cfg(any(target_os = "macos", target_os = "freebsd"))] + MNT_SNAPSHOT; + /// Using soft updates. + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + MNT_SOFTDEP; + /// Directories with the SUID bit set chown new files to their own + /// owner. + #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] + MNT_SUIDDIR; + /// All I/O to the file system should be done synchronously. + MNT_SYNCHRONOUS; + /// Union with underlying fs. + #[cfg(any( + target_os = "macos", + target_os = "freebsd", + target_os = "netbsd" + ))] + MNT_UNION; + /// Indicates that the mount command is being applied to an already + /// mounted file system. + MNT_UPDATE; + /// Check vnode use counts. + #[cfg(target_os = "freebsd")] + MNT_NONBUSY; + } +); + + +/// The Error type of [`Nmount::nmount`]. +/// +/// It wraps an [`Errno`], but also may contain an additional message returned +/// by `nmount(2)`. +#[derive(Debug)] +pub struct NmountError { + errno: Error, + errmsg: Option<String> +} + +impl NmountError { + /// Returns the additional error string sometimes generated by `nmount(2)`. + pub fn errmsg(&self) -> Option<&str> { + self.errmsg.as_deref() + } + + /// Returns the inner [`Error`] + pub fn error(&self) -> Error { + self.errno + } + + fn new(error: Error, errmsg: Option<&CStr>) -> Self { + Self { + errno: error, + errmsg: errmsg.map(CStr::to_string_lossy).map(Cow::into_owned) + } + } +} + +impl std::error::Error for NmountError {} + +impl fmt::Display for NmountError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if let Some(errmsg) = &self.errmsg { + write!(f, "{:?}: {}: {}", self.errno, errmsg, self.errno.desc()) + } else { + write!(f, "{:?}: {}", self.errno, self.errno.desc()) + } + } +} + +impl From<NmountError> for io::Error { + fn from(err: NmountError) -> Self { + err.errno.into() + } +} + +/// Result type of [`Nmount::nmount`]. +pub type NmountResult = std::result::Result<(), NmountError>; + +/// Mount a FreeBSD file system. +/// +/// The `nmount(2)` system call works similarly to the `mount(8)` program; it +/// takes its options as a series of name-value pairs. Most of the values are +/// strings, as are all of the names. The `Nmount` structure builds up an +/// argument list and then executes the syscall. +/// +/// # Examples +/// +/// To mount `target` onto `mountpoint` with `nullfs`: +/// ``` +/// # use nix::unistd::Uid; +/// # use ::sysctl::CtlValue; +/// # if !Uid::current().is_root() && CtlValue::Int(0) == ::sysctl::value("vfs.usermount").unwrap() { +/// # return; +/// # }; +/// use nix::mount::{MntFlags, Nmount, unmount}; +/// use std::ffi::CString; +/// use tempfile::tempdir; +/// +/// let mountpoint = tempdir().unwrap(); +/// let target = tempdir().unwrap(); +/// +/// let fstype = CString::new("fstype").unwrap(); +/// let nullfs = CString::new("nullfs").unwrap(); +/// Nmount::new() +/// .str_opt(&fstype, &nullfs) +/// .str_opt_owned("fspath", mountpoint.path().to_str().unwrap()) +/// .str_opt_owned("target", target.path().to_str().unwrap()) +/// .nmount(MntFlags::empty()).unwrap(); +/// +/// unmount(mountpoint.path(), MntFlags::empty()).unwrap(); +/// ``` +/// +/// # See Also +/// * [`nmount(2)`](https://www.freebsd.org/cgi/man.cgi?query=nmount) +/// * [`nullfs(5)`](https://www.freebsd.org/cgi/man.cgi?query=nullfs) +#[cfg(target_os = "freebsd")] +#[derive(Debug, Default)] +pub struct Nmount<'a>{ + iov: Vec<IoVec<&'a [u8]>>, + is_owned: Vec<bool>, +} + +#[cfg(target_os = "freebsd")] +impl<'a> Nmount<'a> { + /// Add an opaque mount option. + /// + /// Some file systems take binary-valued mount options. They can be set + /// with this method. + /// + /// # Safety + /// + /// Unsafe because it will cause `Nmount::nmount` to dereference a raw + /// pointer. The user is responsible for ensuring that `val` is valid and + /// its lifetime outlives `self`! An easy way to do that is to give the + /// value a larger scope than `name` + /// + /// # Examples + /// ``` + /// use libc::c_void; + /// use nix::mount::Nmount; + /// use std::ffi::CString; + /// use std::mem; + /// + /// // Note that flags outlives name + /// let mut flags: u32 = 0xdeadbeef; + /// let name = CString::new("flags").unwrap(); + /// let p = &mut flags as *mut u32 as *mut c_void; + /// let len = mem::size_of_val(&flags); + /// let mut nmount = Nmount::new(); + /// unsafe { nmount.mut_ptr_opt(&name, p, len) }; + /// ``` + pub unsafe fn mut_ptr_opt( + &mut self, + name: &'a CStr, + val: *mut c_void, + len: usize + ) -> &mut Self + { + self.iov.push(IoVec::from_slice(name.to_bytes_with_nul())); + self.is_owned.push(false); + self.iov.push(IoVec::from_raw_parts(val, len)); + self.is_owned.push(false); + self + } + + /// Add a mount option that does not take a value. + /// + /// # Examples + /// ``` + /// use nix::mount::Nmount; + /// use std::ffi::CString; + /// + /// let read_only = CString::new("ro").unwrap(); + /// Nmount::new() + /// .null_opt(&read_only); + /// ``` + pub fn null_opt(&mut self, name: &'a CStr) -> &mut Self { + self.iov.push(IoVec::from_slice(name.to_bytes_with_nul())); + self.is_owned.push(false); + self.iov.push(IoVec::from_raw_parts(ptr::null_mut(), 0)); + self.is_owned.push(false); + self + } + + /// Add a mount option that does not take a value, but whose name must be + /// owned. + /// + /// + /// This has higher runtime cost than [`Nmount::null_opt`], but is useful + /// when the name's lifetime doesn't outlive the `Nmount`, or it's a + /// different string type than `CStr`. + /// + /// # Examples + /// ``` + /// use nix::mount::Nmount; + /// + /// let read_only = "ro"; + /// let mut nmount: Nmount<'static> = Nmount::new(); + /// nmount.null_opt_owned(read_only); + /// ``` + pub fn null_opt_owned<P: ?Sized + NixPath>(&mut self, name: &P) -> &mut Self + { + name.with_nix_path(|s| { + let len = s.to_bytes_with_nul().len(); + self.iov.push(IoVec::from_raw_parts( + // Must free it later + s.to_owned().into_raw() as *mut c_void, + len + )); + self.is_owned.push(true); + }).unwrap(); + self.iov.push(IoVec::from_raw_parts(ptr::null_mut(), 0)); + self.is_owned.push(false); + self + } + + /// Add a mount option as a [`CStr`]. + /// + /// # Examples + /// ``` + /// use nix::mount::Nmount; + /// use std::ffi::CString; + /// + /// let fstype = CString::new("fstype").unwrap(); + /// let nullfs = CString::new("nullfs").unwrap(); + /// Nmount::new() + /// .str_opt(&fstype, &nullfs); + /// ``` + pub fn str_opt( + &mut self, + name: &'a CStr, + val: &'a CStr + ) -> &mut Self + { + self.iov.push(IoVec::from_slice(name.to_bytes_with_nul())); + self.is_owned.push(false); + self.iov.push(IoVec::from_slice(val.to_bytes_with_nul())); + self.is_owned.push(false); + self + } + + /// Add a mount option as an owned string. + /// + /// This has higher runtime cost than [`Nmount::str_opt`], but is useful + /// when the value's lifetime doesn't outlive the `Nmount`, or it's a + /// different string type than `CStr`. + /// + /// # Examples + /// ``` + /// use nix::mount::Nmount; + /// use std::path::Path; + /// + /// let mountpoint = Path::new("/mnt"); + /// Nmount::new() + /// .str_opt_owned("fspath", mountpoint.to_str().unwrap()); + /// ``` + pub fn str_opt_owned<P1, P2>(&mut self, name: &P1, val: &P2) -> &mut Self + where P1: ?Sized + NixPath, + P2: ?Sized + NixPath + { + name.with_nix_path(|s| { + let len = s.to_bytes_with_nul().len(); + self.iov.push(IoVec::from_raw_parts( + // Must free it later + s.to_owned().into_raw() as *mut c_void, + len + )); + self.is_owned.push(true); + }).unwrap(); + val.with_nix_path(|s| { + let len = s.to_bytes_with_nul().len(); + self.iov.push(IoVec::from_raw_parts( + // Must free it later + s.to_owned().into_raw() as *mut c_void, + len + )); + self.is_owned.push(true); + }).unwrap(); + self + } + + pub fn new() -> Self { + Self::default() + } + + /// Actually mount the file system. + pub fn nmount(&mut self, flags: MntFlags) -> NmountResult { + // nmount can return extra error information via a "errmsg" return + // argument. + const ERRMSG_NAME: &[u8] = b"errmsg\0"; + let mut errmsg = vec![0u8; 255]; + self.iov.push(IoVec::from_raw_parts( + ERRMSG_NAME.as_ptr() as *mut c_void, + ERRMSG_NAME.len() + )); + self.iov.push(IoVec::from_raw_parts( + errmsg.as_mut_ptr() as *mut c_void, + errmsg.len() + )); + + let niov = self.iov.len() as c_uint; + let iovp = self.iov.as_mut_ptr() as *mut libc::iovec; + let res = unsafe { + libc::nmount(iovp, niov, flags.bits) + }; + match Errno::result(res) { + Ok(_) => Ok(()), + Err(error) => { + let errmsg = match errmsg.iter().position(|&x| x == 0) { + None => None, + Some(0) => None, + Some(n) => { + let sl = &errmsg[0..n + 1]; + Some(CStr::from_bytes_with_nul(sl).unwrap()) + } + }; + Err(NmountError::new(error.into(), errmsg)) + } + } + } +} + +#[cfg(target_os = "freebsd")] +impl<'a> Drop for Nmount<'a> { + fn drop(&mut self) { + for (iov, is_owned) in self.iov.iter().zip(self.is_owned.iter()) { + if *is_owned { + // Free the owned string. Safe because we recorded ownership, + // and Nmount does not implement Clone. + unsafe { + CString::from_raw(iov.0.iov_base as *mut c_char); + } + } + } + } +} + +/// Unmount the file system mounted at `mountpoint`. +/// +/// Useful flags include +/// * `MNT_FORCE` - Unmount even if still in use. +/// * `MNT_BYFSID` - `mountpoint` is not a path, but a file system ID +/// encoded as `FSID:val0:val1`, where `val0` and `val1` +/// are the contents of the `fsid_t val[]` array in decimal. +/// The file system that has the specified file system ID +/// will be unmounted. See +/// [`statfs`](crate::sys::statfs::statfs) to determine the +/// `fsid`. +pub fn unmount<P>(mountpoint: &P, flags: MntFlags) -> Result<()> + where P: ?Sized + NixPath +{ + let res = mountpoint.with_nix_path(|cstr| { + unsafe { libc::unmount(cstr.as_ptr(), flags.bits) } + })?; + + Errno::result(res).map(drop) +} diff --git a/src/mount.rs b/src/mount/linux.rs index edb8afb..edb8afb 100644 --- a/src/mount.rs +++ b/src/mount/linux.rs diff --git a/src/mount/mod.rs b/src/mount/mod.rs new file mode 100644 index 0000000..8538bf3 --- /dev/null +++ b/src/mount/mod.rs @@ -0,0 +1,20 @@ +#[cfg(any(target_os = "android", target_os = "linux"))] +mod linux; + +#[cfg(any(target_os = "android", target_os = "linux"))] +pub use self::linux::*; + +#[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] +mod bsd; + +#[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] +pub use self::bsd::*; diff --git a/src/net/if_.rs b/src/net/if_.rs index 70349e5..bc00a43 100644 --- a/src/net/if_.rs +++ b/src/net/if_.rs @@ -3,8 +3,8 @@ //! Uses Linux and/or POSIX functions to resolve interface names like "eth0" //! or "socan1" into device numbers. +use crate::{Error, NixPath, Result}; use libc::c_uint; -use crate::{Result, Error, NixPath}; /// Resolve an interface into a interface number. pub fn if_nametoindex<P: ?Sized + NixPath>(name: &P) -> Result<c_uint> { @@ -267,3 +267,145 @@ libc_bitflags!( IFF_IPMP; } ); + +#[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "fuchsia", + target_os = "ios", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", +))] +mod if_nameindex { + use super::*; + + use std::ffi::CStr; + use std::fmt; + use std::marker::PhantomData; + use std::ptr::NonNull; + + /// A network interface. Has a name like "eth0" or "wlp4s0" or "wlan0", as well as an index + /// (1, 2, 3, etc) that identifies it in the OS's networking stack. + #[allow(missing_copy_implementations)] + #[repr(transparent)] + pub struct Interface(libc::if_nameindex); + + impl Interface { + /// Obtain the index of this interface. + pub fn index(&self) -> c_uint { + self.0.if_index + } + + /// Obtain the name of this interface. + pub fn name(&self) -> &CStr { + unsafe { CStr::from_ptr(self.0.if_name) } + } + } + + impl fmt::Debug for Interface { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Interface") + .field("index", &self.index()) + .field("name", &self.name()) + .finish() + } + } + + /// A list of the network interfaces available on this system. Obtained from [`if_nameindex()`]. + pub struct Interfaces { + ptr: NonNull<libc::if_nameindex>, + } + + impl Interfaces { + /// Iterate over the interfaces in this list. + #[inline] + pub fn iter(&self) -> InterfacesIter<'_> { + self.into_iter() + } + + /// Convert this to a slice of interfaces. Note that the underlying interfaces list is + /// null-terminated, so calling this calculates the length. If random access isn't needed, + /// [`Interfaces::iter()`] should be used instead. + pub fn to_slice(&self) -> &[Interface] { + let ifs = self.ptr.as_ptr() as *const Interface; + let len = self.iter().count(); + unsafe { std::slice::from_raw_parts(ifs, len) } + } + } + + impl Drop for Interfaces { + fn drop(&mut self) { + unsafe { libc::if_freenameindex(self.ptr.as_ptr()) }; + } + } + + impl fmt::Debug for Interfaces { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.to_slice().fmt(f) + } + } + + impl<'a> IntoIterator for &'a Interfaces { + type IntoIter = InterfacesIter<'a>; + type Item = &'a Interface; + #[inline] + fn into_iter(self) -> Self::IntoIter { + InterfacesIter { + ptr: self.ptr.as_ptr(), + _marker: PhantomData, + } + } + } + + /// An iterator over the interfaces in an [`Interfaces`]. + #[derive(Debug)] + pub struct InterfacesIter<'a> { + ptr: *const libc::if_nameindex, + _marker: PhantomData<&'a Interfaces>, + } + + impl<'a> Iterator for InterfacesIter<'a> { + type Item = &'a Interface; + #[inline] + fn next(&mut self) -> Option<Self::Item> { + unsafe { + if (*self.ptr).if_index == 0 { + None + } else { + let ret = &*(self.ptr as *const Interface); + self.ptr = self.ptr.add(1); + Some(ret) + } + } + } + } + + /// Retrieve a list of the network interfaces available on the local system. + /// + /// ``` + /// let interfaces = nix::net::if_::if_nameindex().unwrap(); + /// for iface in &interfaces { + /// println!("Interface #{} is called {}", iface.index(), iface.name().to_string_lossy()); + /// } + /// ``` + pub fn if_nameindex() -> Result<Interfaces> { + unsafe { + let ifs = libc::if_nameindex(); + let ptr = NonNull::new(ifs).ok_or_else(Error::last)?; + Ok(Interfaces { ptr }) + } + } +} +#[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "fuchsia", + target_os = "ios", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", +))] +pub use if_nameindex::*; @@ -70,7 +70,7 @@ impl Drop for PtyMaster { // condition, which can cause confusing errors for future I/O // operations. let e = unistd::close(self.0); - if e == Err(Error::Sys(Errno::EBADF)) { + if e == Err(Errno::EBADF) { panic!("Closing an invalid file descriptor!"); }; } @@ -78,13 +78,13 @@ impl Drop for PtyMaster { impl io::Read for PtyMaster { fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { - unistd::read(self.0, buf).map_err(|e| e.as_errno().unwrap().into()) + unistd::read(self.0, buf).map_err(io::Error::from) } } impl io::Write for PtyMaster { fn write(&mut self, buf: &[u8]) -> io::Result<usize> { - unistd::write(self.0, buf).map_err(|e| e.as_errno().unwrap().into()) + unistd::write(self.0, buf).map_err(io::Error::from) } fn flush(&mut self) -> io::Result<()> { Ok(()) @@ -99,7 +99,7 @@ impl io::Write for PtyMaster { #[inline] pub fn grantpt(fd: &PtyMaster) -> Result<()> { if unsafe { libc::grantpt(fd.as_raw_fd()) } < 0 { - return Err(Error::last()); + return Err(Error::from(Errno::last())); } Ok(()) @@ -145,7 +145,7 @@ pub fn posix_openpt(flags: fcntl::OFlag) -> Result<PtyMaster> { }; if fd < 0 { - return Err(Error::last()); + return Err(Error::from(Errno::last())); } Ok(PtyMaster(fd)) @@ -171,7 +171,7 @@ pub fn posix_openpt(flags: fcntl::OFlag) -> Result<PtyMaster> { pub unsafe fn ptsname(fd: &PtyMaster) -> Result<String> { let name_ptr = libc::ptsname(fd.as_raw_fd()); if name_ptr.is_null() { - return Err(Error::last()); + return Err(Error::from(Errno::last())); } let name = CStr::from_ptr(name_ptr); @@ -190,18 +190,17 @@ pub unsafe fn ptsname(fd: &PtyMaster) -> Result<String> { #[cfg(any(target_os = "android", target_os = "linux"))] #[inline] pub fn ptsname_r(fd: &PtyMaster) -> Result<String> { - let mut name_buf = vec![0u8; 64]; - let name_buf_ptr = name_buf.as_mut_ptr() as *mut libc::c_char; - if unsafe { libc::ptsname_r(fd.as_raw_fd(), name_buf_ptr, name_buf.capacity()) } != 0 { - return Err(Error::last()); - } - - // Find the first null-character terminating this string. This is guaranteed to succeed if the - // return value of `libc::ptsname_r` is 0. - let null_index = name_buf.iter().position(|c| *c == b'\0').unwrap(); - name_buf.truncate(null_index); + let mut name_buf = Vec::<libc::c_char>::with_capacity(64); + let name_buf_ptr = name_buf.as_mut_ptr(); + let cname = unsafe { + let cap = name_buf.capacity(); + if libc::ptsname_r(fd.as_raw_fd(), name_buf_ptr, cap) != 0 { + return Err(Error::last()); + } + CStr::from_ptr(name_buf.as_ptr()) + }; - let name = String::from_utf8(name_buf)?; + let name = cname.to_string_lossy().into_owned(); Ok(name) } @@ -214,7 +213,7 @@ pub fn ptsname_r(fd: &PtyMaster) -> Result<String> { #[inline] pub fn unlockpt(fd: &PtyMaster) -> Result<()> { if unsafe { libc::unlockpt(fd.as_raw_fd()) } < 0 { - return Err(Error::last()); + return Err(Error::from(Errno::last())); } Ok(()) diff --git a/src/sched.rs b/src/sched.rs index 576eb4a..bf51bc1 100644 --- a/src/sched.rs +++ b/src/sched.rs @@ -68,7 +68,7 @@ mod sched_linux_like { /// `field` is the CPU id to test pub fn is_set(&self, field: usize) -> Result<bool> { if field >= CpuSet::count() { - Err(Error::Sys(Errno::EINVAL)) + Err(Error::from(Errno::EINVAL)) } else { Ok(unsafe { libc::CPU_ISSET(field, &self.cpu_set) }) } @@ -78,7 +78,7 @@ mod sched_linux_like { /// `field` is the CPU id to add pub fn set(&mut self, field: usize) -> Result<()> { if field >= CpuSet::count() { - Err(Error::Sys(Errno::EINVAL)) + Err(Error::from(Errno::EINVAL)) } else { unsafe { libc::CPU_SET(field, &mut self.cpu_set); } Ok(()) @@ -89,7 +89,7 @@ mod sched_linux_like { /// `field` is the CPU id to remove pub fn unset(&mut self, field: usize) -> Result<()> { if field >= CpuSet::count() { - Err(Error::Sys(Errno::EINVAL)) + Err(Error::from(Errno::EINVAL)) } else { unsafe { libc::CPU_CLR(field, &mut self.cpu_set);} Ok(()) @@ -176,6 +176,14 @@ mod sched_linux_like { Errno::result(res).and(Ok(cpuset)) } + /// `clone` create a child process + /// ([`clone(2)`](https://man7.org/linux/man-pages/man2/clone.2.html)) + /// + /// `stack` is a reference to an array which will hold the stack of the new + /// process. Unlike when calling `clone(2)` from C, the provided stack + /// address need not be the highest address of the region. Nix will take + /// care of that requirement. The user only needs to provide a reference to + /// a normally allocated buffer. pub fn clone( mut cb: CloneCb, stack: &mut [u8], diff --git a/src/sys/aio.rs b/src/sys/aio.rs index a03caa4..b63affb 100644 --- a/src/sys/aio.rs +++ b/src/sys/aio.rs @@ -379,7 +379,7 @@ impl<'a> AioCb<'a> { } } - /// Like [`from_mut_slice`], but works on constant slices rather than + /// Like [`AioCb::from_mut_slice`], but works on constant slices rather than /// mutable slices. /// /// An `AioCb` created this way cannot be used with `read`, and its @@ -508,7 +508,7 @@ impl<'a> AioCb<'a> { libc::AIO_CANCELED => Ok(AioCancelStat::AioCanceled), libc::AIO_NOTCANCELED => Ok(AioCancelStat::AioNotCanceled), libc::AIO_ALLDONE => Ok(AioCancelStat::AioAllDone), - -1 => Err(Error::last()), + -1 => Err(Error::from(Errno::last())), _ => panic!("unknown aio_cancel return value") } } @@ -519,8 +519,8 @@ impl<'a> AioCb<'a> { }; match r { 0 => Ok(()), - num if num > 0 => Err(Error::from_errno(Errno::from_i32(num))), - -1 => Err(Error::last()), + num if num > 0 => Err(Error::from(Errno::from_i32(num))), + -1 => Err(Error::from(Errno::last())), num => panic!("unknown aio_error return value {:?}", num) } } @@ -735,7 +735,7 @@ pub fn aio_cancel_all(fd: RawFd) -> Result<AioCancelStat> { libc::AIO_CANCELED => Ok(AioCancelStat::AioCanceled), libc::AIO_NOTCANCELED => Ok(AioCancelStat::AioNotCanceled), libc::AIO_ALLDONE => Ok(AioCancelStat::AioAllDone), - -1 => Err(Error::last()), + -1 => Err(Error::from(Errno::last())), _ => panic!("unknown aio_cancel return value") } } @@ -943,8 +943,8 @@ impl<'a> LioCb<'a> { /// LioOpcode::LIO_WRITE /// ).finish(); /// let mut err = liocb.listio(LioMode::LIO_WAIT, SigevNotify::SigevNone); - /// while err == Err(Error::Sys(Errno::EIO)) || - /// err == Err(Error::Sys(Errno::EAGAIN)) { + /// while err == Err(Error::from(Errno::EIO)) || + /// err == Err(Error::from(Errno::EAGAIN)) { /// thread::sleep(time::Duration::from_millis(10)); /// err = liocb.listio_resubmit(LioMode::LIO_WAIT, SigevNotify::SigevNone); /// } @@ -983,13 +983,13 @@ impl<'a> LioCb<'a> { // aiocb is complete; collect its status and don't resubmit self.results[i] = Some(a.aio_return_unpinned()); }, - Err(Error::Sys(Errno::EAGAIN)) => { + Err(Errno::EAGAIN) => { self.list.push(a as *mut AioCb<'a> as *mut libc::aiocb); }, - Err(Error::Sys(Errno::EINPROGRESS)) => { + Err(Errno::EINPROGRESS) => { // aiocb is was successfully queued; no need to do anything }, - Err(Error::Sys(Errno::EINVAL)) => panic!( + Err(Errno::EINVAL) => panic!( "AioCb was never submitted, or already finalized"), _ => unreachable!() } @@ -1102,8 +1102,8 @@ impl<'a> LioCbBuilder<'a> { /// /// Afterwards it will be possible to issue the operations with /// [`LioCb::listio`]. Conversely, it will no longer be possible to add new - /// operations with [`LioCb::emplace_slice`] or - /// [`LioCb::emplace_mut_slice`]. + /// operations with [`LioCbBuilder::emplace_slice`] or + /// [`LioCbBuilder::emplace_mut_slice`]. /// /// [`LioCb::listio`]: struct.LioCb.html#method.listio /// [`LioCb::from_mut_slice`]: struct.LioCb.html#method.from_mut_slice diff --git a/src/sys/epoll.rs b/src/sys/epoll.rs index 2437bbe..b73af13 100644 --- a/src/sys/epoll.rs +++ b/src/sys/epoll.rs @@ -1,10 +1,9 @@ -use crate::Result; +use crate::{Error, Result}; use crate::errno::Errno; use libc::{self, c_int}; use std::os::unix::io::RawFd; use std::ptr; use std::mem; -use crate::Error; libc_bitflags!( pub struct EpollFlags: c_int { @@ -86,7 +85,7 @@ pub fn epoll_ctl<'a, T>(epfd: RawFd, op: EpollOp, fd: RawFd, event: T) -> Result { let mut event: Option<&mut EpollEvent> = event.into(); if event.is_none() && op != EpollOp::EpollCtlDel { - Err(Error::Sys(Errno::EINVAL)) + Err(Error::from(Errno::EINVAL)) } else { let res = unsafe { if let Some(ref mut event) = event { diff --git a/src/sys/mman.rs b/src/sys/mman.rs index 34c7663..58edf08 100644 --- a/src/sys/mman.rs +++ b/src/sys/mman.rs @@ -320,7 +320,7 @@ pub unsafe fn mmap(addr: *mut c_void, length: size_t, prot: ProtFlags, flags: Ma let ret = libc::mmap(addr, length, prot.bits(), flags.bits(), fd, offset); if ret == libc::MAP_FAILED { - Err(Error::Sys(Errno::last())) + Err(Error::from(Errno::last())) } else { Ok(ret) } @@ -344,7 +344,7 @@ pub unsafe fn mremap( let ret = libc::mremap(addr, old_size, new_size, flags.bits(), new_address.unwrap_or(std::ptr::null_mut())); if ret == libc::MAP_FAILED { - Err(Error::Sys(Errno::last())) + Err(Error::from(Errno::last())) } else { Ok(ret) } diff --git a/src/sys/ptrace/linux.rs b/src/sys/ptrace/linux.rs index 8d1dd16..4ac4393 100644 --- a/src/sys/ptrace/linux.rs +++ b/src/sys/ptrace/linux.rs @@ -2,7 +2,7 @@ use cfg_if::cfg_if; use std::{mem, ptr}; -use crate::{Error, Result}; +use crate::Result; use crate::errno::Errno; use libc::{self, c_void, c_long, siginfo_t}; use crate::unistd::Pid; @@ -180,7 +180,7 @@ fn ptrace_peek(request: Request, pid: Pid, addr: AddressType, data: *mut c_void) libc::ptrace(request as RequestType, libc::pid_t::from(pid), addr, data) }; match Errno::result(ret) { - Ok(..) | Err(Error::Sys(Errno::UnknownErrno)) => Ok(ret), + Ok(..) | Err(Errno::UnknownErrno) => Ok(ret), err @ Err(..) => err, } } diff --git a/src/sys/reboot.rs b/src/sys/reboot.rs index e319130..5b37682 100644 --- a/src/sys/reboot.rs +++ b/src/sys/reboot.rs @@ -26,7 +26,7 @@ pub fn reboot(how: RebootMode) -> Result<Infallible> { unsafe { libc::reboot(how as libc::c_int) }; - Err(Error::Sys(Errno::last())) + Err(Error::from(Errno::last())) } /// Enable or disable the reboot keystroke (Ctrl-Alt-Delete). diff --git a/src/sys/signal.rs b/src/sys/signal.rs index bf3f762..273b352 100644 --- a/src/sys/signal.rs +++ b/src/sys/signal.rs @@ -121,7 +121,7 @@ impl FromStr for Signal { target_os = "fuchsia", target_os = "linux", target_os = "redox")))] "SIGINFO" => Signal::SIGINFO, - _ => return Err(Error::invalid_argument()), + _ => return Err(Error::from(Errno::EINVAL)), }) } } @@ -368,7 +368,7 @@ impl TryFrom<libc::c_int> for Signal { if 0 < signum && signum < NSIG { Ok(unsafe { mem::transmute(signum) }) } else { - Err(Error::invalid_argument()) + Err(Error::from(Errno::EINVAL)) } } } @@ -664,7 +664,7 @@ pub unsafe fn sigaction(signal: Signal, sigaction: &SigAction) -> Result<SigActi /// /// # Errors /// -/// Returns [`Error::UnsupportedOperation`] if `handler` is +/// Returns [`Error(Errno::EOPNOTSUPP)`] if `handler` is /// [`SigAction`][SigActionStruct]. Use [`sigaction`][SigActionFn] instead. /// /// `signal` also returns any error from `libc::signal`, such as when an attempt @@ -681,7 +681,7 @@ pub unsafe fn signal(signal: Signal, handler: SigHandler) -> Result<SigHandler> SigHandler::SigIgn => libc::signal(signal, libc::SIG_IGN), SigHandler::Handler(handler) => libc::signal(signal, handler as libc::sighandler_t), #[cfg(not(target_os = "redox"))] - SigHandler::SigAction(_) => return Err(Error::UnsupportedOperation), + SigHandler::SigAction(_) => return Err(Error::from(Errno::ENOTSUP)), }; Errno::result(res).map(|oldhandler| { match oldhandler { @@ -949,7 +949,7 @@ mod tests { #[test] fn test_from_str_invalid_value() { - let errval = Err(Error::Sys(Errno::EINVAL)); + let errval = Err(Error::from(Errno::EINVAL)); assert_eq!("NOSIGNAL".parse::<Signal>(), errval); assert_eq!("kill".parse::<Signal>(), errval); assert_eq!("9".parse::<Signal>(), errval); diff --git a/src/sys/signalfd.rs b/src/sys/signalfd.rs index 1e162cf..49811a1 100644 --- a/src/sys/signalfd.rs +++ b/src/sys/signalfd.rs @@ -108,7 +108,7 @@ impl SignalFd { match res { Ok(SIGNALFD_SIGINFO_SIZE) => Ok(Some(unsafe { mem::transmute(buffer.assume_init()) })), Ok(_) => unreachable!("partial read on signalfd"), - Err(Error::Sys(Errno::EAGAIN)) => Ok(None), + Err(Errno::EAGAIN) => Ok(None), Err(error) => Err(error) } } @@ -117,7 +117,7 @@ impl SignalFd { impl Drop for SignalFd { fn drop(&mut self) { let e = unistd::close(self.0); - if !std::thread::panicking() && e == Err(Error::Sys(Errno::EBADF)) { + if !std::thread::panicking() && e == Err(Error::from(Errno::EBADF)) { panic!("Closing an invalid file descriptor!"); }; } diff --git a/src/sys/socket/addr.rs b/src/sys/socket/addr.rs index 6a0bc9a..d486056 100644 --- a/src/sys/socket/addr.rs +++ b/src/sys/socket/addr.rs @@ -536,7 +536,7 @@ impl UnixAddr { let bytes = cstr.to_bytes(); if bytes.len() > ret.sun_path.len() { - return Err(Error::Sys(Errno::ENAMETOOLONG)); + return Err(Error::from(Errno::ENAMETOOLONG)); } ptr::copy_nonoverlapping(bytes.as_ptr(), @@ -563,7 +563,7 @@ impl UnixAddr { }; if path.len() + 1 > ret.sun_path.len() { - return Err(Error::Sys(Errno::ENAMETOOLONG)); + return Err(Error::from(Errno::ENAMETOOLONG)); } // Abstract addresses are represented by sun_path[0] == @@ -1035,7 +1035,7 @@ pub mod sys_control { pub fn from_name(sockfd: RawFd, name: &str, unit: u32) -> Result<SysControlAddr> { if name.len() > MAX_KCTL_NAME { - return Err(Error::Sys(Errno::ENAMETOOLONG)); + return Err(Error::from(Errno::ENAMETOOLONG)); } let mut ctl_name = [0; MAX_KCTL_NAME]; diff --git a/src/sys/socket/mod.rs b/src/sys/socket/mod.rs index 6907046..da5573c 100644 --- a/src/sys/socket/mod.rs +++ b/src/sys/socket/mod.rs @@ -610,6 +610,17 @@ pub enum ControlMessageOwned { #[cfg(target_os = "linux")] UdpGroSegments(u16), + /// SO_RXQ_OVFL indicates that an unsigned 32 bit value + /// ancilliary msg (cmsg) should be attached to recieved + /// skbs indicating the number of packets dropped by the + /// socket between the last recieved packet and this + /// received packet. + /// + /// `RxqOvfl` socket option should be enabled on a socket + /// to allow receiving the drop counter. + #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] + RxqOvfl(u32), + /// Catch-all variant for unimplemented cmsg types. #[doc(hidden)] Unknown(UnknownCmsg), @@ -708,6 +719,11 @@ impl ControlMessageOwned { let gso_size: u16 = ptr::read_unaligned(p as *const _); ControlMessageOwned::UdpGroSegments(gso_size) }, + #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] + (libc::SOL_SOCKET, libc::SO_RXQ_OVFL) => { + let drop_counter = ptr::read_unaligned(p as *const u32); + ControlMessageOwned::RxqOvfl(drop_counter) + }, (_, _) => { let sl = slice::from_raw_parts(p, len); let ucmsg = UnknownCmsg(*header, Vec::<u8>::from(sl)); @@ -826,6 +842,14 @@ pub enum ControlMessage<'a> { target_os = "android", target_os = "ios",))] Ipv6PacketInfo(&'a libc::in6_pktinfo), + + /// SO_RXQ_OVFL indicates that an unsigned 32 bit value + /// ancilliary msg (cmsg) should be attached to recieved + /// skbs indicating the number of packets dropped by the + /// socket between the last recieved packet and this + /// received packet. + #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] + RxqOvfl(&'a u32), } // An opaque structure used to prevent cmsghdr from being a public type @@ -916,6 +940,10 @@ impl<'a> ControlMessage<'a> { target_os = "netbsd", target_os = "freebsd", target_os = "android", target_os = "ios",))] ControlMessage::Ipv6PacketInfo(info) => info as *const _ as *const u8, + #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] + ControlMessage::RxqOvfl(drop_count) => { + drop_count as *const _ as *const u8 + }, }; unsafe { ptr::copy_nonoverlapping( @@ -964,6 +992,10 @@ impl<'a> ControlMessage<'a> { target_os = "netbsd", target_os = "freebsd", target_os = "android", target_os = "ios",))] ControlMessage::Ipv6PacketInfo(info) => mem::size_of_val(info), + #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] + ControlMessage::RxqOvfl(drop_count) => { + mem::size_of_val(drop_count) + }, } } @@ -988,6 +1020,8 @@ impl<'a> ControlMessage<'a> { target_os = "netbsd", target_os = "freebsd", target_os = "android", target_os = "ios",))] ControlMessage::Ipv6PacketInfo(_) => libc::IPPROTO_IPV6, + #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] + ControlMessage::RxqOvfl(_) => libc::SOL_SOCKET, } } @@ -1023,6 +1057,10 @@ impl<'a> ControlMessage<'a> { target_os = "netbsd", target_os = "freebsd", target_os = "android", target_os = "ios",))] ControlMessage::Ipv6PacketInfo(_) => libc::IPV6_PKTINFO, + #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] + ControlMessage::RxqOvfl(_) => { + libc::SO_RXQ_OVFL + }, } } @@ -1572,7 +1610,7 @@ pub fn recvfrom(sockfd: RawFd, buf: &mut [u8]) &mut len as *mut socklen_t))? as usize; match sockaddr_storage_to_addr(&addr, len as usize) { - Err(Error::Sys(Errno::ENOTCONN)) => Ok((ret, None)), + Err(Errno::ENOTCONN) => Ok((ret, None)), Ok(addr) => Ok((ret, Some(addr))), Err(e) => Err(e) } @@ -1708,7 +1746,7 @@ pub fn sockaddr_storage_to_addr( assert!(len <= mem::size_of::<sockaddr_un>()); if len < mem::size_of_val(&addr.ss_family) { - return Err(Error::Sys(Errno::ENOTCONN)); + return Err(Error::from(Errno::ENOTCONN)); } match c_int::from(addr.ss_family) { diff --git a/src/sys/socket/sockopt.rs b/src/sys/socket/sockopt.rs index fe17395..e2f2caf 100644 --- a/src/sys/socket/sockopt.rs +++ b/src/sys/socket/sockopt.rs @@ -238,6 +238,8 @@ cfg_if! { } sockopt_impl!(Both, IpMulticastTtl, libc::IPPROTO_IP, libc::IP_MULTICAST_TTL, u8); sockopt_impl!(Both, IpMulticastLoop, libc::IPPROTO_IP, libc::IP_MULTICAST_LOOP, bool); +#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] +sockopt_impl!(Both, IpFreebind, libc::IPPROTO_IP, libc::IP_FREEBIND, bool); sockopt_impl!(Both, ReceiveTimeout, libc::SOL_SOCKET, libc::SO_RCVTIMEO, TimeVal); sockopt_impl!(Both, SendTimeout, libc::SOL_SOCKET, libc::SO_SNDTIMEO, TimeVal); sockopt_impl!(Both, Broadcast, libc::SOL_SOCKET, libc::SO_BROADCAST, bool); @@ -259,6 +261,8 @@ sockopt_impl!(Both, TcpKeepIdle, libc::IPPROTO_TCP, libc::TCP_KEEPIDLE, u32); sockopt_impl!(Both, TcpKeepCount, libc::IPPROTO_TCP, libc::TCP_KEEPCNT, u32); #[cfg(not(target_os = "openbsd"))] sockopt_impl!(Both, TcpKeepInterval, libc::IPPROTO_TCP, libc::TCP_KEEPINTVL, u32); +#[cfg(any(target_os = "fuchsia", target_os = "linux"))] +sockopt_impl!(Both, TcpUserTimeout, libc::IPPROTO_TCP, libc::TCP_USER_TIMEOUT, u32); sockopt_impl!(Both, RcvBuf, libc::SOL_SOCKET, libc::SO_RCVBUF, usize); sockopt_impl!(Both, SndBuf, libc::SOL_SOCKET, libc::SO_SNDBUF, usize); #[cfg(any(target_os = "android", target_os = "linux"))] @@ -324,6 +328,8 @@ sockopt_impl!(Both, Ipv4RecvDstAddr, libc::IPPROTO_IP, libc::IP_RECVDSTADDR, boo sockopt_impl!(Both, UdpGsoSegment, libc::SOL_UDP, libc::UDP_SEGMENT, libc::c_int); #[cfg(target_os = "linux")] sockopt_impl!(Both, UdpGroSegment, libc::IPPROTO_UDP, libc::UDP_GRO, bool); +#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] +sockopt_impl!(Both, RxqOvfl, libc::SOL_SOCKET, libc::SO_RXQ_OVFL, libc::c_int); #[cfg(any(target_os = "android", target_os = "linux"))] #[derive(Copy, Clone, Debug)] diff --git a/src/sys/termios.rs b/src/sys/termios.rs index 8c3c1cf..9abae9d 100644 --- a/src/sys/termios.rs +++ b/src/sys/termios.rs @@ -449,7 +449,7 @@ impl TryFrom<libc::speed_t> for BaudRate { B3500000 => Ok(BaudRate::B3500000), #[cfg(any(target_os = "android", all(target_os = "linux", not(target_arch = "sparc64"))))] B4000000 => Ok(BaudRate::B4000000), - _ => Err(Error::invalid_argument()) + _ => Err(Error::from(Errno::EINVAL)) } } } diff --git a/src/sys/timerfd.rs b/src/sys/timerfd.rs index e42fffd..44915be 100644 --- a/src/sys/timerfd.rs +++ b/src/sys/timerfd.rs @@ -30,7 +30,7 @@ //! ``` use crate::sys::time::TimeSpec; use crate::unistd::read; -use crate::{errno::Errno, Error, Result}; +use crate::{errno::Errno, Result}; use bitflags::bitflags; use libc::c_int; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; @@ -259,7 +259,7 @@ impl TimerFd { loop { if let Err(e) = read(self.fd, &mut [0u8; 8]) { match e { - Error::Sys(Errno::EINTR) => continue, + Errno::EINTR => continue, _ => return Err(e), } } else { @@ -277,7 +277,7 @@ impl Drop for TimerFd { let result = Errno::result(unsafe { libc::close(self.fd) }); - if let Err(Error::Sys(Errno::EBADF)) = result { + if let Err(Errno::EBADF) = result { panic!("close of TimerFd encountered EBADF"); } } diff --git a/src/sys/uio.rs b/src/sys/uio.rs index b8ae860..48a0efd 100644 --- a/src/sys/uio.rs +++ b/src/sys/uio.rs @@ -168,7 +168,7 @@ pub fn process_vm_readv( #[repr(transparent)] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -pub struct IoVec<T>(libc::iovec, PhantomData<T>); +pub struct IoVec<T>(pub(crate) libc::iovec, PhantomData<T>); impl<T> IoVec<T> { #[inline] @@ -184,6 +184,14 @@ impl<T> IoVec<T> { } impl<'a> IoVec<&'a [u8]> { + #[cfg(target_os = "freebsd")] + pub(crate) fn from_raw_parts(base: *mut c_void, len: usize) -> Self { + IoVec(libc::iovec { + iov_base: base, + iov_len: len + }, PhantomData) + } + pub fn from_slice(buf: &'a [u8]) -> IoVec<&'a [u8]> { IoVec(libc::iovec { iov_base: buf.as_ptr() as *mut c_void, diff --git a/src/time.rs b/src/time.rs index f7da654..45dd26e 100644 --- a/src/time.rs +++ b/src/time.rs @@ -255,6 +255,6 @@ pub fn clock_getcpuclockid(pid: Pid) -> Result<ClockId> { let res = unsafe { clk_id.assume_init() }; Ok(ClockId::from(res)) } else { - Err(Error::Sys(Errno::from_i32(ret))) + Err(Error::from(Errno::from_i32(ret))) } } diff --git a/src/unistd.rs b/src/unistd.rs index d406efe..de3b049 100644 --- a/src/unistd.rs +++ b/src/unistd.rs @@ -393,7 +393,7 @@ pub fn dup3(oldfd: RawFd, newfd: RawFd, flags: OFlag) -> Result<RawFd> { #[inline] fn dup3_polyfill(oldfd: RawFd, newfd: RawFd, flags: OFlag) -> Result<RawFd> { if oldfd == newfd { - return Err(Error::Sys(Errno::EINVAL)); + return Err(Error::from(Errno::EINVAL)); } let fd = dup2(oldfd, newfd)?; @@ -567,7 +567,7 @@ fn reserve_double_buffer_size<T>(buf: &mut Vec<T>, limit: usize) -> Result<()> { use std::cmp::min; if buf.capacity() >= limit { - return Err(Error::Sys(Errno::ERANGE)) + return Err(Error::from(Errno::ERANGE)) } let capacity = min(buf.capacity() * 2, limit); @@ -610,7 +610,7 @@ pub fn getcwd() -> Result<PathBuf> { let error = Errno::last(); // ERANGE means buffer was too small to store directory name if error != Errno::ERANGE { - return Err(Error::Sys(error)); + return Err(Error::from(error)); } } @@ -734,7 +734,7 @@ pub fn execv<S: AsRef<CStr>>(path: &CStr, argv: &[S]) -> Result<Infallible> { libc::execv(path.as_ptr(), args_p.as_ptr()) }; - Err(Error::Sys(Errno::last())) + Err(Error::from(Errno::last())) } @@ -759,7 +759,7 @@ pub fn execve<SA: AsRef<CStr>, SE: AsRef<CStr>>(path: &CStr, args: &[SA], env: & libc::execve(path.as_ptr(), args_p.as_ptr(), env_p.as_ptr()) }; - Err(Error::Sys(Errno::last())) + Err(Error::from(Errno::last())) } /// Replace the current process image with a new one and replicate shell `PATH` @@ -779,7 +779,7 @@ pub fn execvp<S: AsRef<CStr>>(filename: &CStr, args: &[S]) -> Result<Infallible> libc::execvp(filename.as_ptr(), args_p.as_ptr()) }; - Err(Error::Sys(Errno::last())) + Err(Error::from(Errno::last())) } /// Replace the current process image with a new one and replicate shell `PATH` @@ -800,7 +800,7 @@ pub fn execvpe<SA: AsRef<CStr>, SE: AsRef<CStr>>(filename: &CStr, args: &[SA], e libc::execvpe(filename.as_ptr(), args_p.as_ptr(), env_p.as_ptr()) }; - Err(Error::Sys(Errno::last())) + Err(Error::from(Errno::last())) } /// Replace the current process image with a new one (see @@ -828,7 +828,7 @@ pub fn fexecve<SA: AsRef<CStr> ,SE: AsRef<CStr>>(fd: RawFd, args: &[SA], env: &[ libc::fexecve(fd, args_p.as_ptr(), env_p.as_ptr()) }; - Err(Error::Sys(Errno::last())) + Err(Error::from(Errno::last())) } /// Execute program relative to a directory file descriptor (see @@ -853,7 +853,7 @@ pub fn execveat<SA: AsRef<CStr>,SE: AsRef<CStr>>(dirfd: RawFd, pathname: &CStr, args_p.as_ptr(), env_p.as_ptr(), flags); }; - Err(Error::Sys(Errno::last())) + Err(Error::from(Errno::last())) } /// Daemonize this process by detaching from the controlling terminal (see @@ -1056,13 +1056,13 @@ pub fn lseek64(fd: RawFd, offset: libc::off64_t, whence: Whence) -> Result<libc: /// Create an interprocess channel. /// /// See also [pipe(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/pipe.html) -pub fn pipe() -> Result<(RawFd, RawFd)> { +pub fn pipe() -> std::result::Result<(RawFd, RawFd), Error> { unsafe { let mut fds = mem::MaybeUninit::<[c_int; 2]>::uninit(); let res = libc::pipe(fds.as_mut_ptr() as *mut c_int); - Errno::result(res)?; + Error::result(res)?; Ok((fds.assume_init()[0], fds.assume_init()[1])) } @@ -1133,7 +1133,7 @@ pub fn isatty(fd: RawFd) -> Result<bool> { } else { match Errno::last() { Errno::ENOTTY => Ok(false), - err => Err(Error::Sys(err)), + err => Err(Error::from(err)), } } } @@ -1432,11 +1432,11 @@ pub fn getgroups() -> Result<Vec<Gid>> { unsafe { groups.set_len(s as usize) }; return Ok(groups); }, - Err(Error::Sys(Errno::EINVAL)) => { + Err(Errno::EINVAL) => { // EINVAL indicates that the buffer size was too // small, resize it up to ngroups_max as limit. reserve_double_buffer_size(&mut groups, ngroups_max) - .or(Err(Error::Sys(Errno::EINVAL)))?; + .or(Err(Error::from(Errno::EINVAL)))?; }, Err(e) => return Err(e) } @@ -1558,7 +1558,7 @@ pub fn getgrouplist(user: &CStr, group: Gid) -> Result<Vec<Gid>> { // groups as possible, but Linux manpages do not mention this // behavior. reserve_double_buffer_size(&mut groups, ngroups_max as usize) - .map_err(|_| Error::invalid_argument())?; + .map_err(|_| Error::from(Errno::EINVAL))?; } } } @@ -1670,10 +1670,11 @@ pub mod alarm { //! sigaction(Signal::SIGALRM, &sa); //! } //! + //! let start = Instant::now(); + //! //! // Set an alarm for 1 second from now. //! alarm::set(1); //! - //! let start = Instant::now(); //! // Pause the process until the alarm signal is received. //! let mut sigset = SigSet::empty(); //! sigset.add(Signal::SIGALRM); @@ -1915,7 +1916,7 @@ pub fn fpathconf(fd: RawFd, var: PathconfVar) -> Result<Option<c_long>> { if errno::errno() == 0 { Ok(None) } else { - Err(Error::Sys(Errno::last())) + Err(Error::from(Errno::last())) } } else { Ok(Some(raw)) @@ -1954,7 +1955,7 @@ pub fn pathconf<P: ?Sized + NixPath>(path: &P, var: PathconfVar) -> Result<Optio if errno::errno() == 0 { Ok(None) } else { - Err(Error::Sys(Errno::last())) + Err(Error::from(Errno::last())) } } else { Ok(Some(raw)) @@ -2453,7 +2454,7 @@ pub fn sysconf(var: SysconfVar) -> Result<Option<c_long>> { if errno::errno() == 0 { Ok(None) } else { - Err(Error::Sys(Errno::last())) + Err(Error::from(Errno::last())) } } else { Ok(Some(raw)) @@ -2720,7 +2721,7 @@ impl User { // Trigger the internal buffer resizing logic. reserve_double_buffer_size(&mut cbuf, buflimit)?; } else { - return Err(Error::Sys(Errno::last())); + return Err(Error::from(Errno::last())); } } } @@ -2841,7 +2842,7 @@ impl Group { // Trigger the internal buffer resizing logic. reserve_double_buffer_size(&mut cbuf, buflimit)?; } else { - return Err(Error::Sys(Errno::last())); + return Err(Error::from(Errno::last())); } } } @@ -2900,7 +2901,7 @@ pub fn ttyname(fd: RawFd) -> Result<PathBuf> { let ret = unsafe { libc::ttyname_r(fd, c_buf, buf.len()) }; if ret != 0 { - return Err(Error::Sys(Errno::from_i32(ret))); + return Err(Error::from(Errno::from_i32(ret))); } let nul = buf.iter().position(|c| *c == b'\0').unwrap(); diff --git a/test/common/mod.rs b/test/common/mod.rs index 8a79d6a..cdc3258 100644 --- a/test/common/mod.rs +++ b/test/common/mod.rs @@ -31,6 +31,20 @@ cfg_if! { } } +/// Skip the test if we don't have the ability to mount file systems. +#[cfg(target_os = "freebsd")] +#[macro_export] macro_rules! require_mount { + ($name:expr) => { + use ::sysctl::CtlValue; + use nix::unistd::Uid; + + if !Uid::current().is_root() && CtlValue::Int(0) == ::sysctl::value("vfs.usermount").unwrap() + { + skip!("{} requires the ability to mount file systems. Skipping test.", $name); + } + } +} + #[cfg(any(target_os = "linux", target_os= "android"))] #[macro_export] macro_rules! skip_if_cirrus { ($reason:expr) => { diff --git a/test/sys/test_aio.rs b/test/sys/test_aio.rs index 753f118..3208410 100644 --- a/test/sys/test_aio.rs +++ b/test/sys/test_aio.rs @@ -142,9 +142,7 @@ fn test_fsync_error() { } #[test] -#[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)] -// On Travis, aio_suspend hits an assertion within glibc. This is either a bug -// in Travis's version of glibc or Linux. Either way, we must skip the test. +// On Cirrus on Linux, this test fails due to a glibc bug. // https://github.com/nix-rust/nix/issues/1099 #[cfg_attr(target_os = "linux", ignore)] // On Cirrus, aio_suspend is failing with EINVAL @@ -179,7 +177,7 @@ fn test_aio_suspend() { let cbbuf = [wcb.as_ref(), rcb.as_ref()]; let r = aio_suspend(&cbbuf[..], Some(timeout)); match r { - Err(Error::Sys(Errno::EINTR)) => continue, + Err(Errno::EINTR) => continue, Err(e) => panic!("aio_suspend returned {:?}", e), Ok(_) => () }; diff --git a/test/sys/test_aio_drop.rs b/test/sys/test_aio_drop.rs index 784ee3e..71a2183 100644 --- a/test/sys/test_aio_drop.rs +++ b/test/sys/test_aio_drop.rs @@ -9,7 +9,6 @@ target_os = "macos", target_os = "freebsd", target_os = "netbsd")))] -#[cfg_attr(target_env = "gnu", ignore = "Occasionally fails in Travis; glibc bug suspected")] fn test_drop() { use nix::sys::aio::*; use nix::sys::signal::*; diff --git a/test/sys/test_epoll.rs b/test/sys/test_epoll.rs index e0dc513..57bc484 100644 --- a/test/sys/test_epoll.rs +++ b/test/sys/test_epoll.rs @@ -8,11 +8,11 @@ pub fn test_epoll_errno() { let efd = epoll_create1(EpollCreateFlags::empty()).unwrap(); let result = epoll_ctl(efd, EpollOp::EpollCtlDel, 1, None); assert!(result.is_err()); - assert_eq!(result.unwrap_err(), Error::Sys(Errno::ENOENT)); + assert_eq!(result.unwrap_err(), Error::from(Errno::ENOENT)); let result = epoll_ctl(efd, EpollOp::EpollCtlAdd, 1, None); assert!(result.is_err()); - assert_eq!(result.unwrap_err(), Error::Sys(Errno::EINVAL)); + assert_eq!(result.unwrap_err(), Error::from(Errno::EINVAL)); } #[test] diff --git a/test/sys/test_inotify.rs b/test/sys/test_inotify.rs index a8ead46..121b726 100644 --- a/test/sys/test_inotify.rs +++ b/test/sys/test_inotify.rs @@ -14,7 +14,7 @@ pub fn test_inotify() { instance.add_watch(tempdir.path(), AddWatchFlags::IN_ALL_EVENTS).unwrap(); let events = instance.read_events(); - assert_eq!(events.unwrap_err(), Error::Sys(Errno::EAGAIN)); + assert_eq!(events.unwrap_err(), Error::from(Errno::EAGAIN)); File::create(tempdir.path().join("test")).unwrap(); @@ -31,7 +31,7 @@ pub fn test_inotify_multi_events() { instance.add_watch(tempdir.path(), AddWatchFlags::IN_ALL_EVENTS).unwrap(); let events = instance.read_events(); - assert_eq!(events.unwrap_err(), Error::Sys(Errno::EAGAIN)); + assert_eq!(events.unwrap_err(), Error::from(Errno::EAGAIN)); File::create(tempdir.path().join("test")).unwrap(); rename(tempdir.path().join("test"), tempdir.path().join("test2")).unwrap(); diff --git a/test/sys/test_ioctl.rs b/test/sys/test_ioctl.rs index ddb8696..236d242 100644 --- a/test/sys/test_ioctl.rs +++ b/test/sys/test_ioctl.rs @@ -167,15 +167,14 @@ mod linux_ioctls { use tempfile::tempfile; use libc::{TCGETS, TCSBRK, TCSETS, TIOCNXCL, termios}; - use nix::Error::Sys; - use nix::errno::Errno::{ENOTTY, ENOSYS}; + use nix::errno::Errno; ioctl_none_bad!(tiocnxcl, TIOCNXCL); #[test] fn test_ioctl_none_bad() { let file = tempfile().unwrap(); let res = unsafe { tiocnxcl(file.as_raw_fd()) }; - assert_eq!(res, Err(Sys(ENOTTY))); + assert_eq!(res, Err(Errno::ENOTTY)); } ioctl_read_bad!(tcgets, TCGETS, termios); @@ -184,7 +183,7 @@ mod linux_ioctls { let file = tempfile().unwrap(); let mut termios = unsafe { mem::zeroed() }; let res = unsafe { tcgets(file.as_raw_fd(), &mut termios) }; - assert_eq!(res, Err(Sys(ENOTTY))); + assert_eq!(res, Err(Errno::ENOTTY)); } ioctl_write_int_bad!(tcsbrk, TCSBRK); @@ -192,7 +191,7 @@ mod linux_ioctls { fn test_ioctl_write_int_bad() { let file = tempfile().unwrap(); let res = unsafe { tcsbrk(file.as_raw_fd(), 0) }; - assert_eq!(res, Err(Sys(ENOTTY))); + assert_eq!(res, Err(Errno::ENOTTY)); } ioctl_write_ptr_bad!(tcsets, TCSETS, termios); @@ -201,7 +200,7 @@ mod linux_ioctls { let file = tempfile().unwrap(); let termios: termios = unsafe { mem::zeroed() }; let res = unsafe { tcsets(file.as_raw_fd(), &termios) }; - assert_eq!(res, Err(Sys(ENOTTY))); + assert_eq!(res, Err(Errno::ENOTTY)); } // FIXME: Find a suitable example for `ioctl_readwrite_bad` @@ -212,7 +211,7 @@ mod linux_ioctls { fn test_ioctl_none() { let file = tempfile().unwrap(); let res = unsafe { log_status(file.as_raw_fd()) }; - assert!(res == Err(Sys(ENOTTY)) || res == Err(Sys(ENOSYS))); + assert!(res == Err(Errno::ENOTTY) || res == Err(Errno::ENOSYS)); } #[repr(C)] @@ -231,7 +230,7 @@ mod linux_ioctls { let file = tempfile().unwrap(); let data: v4l2_audio = unsafe { mem::zeroed() }; let res = unsafe { s_audio(file.as_raw_fd(), &data) }; - assert!(res == Err(Sys(ENOTTY)) || res == Err(Sys(ENOSYS))); + assert!(res == Err(Errno::ENOTTY) || res == Err(Errno::ENOSYS)); } // From linux/net/bluetooth/hci_sock.h @@ -242,7 +241,7 @@ mod linux_ioctls { fn test_ioctl_write_int() { let file = tempfile().unwrap(); let res = unsafe { hcidevup(file.as_raw_fd(), 0) }; - assert!(res == Err(Sys(ENOTTY)) || res == Err(Sys(ENOSYS))); + assert!(res == Err(Errno::ENOTTY) || res == Err(Errno::ENOSYS)); } // From linux/videodev2.h @@ -252,7 +251,7 @@ mod linux_ioctls { let file = tempfile().unwrap(); let mut data: v4l2_audio = unsafe { mem::zeroed() }; let res = unsafe { g_audio(file.as_raw_fd(), &mut data) }; - assert!(res == Err(Sys(ENOTTY)) || res == Err(Sys(ENOSYS))); + assert!(res == Err(Errno::ENOTTY) || res == Err(Errno::ENOSYS)); } // From linux/videodev2.h @@ -262,7 +261,7 @@ mod linux_ioctls { let file = tempfile().unwrap(); let mut data: v4l2_audio = unsafe { mem::zeroed() }; let res = unsafe { enum_audio(file.as_raw_fd(), &mut data) }; - assert!(res == Err(Sys(ENOTTY)) || res == Err(Sys(ENOSYS))); + assert!(res == Err(Errno::ENOTTY) || res == Err(Errno::ENOSYS)); } // FIXME: Find a suitable example for `ioctl_read_buf`. @@ -288,7 +287,7 @@ mod linux_ioctls { let file = tempfile().unwrap(); let data: [spi_ioc_transfer; 4] = unsafe { mem::zeroed() }; let res = unsafe { spi_ioc_message(file.as_raw_fd(), &data[..]) }; - assert!(res == Err(Sys(ENOTTY)) || res == Err(Sys(ENOSYS))); + assert!(res == Err(Errno::ENOTTY) || res == Err(Errno::ENOSYS)); } // FIXME: Find a suitable example for `ioctl_readwrite_buf`. @@ -302,8 +301,7 @@ mod freebsd_ioctls { use tempfile::tempfile; use libc::termios; - use nix::Error::Sys; - use nix::errno::Errno::ENOTTY; + use nix::errno::Errno; // From sys/sys/ttycom.h const TTY_IOC_MAGIC: u8 = b't'; @@ -316,7 +314,7 @@ mod freebsd_ioctls { fn test_ioctl_none() { let file = tempfile().unwrap(); let res = unsafe { tiocnxcl(file.as_raw_fd()) }; - assert_eq!(res, Err(Sys(ENOTTY))); + assert_eq!(res, Err(Errno::ENOTTY)); } ioctl_read!(tiocgeta, TTY_IOC_MAGIC, TTY_IOC_TYPE_GETA, termios); @@ -325,7 +323,7 @@ mod freebsd_ioctls { let file = tempfile().unwrap(); let mut termios = unsafe { mem::zeroed() }; let res = unsafe { tiocgeta(file.as_raw_fd(), &mut termios) }; - assert_eq!(res, Err(Sys(ENOTTY))); + assert_eq!(res, Err(Errno::ENOTTY)); } ioctl_write_ptr!(tiocseta, TTY_IOC_MAGIC, TTY_IOC_TYPE_SETA, termios); @@ -334,6 +332,6 @@ mod freebsd_ioctls { let file = tempfile().unwrap(); let termios: termios = unsafe { mem::zeroed() }; let res = unsafe { tiocseta(file.as_raw_fd(), &termios) }; - assert_eq!(res, Err(Sys(ENOTTY))); + assert_eq!(res, Err(Errno::ENOTTY)); } } diff --git a/test/sys/test_lio_listio_resubmit.rs b/test/sys/test_lio_listio_resubmit.rs index 6b33aa4..c907789 100644 --- a/test/sys/test_lio_listio_resubmit.rs +++ b/test/sys/test_lio_listio_resubmit.rs @@ -4,7 +4,6 @@ // we must disable the test here rather than in Cargo.toml #![cfg(target_os = "freebsd")] -use nix::Error; use nix::errno::*; use nix::libc::off_t; use nix::sys::aio::*; @@ -25,7 +24,7 @@ fn finish_liocb(liocb: &mut LioCb) { let e = liocb.error(j); match e { Ok(()) => break, - Err(Error::Sys(Errno::EINPROGRESS)) => + Err(Errno::EINPROGRESS) => thread::sleep(time::Duration::from_millis(10)), Err(x) => panic!("aio_error({:?})", x) } @@ -82,9 +81,9 @@ fn test_lio_listio_resubmit() { } let mut liocb = builder.finish(); let mut err = liocb.listio(LioMode::LIO_NOWAIT, SigevNotify::SigevNone); - while err == Err(Error::Sys(Errno::EIO)) || - err == Err(Error::Sys(Errno::EAGAIN)) || - err == Err(Error::Sys(Errno::EINTR)) { + while err == Err(Errno::EIO) || + err == Err(Errno::EAGAIN) || + err == Err(Errno::EINTR) { // thread::sleep(time::Duration::from_millis(10)); resubmit_count += 1; diff --git a/test/sys/test_ptrace.rs b/test/sys/test_ptrace.rs index b9793b3..985945d 100644 --- a/test/sys/test_ptrace.rs +++ b/test/sys/test_ptrace.rs @@ -1,4 +1,3 @@ -use nix::Error; use nix::errno::Errno; use nix::unistd::getpid; use nix::sys::ptrace; @@ -16,8 +15,8 @@ fn test_ptrace() { // FIXME: qemu-user doesn't implement ptrace on all arches, so permit ENOSYS require_capability!(CAP_SYS_PTRACE); let err = ptrace::attach(getpid()).unwrap_err(); - assert!(err == Error::Sys(Errno::EPERM) || err == Error::Sys(Errno::EINVAL) || - err == Error::Sys(Errno::ENOSYS)); + assert!(err == Errno::EPERM || err == Errno::EINVAL || + err == Errno::ENOSYS); } // Just make sure ptrace_setoptions can be called at all, for now. @@ -26,7 +25,7 @@ fn test_ptrace() { fn test_ptrace_setoptions() { require_capability!(CAP_SYS_PTRACE); let err = ptrace::setoptions(getpid(), Options::PTRACE_O_TRACESYSGOOD).unwrap_err(); - assert!(err != Error::UnsupportedOperation); + assert!(err != Errno::EOPNOTSUPP); } // Just make sure ptrace_getevent can be called at all, for now. @@ -35,7 +34,7 @@ fn test_ptrace_setoptions() { fn test_ptrace_getevent() { require_capability!(CAP_SYS_PTRACE); let err = ptrace::getevent(getpid()).unwrap_err(); - assert!(err != Error::UnsupportedOperation); + assert!(err != Errno::EOPNOTSUPP); } // Just make sure ptrace_getsiginfo can be called at all, for now. @@ -43,8 +42,8 @@ fn test_ptrace_getevent() { #[cfg(any(target_os = "android", target_os = "linux"))] fn test_ptrace_getsiginfo() { require_capability!(CAP_SYS_PTRACE); - if let Err(Error::UnsupportedOperation) = ptrace::getsiginfo(getpid()) { - panic!("ptrace_getsiginfo returns Error::UnsupportedOperation!"); + if let Err(Errno::EOPNOTSUPP) = ptrace::getsiginfo(getpid()) { + panic!("ptrace_getsiginfo returns Errno::EOPNOTSUPP!"); } } @@ -54,8 +53,8 @@ fn test_ptrace_getsiginfo() { fn test_ptrace_setsiginfo() { require_capability!(CAP_SYS_PTRACE); let siginfo = unsafe { mem::zeroed() }; - if let Err(Error::UnsupportedOperation) = ptrace::setsiginfo(getpid(), &siginfo) { - panic!("ptrace_setsiginfo returns Error::UnsupportedOperation!"); + if let Err(Errno::EOPNOTSUPP) = ptrace::setsiginfo(getpid(), &siginfo) { + panic!("ptrace_setsiginfo returns Errno::EOPNOTSUPP!"); } } @@ -79,7 +78,7 @@ fn test_ptrace_cont() { // On valid platforms the ptrace call should return Errno::EPERM, this // is already tested by `test_ptrace`. let err = ptrace::attach(getpid()).unwrap_err(); - if err == Error::Sys(Errno::ENOSYS) { + if err == Errno::ENOSYS { return; } diff --git a/test/sys/test_signal.rs b/test/sys/test_signal.rs index c8c13e5..1b89af5 100644 --- a/test/sys/test_signal.rs +++ b/test/sys/test_signal.rs @@ -1,5 +1,5 @@ #[cfg(not(target_os = "redox"))] -use nix::Error; +use nix::errno::Errno; use nix::sys::signal::*; use nix::unistd::*; use std::convert::TryFrom; @@ -92,7 +92,7 @@ fn test_signal_sigaction() { let _m = crate::SIGNAL_MTX.lock().expect("Mutex got poisoned by another test"); let action_handler = SigHandler::SigAction(test_sigaction_action); - assert_eq!(unsafe { signal(Signal::SIGINT, action_handler) }.unwrap_err(), Error::UnsupportedOperation); + assert_eq!(unsafe { signal(Signal::SIGINT, action_handler) }.unwrap_err(), Errno::ENOTSUP); } #[test] diff --git a/test/sys/test_socket.rs b/test/sys/test_socket.rs index c22eaeb..5471afe 100644 --- a/test/sys/test_socket.rs +++ b/test/sys/test_socket.rs @@ -519,7 +519,6 @@ mod recvfrom { // Test error handling of our recvmsg wrapper #[test] pub fn test_recvmsg_ebadf() { - use nix::Error; use nix::errno::Errno; use nix::sys::socket::{MsgFlags, recvmsg}; use nix::sys::uio::IoVec; @@ -528,7 +527,7 @@ pub fn test_recvmsg_ebadf() { let iov = [IoVec::from_mut_slice(&mut buf[..])]; let fd = -1; // Bad file descriptor let r = recvmsg(fd, &iov, None, MsgFlags::empty()); - assert_eq!(r.err().unwrap(), Error::Sys(Errno::EBADF)); + assert_eq!(r.err().unwrap(), Errno::EBADF); } // Disable the test on emulated platforms due to a bug in QEMU versions < @@ -818,7 +817,6 @@ pub fn test_sendmsg_ipv4packetinfo() { target_os = "freebsd"))] #[test] pub fn test_sendmsg_ipv6packetinfo() { - use nix::Error; use nix::errno::Errno; use nix::sys::uio::IoVec; use nix::sys::socket::{socket, sendmsg, bind, @@ -835,7 +833,7 @@ pub fn test_sendmsg_ipv6packetinfo() { let inet_addr = InetAddr::from_std(&std_sa); let sock_addr = SockAddr::new_inet(inet_addr); - if let Err(Error::Sys(Errno::EADDRNOTAVAIL)) = bind(sock, &sock_addr) { + if let Err(Errno::EADDRNOTAVAIL) = bind(sock, &sock_addr) { println!("IPv6 not available, skipping test."); return; } @@ -1145,7 +1143,6 @@ pub fn test_unixdomain() { #[cfg(any(target_os = "macos", target_os = "ios"))] #[test] pub fn test_syscontrol() { - use nix::Error; use nix::errno::Errno; use nix::sys::socket::{socket, SockAddr, SockType, SockFlag, SockProtocol}; @@ -1153,7 +1150,7 @@ pub fn test_syscontrol() { SockFlag::empty(), SockProtocol::KextControl) .expect("socket failed"); let _sockaddr = SockAddr::new_sys_control(fd, "com.apple.net.utun_control", 0).expect("resolving sys_control name failed"); - assert_eq!(SockAddr::new_sys_control(fd, "foo.bar.lol", 0).err(), Some(Error::Sys(Errno::ENOENT))); + assert_eq!(SockAddr::new_sys_control(fd, "foo.bar.lol", 0).err(), Some(Errno::ENOENT)); // requires root privileges // connect(fd, &sockaddr).expect("connect failed"); @@ -1500,7 +1497,6 @@ pub fn test_recv_ipv6pktinfo() { #[test] pub fn test_vsock() { use libc; - use nix::Error; use nix::errno::Errno; use nix::sys::socket::{AddressFamily, socket, bind, connect, listen, SockAddr, SockType, SockFlag}; @@ -1516,7 +1512,7 @@ pub fn test_vsock() { // VMADDR_CID_HYPERVISOR is reserved, so we expect an EADDRNOTAVAIL error. let sockaddr = SockAddr::new_vsock(libc::VMADDR_CID_HYPERVISOR, port); assert_eq!(bind(s1, &sockaddr).err(), - Some(Error::Sys(Errno::EADDRNOTAVAIL))); + Some(Errno::EADDRNOTAVAIL)); let sockaddr = SockAddr::new_vsock(libc::VMADDR_CID_ANY, port); assert_eq!(bind(s1, &sockaddr), Ok(())); @@ -1649,3 +1645,86 @@ fn test_recvmmsg_timestampns() { // Close socket nix::unistd::close(in_socket).unwrap(); } + +// Disable the test on emulated platforms because it fails in Cirrus-CI. Lack of QEMU +// support is suspected. +#[cfg_attr(not(any(target_arch = "x86_64")), ignore)] +#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] +#[test] +fn test_recvmsg_rxq_ovfl() { + use nix::Error; + use nix::sys::socket::*; + use nix::sys::uio::IoVec; + use nix::sys::socket::sockopt::{RxqOvfl, RcvBuf}; + + let message = [0u8; 2048]; + let bufsize = message.len() * 2; + + let in_socket = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None).unwrap(); + let out_socket = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None).unwrap(); + + let localhost = InetAddr::new(IpAddr::new_v4(127, 0, 0, 1), 0); + bind(in_socket, &SockAddr::new_inet(localhost)).unwrap(); + + let address = getsockname(in_socket).unwrap(); + connect(out_socket, &address).unwrap(); + + // Set SO_RXQ_OVFL flag. + setsockopt(in_socket, RxqOvfl, &1).unwrap(); + + // Set the receiver buffer size to hold only 2 messages. + setsockopt(in_socket, RcvBuf, &bufsize).unwrap(); + + let mut drop_counter = 0; + + for _ in 0..2 { + let iov = [IoVec::from_slice(&message)]; + let flags = MsgFlags::empty(); + + // Send the 3 messages (the receiver buffer can only hold 2 messages) + // to create an overflow. + for _ in 0..3 { + let l = sendmsg(out_socket, &iov, &[], flags, Some(&address)).unwrap(); + assert_eq!(message.len(), l); + } + + // Receive the message and check the drop counter if any. + loop { + let mut buffer = vec![0u8; message.len()]; + let mut cmsgspace = nix::cmsg_space!(u32); + + let iov = [IoVec::from_mut_slice(&mut buffer)]; + + match recvmsg( + in_socket, + &iov, + Some(&mut cmsgspace), + MsgFlags::MSG_DONTWAIT) { + Ok(r) => { + drop_counter = match r.cmsgs().next() { + Some(ControlMessageOwned::RxqOvfl(drop_counter)) => drop_counter, + Some(_) => panic!("Unexpected control message"), + None => 0, + }; + }, + Err(Error::EAGAIN) => { break; }, + _ => { panic!("unknown recvmsg() error"); }, + } + } + } + + // One packet lost. + assert_eq!(drop_counter, 1); + + // Close sockets + nix::unistd::close(in_socket).unwrap(); + nix::unistd::close(out_socket).unwrap(); +} diff --git a/test/sys/test_termios.rs b/test/sys/test_termios.rs index 00aeb2f..63d6a51 100644 --- a/test/sys/test_termios.rs +++ b/test/sys/test_termios.rs @@ -1,7 +1,7 @@ use std::os::unix::prelude::*; use tempfile::tempfile; -use nix::{Error, fcntl}; +use nix::fcntl; use nix::errno::Errno; use nix::pty::openpty; use nix::sys::termios::{self, LocalFlags, OutputFlags, tcgetattr}; @@ -32,14 +32,14 @@ fn test_tcgetattr_pty() { fn test_tcgetattr_enotty() { let file = tempfile().unwrap(); assert_eq!(termios::tcgetattr(file.as_raw_fd()).err(), - Some(Error::Sys(Errno::ENOTTY))); + Some(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))); + Some(Errno::EBADF)); } // Test modifying output flags @@ -126,5 +126,5 @@ fn test_local_flags() { let read = read(pty.master, &mut buf).unwrap_err(); close(pty.master).unwrap(); close(pty.slave).unwrap(); - assert_eq!(read, Error::Sys(Errno::EAGAIN)); + assert_eq!(read, Errno::EAGAIN); } diff --git a/test/sys/test_wait.rs b/test/sys/test_wait.rs index f68b8b0..2d26fb8 100644 --- a/test/sys/test_wait.rs +++ b/test/sys/test_wait.rs @@ -1,4 +1,4 @@ -use nix::Error; +use nix::errno::Errno; use nix::unistd::*; use nix::unistd::ForkResult::*; use nix::sys::signal::*; @@ -41,7 +41,7 @@ fn test_waitstatus_from_raw() { let pid = Pid::from_raw(1); assert_eq!(WaitStatus::from_raw(pid, 0x0002), Ok(WaitStatus::Signaled(pid, Signal::SIGINT, false))); assert_eq!(WaitStatus::from_raw(pid, 0x0200), Ok(WaitStatus::Exited(pid, 2))); - assert_eq!(WaitStatus::from_raw(pid, 0x7f7f), Err(Error::invalid_argument())); + assert_eq!(WaitStatus::from_raw(pid, 0x7f7f), Err(Errno::EINVAL)); } #[test] diff --git a/test/test.rs b/test/test.rs index 5a5330b..94f8e22 100644 --- a/test/test.rs +++ b/test/test.rs @@ -13,6 +13,8 @@ mod test_fcntl; #[cfg(any(target_os = "android", target_os = "linux"))] mod test_kmod; +#[cfg(target_os = "freebsd")] +mod test_nmount; #[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "fushsia", diff --git a/test/test_dir.rs b/test/test_dir.rs index 4d7f5f7..0dc7308 100644 --- a/test/test_dir.rs +++ b/test/test_dir.rs @@ -51,5 +51,5 @@ fn rewind() { #[test] fn ebadf() { - assert_eq!(Dir::from_fd(-1).unwrap_err(), nix::Error::Sys(nix::errno::Errno::EBADF)); + assert_eq!(Dir::from_fd(-1).unwrap_err(), nix::Error::EBADF); } diff --git a/test/test_fcntl.rs b/test/test_fcntl.rs index 48d4662..ae6756e 100644 --- a/test/test_fcntl.rs +++ b/test/test_fcntl.rs @@ -1,11 +1,20 @@ #[cfg(not(target_os = "redox"))] -use nix::Error; -#[cfg(not(target_os = "redox"))] use nix::errno::*; #[cfg(not(target_os = "redox"))] use nix::fcntl::{open, OFlag, readlink}; #[cfg(not(target_os = "redox"))] use nix::fcntl::{openat, readlinkat, renameat}; +#[cfg(all( + target_os = "linux", + target_env = "gnu", + any( + target_arch = "x86_64", + target_arch = "x32", + target_arch = "powerpc", + target_arch = "s390x" + ) +))] +use nix::fcntl::{RenameFlags, renameat2}; #[cfg(not(target_os = "redox"))] use nix::sys::stat::Mode; #[cfg(not(target_os = "redox"))] @@ -55,12 +64,138 @@ fn test_renameat() { let new_dirfd = open(new_dir.path(), OFlag::empty(), Mode::empty()).unwrap(); renameat(Some(old_dirfd), "old", Some(new_dirfd), "new").unwrap(); assert_eq!(renameat(Some(old_dirfd), "old", Some(new_dirfd), "new").unwrap_err(), - Error::Sys(Errno::ENOENT)); + Errno::ENOENT); + close(old_dirfd).unwrap(); + close(new_dirfd).unwrap(); + assert!(new_dir.path().join("new").exists()); +} + +#[test] +#[cfg(all( + target_os = "linux", + target_env = "gnu", + any( + target_arch = "x86_64", + target_arch = "x32", + target_arch = "powerpc", + target_arch = "s390x" + ) +))] +fn test_renameat2_behaves_like_renameat_with_no_flags() { + let old_dir = tempfile::tempdir().unwrap(); + let old_dirfd = open(old_dir.path(), OFlag::empty(), Mode::empty()).unwrap(); + let old_path = old_dir.path().join("old"); + File::create(&old_path).unwrap(); + let new_dir = tempfile::tempdir().unwrap(); + let new_dirfd = open(new_dir.path(), OFlag::empty(), Mode::empty()).unwrap(); + renameat2( + Some(old_dirfd), + "old", + Some(new_dirfd), + "new", + RenameFlags::empty(), + ) + .unwrap(); + assert_eq!( + renameat2( + Some(old_dirfd), + "old", + Some(new_dirfd), + "new", + RenameFlags::empty() + ) + .unwrap_err(), + Errno::ENOENT + ); + close(old_dirfd).unwrap(); + close(new_dirfd).unwrap(); + assert!(new_dir.path().join("new").exists()); +} + +#[test] +#[cfg(all( + target_os = "linux", + target_env = "gnu", + any( + target_arch = "x86_64", + target_arch = "x32", + target_arch = "powerpc", + target_arch = "s390x" + ) +))] +fn test_renameat2_exchange() { + let old_dir = tempfile::tempdir().unwrap(); + let old_dirfd = open(old_dir.path(), OFlag::empty(), Mode::empty()).unwrap(); + let old_path = old_dir.path().join("old"); + { + let mut old_f = File::create(&old_path).unwrap(); + old_f.write(b"old").unwrap(); + } + let new_dir = tempfile::tempdir().unwrap(); + let new_dirfd = open(new_dir.path(), OFlag::empty(), Mode::empty()).unwrap(); + let new_path = new_dir.path().join("new"); + { + let mut new_f = File::create(&new_path).unwrap(); + new_f.write(b"new").unwrap(); + } + renameat2( + Some(old_dirfd), + "old", + Some(new_dirfd), + "new", + RenameFlags::RENAME_EXCHANGE, + ) + .unwrap(); + let mut buf = String::new(); + let mut new_f = File::open(&new_path).unwrap(); + new_f.read_to_string(&mut buf).unwrap(); + assert_eq!(buf, "old"); + buf = "".to_string(); + let mut old_f = File::open(&old_path).unwrap(); + old_f.read_to_string(&mut buf).unwrap(); + assert_eq!(buf, "new"); + close(old_dirfd).unwrap(); + close(new_dirfd).unwrap(); +} + +#[test] +#[cfg(all( + target_os = "linux", + target_env = "gnu", + any( + target_arch = "x86_64", + target_arch = "x32", + target_arch = "powerpc", + target_arch = "s390x" + ) +))] +fn test_renameat2_noreplace() { + let old_dir = tempfile::tempdir().unwrap(); + let old_dirfd = open(old_dir.path(), OFlag::empty(), Mode::empty()).unwrap(); + let old_path = old_dir.path().join("old"); + File::create(&old_path).unwrap(); + let new_dir = tempfile::tempdir().unwrap(); + let new_dirfd = open(new_dir.path(), OFlag::empty(), Mode::empty()).unwrap(); + let new_path = new_dir.path().join("new"); + File::create(&new_path).unwrap(); + assert_eq!( + renameat2( + Some(old_dirfd), + "old", + Some(new_dirfd), + "new", + RenameFlags::RENAME_NOREPLACE + ) + .unwrap_err(), + Errno::EEXIST + ); close(old_dirfd).unwrap(); close(new_dirfd).unwrap(); assert!(new_dir.path().join("new").exists()); + assert!(old_dir.path().join("old").exists()); } + #[test] #[cfg(not(target_os = "redox"))] fn test_readlink() { @@ -102,11 +237,15 @@ mod linux_android { /// resulting file is read and should contain the contents `bar`. /// The from_offset should be updated by the call to reflect /// the 3 bytes read (6). - /// - /// FIXME: This test is disabled for linux based builds, because Travis - /// Linux version is too old for `copy_file_range`. #[test] - #[ignore] + // QEMU does not support copy_file_range. Skip platforms that use QEMU in CI + #[cfg_attr(all(target_os = "linux", any( + target_arch = "aarch64", + target_arch = "arm", + target_arch = "mips", + target_arch = "mips64", + target_arch = "powerpc64" + )), ignore)] fn test_copy_file_range() { const CONTENTS: &[u8] = b"foobarbaz"; @@ -381,7 +520,7 @@ mod test_posix_fallocate { assert_eq!(tmp.read(&mut data).expect("read failure"), LEN); assert_eq!(&data[..], &[0u8; LEN][..]); } - Err(nix::Error::Sys(Errno::EINVAL)) => { + Err(Errno::EINVAL) => { // POSIX requires posix_fallocate to return EINVAL both for // invalid arguments (i.e. len < 0) and if the operation is not // supported by the file system. @@ -397,12 +536,8 @@ mod test_posix_fallocate { fn errno() { let (rd, _wr) = pipe().unwrap(); let err = posix_fallocate(rd as RawFd, 0, 100).unwrap_err(); - use nix::Error::Sys; match err { - Sys(Errno::EINVAL) - | Sys(Errno::ENODEV) - | Sys(Errno::ESPIPE) - | Sys(Errno::EBADF) => (), + Errno::EINVAL | Errno::ENODEV | Errno::ESPIPE | Errno::EBADF => (), errno => panic!( "unexpected errno {}", diff --git a/test/test_kmod/mod.rs b/test/test_kmod/mod.rs index fb7260b..7626330 100644 --- a/test/test_kmod/mod.rs +++ b/test/test_kmod/mod.rs @@ -130,7 +130,7 @@ fn test_finit_module_invalid() { let f = File::open(kmod_path).expect("unable to open kernel module"); let result = finit_module(&f, &CString::new("").unwrap(), ModuleInitFlags::empty()); - assert_eq!(result.unwrap_err(), Error::Sys(Errno::EINVAL)); + assert_eq!(result.unwrap_err(), Error::from(Errno::EINVAL)); } #[test] @@ -147,7 +147,7 @@ fn test_finit_module_twice_and_delete_module() { let result = finit_module(&f, &CString::new("").unwrap(), ModuleInitFlags::empty()); - assert_eq!(result.unwrap_err(), Error::Sys(Errno::EEXIST)); + assert_eq!(result.unwrap_err(), Error::from(Errno::EEXIST)); delete_module( &CString::new(kmod_name).unwrap(), @@ -163,5 +163,5 @@ fn test_delete_module_not_loaded() { let result = delete_module(&CString::new("hello").unwrap(), DeleteModuleFlags::empty()); - assert_eq!(result.unwrap_err(), Error::Sys(Errno::ENOENT)); + assert_eq!(result.unwrap_err(), Error::from(Errno::ENOENT)); } diff --git a/test/test_mq.rs b/test/test_mq.rs index 1667a35..d082692 100644 --- a/test/test_mq.rs +++ b/test/test_mq.rs @@ -1,8 +1,7 @@ use std::ffi::CString; use std::str; -use nix::errno::Errno::*; -use nix::Error::Sys; +use nix::errno::Errno; use nix::mqueue::{mq_open, mq_close, mq_send, mq_receive, mq_attr_member_t}; use nix::mqueue::{MqAttr, MQ_OFlag}; use nix::sys::stat::Mode; @@ -16,7 +15,7 @@ fn test_mq_send_and_receive() { let oflag0 = MQ_OFlag::O_CREAT | MQ_OFlag::O_WRONLY; let mode = Mode::S_IWUSR | Mode::S_IRUSR | Mode::S_IRGRP | Mode::S_IROTH; let r0 = mq_open(mq_name, oflag0, mode, Some(&attr)); - if let Err(Sys(ENOSYS)) = r0 { + if let Err(Errno::ENOSYS) = r0 { println!("message queues not supported or module not loaded?"); return; }; @@ -47,7 +46,7 @@ fn test_mq_getattr() { let oflag = MQ_OFlag::O_CREAT | MQ_OFlag::O_WRONLY; let mode = Mode::S_IWUSR | Mode::S_IRUSR | Mode::S_IRGRP | Mode::S_IROTH; let r = mq_open(mq_name, oflag, mode, Some(&initial_attr)); - if let Err(Sys(ENOSYS)) = r { + if let Err(Errno::ENOSYS) = r { println!("message queues not supported or module not loaded?"); return; }; @@ -70,7 +69,7 @@ fn test_mq_setattr() { let oflag = MQ_OFlag::O_CREAT | MQ_OFlag::O_WRONLY; let mode = Mode::S_IWUSR | Mode::S_IRUSR | Mode::S_IRGRP | Mode::S_IROTH; let r = mq_open(mq_name, oflag, mode, Some(&initial_attr)); - if let Err(Sys(ENOSYS)) = r { + if let Err(Errno::ENOSYS) = r { println!("message queues not supported or module not loaded?"); return; }; @@ -107,7 +106,7 @@ fn test_mq_set_nonblocking() { let oflag = MQ_OFlag::O_CREAT | MQ_OFlag::O_WRONLY; let mode = Mode::S_IWUSR | Mode::S_IRUSR | Mode::S_IRGRP | Mode::S_IROTH; let r = mq_open(mq_name, oflag, mode, Some(&initial_attr)); - if let Err(Sys(ENOSYS)) = r { + if let Err(Errno::ENOSYS) = r { println!("message queues not supported or module not loaded?"); return; }; @@ -132,7 +131,7 @@ fn test_mq_unlink() { let oflag = MQ_OFlag::O_CREAT | MQ_OFlag::O_WRONLY; let mode = Mode::S_IWUSR | Mode::S_IRUSR | Mode::S_IRGRP | Mode::S_IROTH; let r = mq_open(mq_name_opened, oflag, mode, Some(&initial_attr)); - if let Err(Sys(ENOSYS)) = r { + if let Err(Errno::ENOSYS) = r { println!("message queues not supported or module not loaded?"); return; }; @@ -142,9 +141,9 @@ fn test_mq_unlink() { assert_eq!(res_unlink, Ok(()) ); let res_unlink_not_opened = mq_unlink(mq_name_not_opened); - assert_eq!(res_unlink_not_opened, Err(Sys(ENOENT)) ); + assert_eq!(res_unlink_not_opened, Err(Errno::ENOENT) ); mq_close(mqd).unwrap(); let res_unlink_after_close = mq_unlink(mq_name_opened); - assert_eq!(res_unlink_after_close, Err(Sys(ENOENT)) ); + assert_eq!(res_unlink_after_close, Err(Errno::ENOENT) ); } diff --git a/test/test_nmount.rs b/test/test_nmount.rs new file mode 100644 index 0000000..4c74ecf --- /dev/null +++ b/test/test_nmount.rs @@ -0,0 +1,51 @@ +use crate::*; +use nix::{ + errno::Errno, + mount::{MntFlags, Nmount, unmount} +}; +use std::{ + ffi::CString, + fs::File, + path::Path +}; +use tempfile::tempdir; + +#[test] +fn ok() { + require_mount!("nullfs"); + + let mountpoint = tempdir().unwrap(); + let target = tempdir().unwrap(); + let _sentry = File::create(target.path().join("sentry")).unwrap(); + + let fstype = CString::new("fstype").unwrap(); + let nullfs = CString::new("nullfs").unwrap(); + Nmount::new() + .str_opt(&fstype, &nullfs) + .str_opt_owned("fspath", mountpoint.path().to_str().unwrap()) + .str_opt_owned("target", target.path().to_str().unwrap()) + .nmount(MntFlags::empty()).unwrap(); + + // Now check that the sentry is visible through the mountpoint + let exists = Path::exists(&mountpoint.path().join("sentry")); + + // Cleanup the mountpoint before asserting + unmount(mountpoint.path(), MntFlags::empty()).unwrap(); + + assert!(exists); +} + +#[test] +fn bad_fstype() { + let mountpoint = tempdir().unwrap(); + let target = tempdir().unwrap(); + let _sentry = File::create(target.path().join("sentry")).unwrap(); + + let e = Nmount::new() + .str_opt_owned("fspath", mountpoint.path().to_str().unwrap()) + .str_opt_owned("target", target.path().to_str().unwrap()) + .nmount(MntFlags::empty()).unwrap_err(); + + assert_eq!(e.error(), Errno::EINVAL); + assert_eq!(e.errmsg(), Some("Invalid fstype")); +} diff --git a/test/test_poll.rs b/test/test_poll.rs index acfaad8..0395512 100644 --- a/test/test_poll.rs +++ b/test/test_poll.rs @@ -1,5 +1,4 @@ use nix::{ - Error, errno::Errno, poll::{PollFlags, poll, PollFd}, unistd::{write, pipe} @@ -10,7 +9,7 @@ macro_rules! loop_while_eintr { loop { match $poll_expr { Ok(nfds) => break nfds, - Err(Error::Sys(Errno::EINTR)) => (), + Err(Errno::EINTR) => (), Err(e) => panic!("{}", e) } } diff --git a/test/test_ptymaster_drop.rs b/test/test_ptymaster_drop.rs index ff939b9..a68f81e 100644 --- a/test/test_ptymaster_drop.rs +++ b/test/test_ptymaster_drop.rs @@ -12,10 +12,6 @@ mod t { /// 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(OFlag::O_RDWR).unwrap(); close(m.as_raw_fd()).unwrap(); diff --git a/test/test_stat.rs b/test/test_stat.rs index 27fcee5..424371f 100644 --- a/test/test_stat.rs +++ b/test/test_stat.rs @@ -15,7 +15,7 @@ use libc::{S_IFMT, S_IFLNK}; use libc::mode_t; #[cfg(not(target_os = "redox"))] -use nix::{fcntl, Error}; +use nix::fcntl; #[cfg(not(target_os = "redox"))] use nix::errno::Errno; #[cfg(not(target_os = "redox"))] @@ -304,5 +304,5 @@ fn test_mkdirat_fail() { let dirfd = fcntl::open(&tempdir.path().join(not_dir_filename), fcntl::OFlag::O_CREAT, stat::Mode::empty()).unwrap(); let result = mkdirat(dirfd, filename, Mode::S_IRWXU).unwrap_err(); - assert_eq!(result, Error::Sys(Errno::ENOTDIR)); + assert_eq!(result, Errno::ENOTDIR); } diff --git a/test/test_unistd.rs b/test/test_unistd.rs index 94d2d1b..b95f154 100644 --- a/test/test_unistd.rs +++ b/test/test_unistd.rs @@ -1,6 +1,6 @@ #[cfg(not(target_os = "redox"))] use nix::fcntl::{self, open, readlink}; -use nix::fcntl::{fcntl, FcntlArg, FdFlag, OFlag}; +use nix::fcntl::OFlag; use nix::unistd::*; use nix::unistd::ForkResult::*; #[cfg(not(target_os = "redox"))] @@ -10,17 +10,15 @@ use nix::sys::stat::{self, Mode, SFlag}; #[cfg(not(any(target_os = "redox", target_os = "fuchsia")))] use nix::pty::{posix_openpt, grantpt, unlockpt, ptsname}; use nix::errno::Errno; -#[cfg(not(target_os = "redox"))] -use nix::Error; use std::{env, iter}; -#[cfg(not(target_os = "redox"))] +#[cfg(not(any(target_os = "fuchsia", target_os = "redox")))] use std::ffi::CString; #[cfg(not(target_os = "redox"))] use std::fs::DirBuilder; use std::fs::{self, File}; use std::io::Write; use std::os::unix::prelude::*; -#[cfg(not(target_os = "redox"))] +#[cfg(not(any(target_os = "fuchsia", target_os = "redox")))] use std::path::Path; use tempfile::{tempdir, tempfile}; use libc::{_exit, mode_t, off_t}; @@ -135,6 +133,8 @@ fn test_mkfifoat_none() { target_os = "macos", target_os = "ios", target_os = "android", target_os = "redox")))] fn test_mkfifoat() { + use nix::fcntl; + let tempdir = tempdir().unwrap(); let dirfd = open(tempdir.path(), OFlag::empty(), Mode::empty()).unwrap(); let mkfifoat_name = "mkfifoat_name"; @@ -258,7 +258,7 @@ fn test_initgroups() { setgroups(&old_groups).unwrap(); } -#[cfg(not(target_os = "redox"))] +#[cfg(not(any(target_os = "fuchsia", target_os = "redox")))] macro_rules! execve_test_factory( ($test_name:ident, $syscall:ident, $exe: expr $(, $pathname:expr, $flags:expr)*) => ( @@ -669,6 +669,8 @@ fn test_pipe() { target_os = "solaris"))] #[test] fn test_pipe2() { + use nix::fcntl::{fcntl, FcntlArg, FdFlag}; + let (fd0, fd1) = pipe2(OFlag::O_CLOEXEC).unwrap(); let f0 = FdFlag::from_bits_truncate(fcntl(fd0, FcntlArg::F_GETFD).unwrap()); assert!(f0.contains(FdFlag::FD_CLOEXEC)); @@ -968,7 +970,7 @@ fn test_unlinkat_dir_noremovedir() { // Attempt unlink dir at relative path without proper flag let err_result = unlinkat(Some(dirfd), dirname, UnlinkatFlags::NoRemoveDir).unwrap_err(); - assert!(err_result == Error::Sys(Errno::EISDIR) || err_result == Error::Sys(Errno::EPERM)); + assert!(err_result == Errno::EISDIR || err_result == Errno::EPERM); } #[test] @@ -1011,7 +1013,7 @@ fn test_unlinkat_file() { fn test_access_not_existing() { let tempdir = tempdir().unwrap(); let dir = tempdir.path().join("does_not_exist.txt"); - assert_eq!(access(&dir, AccessFlags::F_OK).err().unwrap().as_errno().unwrap(), + assert_eq!(access(&dir, AccessFlags::F_OK).err().unwrap(), Errno::ENOENT); } @@ -1090,13 +1092,13 @@ fn test_ttyname() { fn test_ttyname_not_pty() { let fd = File::open("/dev/zero").unwrap(); assert!(fd.as_raw_fd() > 0); - assert_eq!(ttyname(fd.as_raw_fd()), Err(Error::Sys(Errno::ENOTTY))); + assert_eq!(ttyname(fd.as_raw_fd()), Err(Errno::ENOTTY)); } #[test] #[cfg(not(any(target_os = "redox", target_os = "fuchsia")))] fn test_ttyname_invalid_fd() { - assert_eq!(ttyname(-1), Err(Error::Sys(Errno::EBADF))); + assert_eq!(ttyname(-1), Err(Errno::EBADF)); } #[test] |