summaryrefslogtreecommitdiffstats
path: root/third_party/rust/nix/test
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/nix/test')
-rw-r--r--third_party/rust/nix/test/common/mod.rs149
-rw-r--r--third_party/rust/nix/test/sys/mod.rs60
-rw-r--r--third_party/rust/nix/test/sys/test_aio.rs626
-rw-r--r--third_party/rust/nix/test/sys/test_aio_drop.rs35
-rw-r--r--third_party/rust/nix/test/sys/test_epoll.rs24
-rw-r--r--third_party/rust/nix/test/sys/test_inotify.rs65
-rw-r--r--third_party/rust/nix/test/sys/test_ioctl.rs376
-rw-r--r--third_party/rust/nix/test/sys/test_mman.rs122
-rw-r--r--third_party/rust/nix/test/sys/test_pthread.rs22
-rw-r--r--third_party/rust/nix/test/sys/test_ptrace.rs275
-rw-r--r--third_party/rust/nix/test/sys/test_select.rs81
-rw-r--r--third_party/rust/nix/test/sys/test_signal.rs147
-rw-r--r--third_party/rust/nix/test/sys/test_signalfd.rs27
-rw-r--r--third_party/rust/nix/test/sys/test_socket.rs2628
-rw-r--r--third_party/rust/nix/test/sys/test_sockopt.rs431
-rw-r--r--third_party/rust/nix/test/sys/test_stat.rs29
-rw-r--r--third_party/rust/nix/test/sys/test_sysinfo.rs20
-rw-r--r--third_party/rust/nix/test/sys/test_termios.rs136
-rw-r--r--third_party/rust/nix/test/sys/test_timerfd.rs69
-rw-r--r--third_party/rust/nix/test/sys/test_uio.rs270
-rw-r--r--third_party/rust/nix/test/sys/test_wait.rs257
-rw-r--r--third_party/rust/nix/test/test.rs124
-rw-r--r--third_party/rust/nix/test/test_clearenv.rs9
-rw-r--r--third_party/rust/nix/test/test_dir.rs65
-rw-r--r--third_party/rust/nix/test/test_fcntl.rs565
-rw-r--r--third_party/rust/nix/test/test_kmod/hello_mod/Makefile7
-rw-r--r--third_party/rust/nix/test/test_kmod/hello_mod/hello.c26
-rw-r--r--third_party/rust/nix/test/test_kmod/mod.rs188
-rw-r--r--third_party/rust/nix/test/test_mount.rs271
-rw-r--r--third_party/rust/nix/test/test_mq.rs190
-rw-r--r--third_party/rust/nix/test/test_net.rs19
-rw-r--r--third_party/rust/nix/test/test_nix_path.rs1
-rw-r--r--third_party/rust/nix/test/test_nmount.rs49
-rw-r--r--third_party/rust/nix/test/test_poll.rs84
-rw-r--r--third_party/rust/nix/test/test_pty.rs313
-rw-r--r--third_party/rust/nix/test/test_ptymaster_drop.rs20
-rw-r--r--third_party/rust/nix/test/test_resource.rs34
-rw-r--r--third_party/rust/nix/test/test_sched.rs39
-rw-r--r--third_party/rust/nix/test/test_sendfile.rs208
-rw-r--r--third_party/rust/nix/test/test_stat.rs421
-rw-r--r--third_party/rust/nix/test/test_time.rs59
-rw-r--r--third_party/rust/nix/test/test_timer.rs102
-rw-r--r--third_party/rust/nix/test/test_unistd.rs1407
43 files changed, 10050 insertions, 0 deletions
diff --git a/third_party/rust/nix/test/common/mod.rs b/third_party/rust/nix/test/common/mod.rs
new file mode 100644
index 0000000000..bb056aab87
--- /dev/null
+++ b/third_party/rust/nix/test/common/mod.rs
@@ -0,0 +1,149 @@
+use cfg_if::cfg_if;
+
+#[macro_export]
+macro_rules! skip {
+ ($($reason: expr),+) => {
+ use ::std::io::{self, Write};
+
+ let stderr = io::stderr();
+ let mut handle = stderr.lock();
+ writeln!(handle, $($reason),+).unwrap();
+ return;
+ }
+}
+
+cfg_if! {
+ if #[cfg(any(target_os = "android", target_os = "linux"))] {
+ #[macro_export] macro_rules! require_capability {
+ ($name:expr, $capname:ident) => {
+ use ::caps::{Capability, CapSet, has_cap};
+
+ if !has_cap(None, CapSet::Effective, Capability::$capname)
+ .unwrap()
+ {
+ skip!("{} requires capability {}. Skipping test.", $name, Capability::$capname);
+ }
+ }
+ }
+ } else if #[cfg(not(target_os = "redox"))] {
+ #[macro_export] macro_rules! require_capability {
+ ($name:expr, $capname:ident) => {}
+ }
+ }
+}
+
+/// 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, Sysctl};
+ use nix::unistd::Uid;
+
+ let ctl = ::sysctl::Ctl::new("vfs.usermount").unwrap();
+ if !Uid::current().is_root() && CtlValue::Int(0) == ctl.value().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) => {
+ if std::env::var_os("CIRRUS_CI").is_some() {
+ skip!("{}", $reason);
+ }
+ };
+}
+
+#[cfg(target_os = "freebsd")]
+#[macro_export]
+macro_rules! skip_if_jailed {
+ ($name:expr) => {
+ use ::sysctl::{CtlValue, Sysctl};
+
+ let ctl = ::sysctl::Ctl::new("security.jail.jailed").unwrap();
+ if let CtlValue::Int(1) = ctl.value().unwrap() {
+ skip!("{} cannot run in a jail. Skipping test.", $name);
+ }
+ };
+}
+
+#[cfg(not(any(target_os = "redox", target_os = "fuchsia")))]
+#[macro_export]
+macro_rules! skip_if_not_root {
+ ($name:expr) => {
+ use nix::unistd::Uid;
+
+ if !Uid::current().is_root() {
+ skip!("{} requires root privileges. Skipping test.", $name);
+ }
+ };
+}
+
+cfg_if! {
+ if #[cfg(any(target_os = "android", target_os = "linux"))] {
+ #[macro_export] macro_rules! skip_if_seccomp {
+ ($name:expr) => {
+ if let Ok(s) = std::fs::read_to_string("/proc/self/status") {
+ for l in s.lines() {
+ let mut fields = l.split_whitespace();
+ if fields.next() == Some("Seccomp:") &&
+ fields.next() != Some("0")
+ {
+ skip!("{} cannot be run in Seccomp mode. Skipping test.",
+ stringify!($name));
+ }
+ }
+ }
+ }
+ }
+ } else if #[cfg(not(target_os = "redox"))] {
+ #[macro_export] macro_rules! skip_if_seccomp {
+ ($name:expr) => {}
+ }
+ }
+}
+
+cfg_if! {
+ if #[cfg(target_os = "linux")] {
+ #[macro_export] macro_rules! require_kernel_version {
+ ($name:expr, $version_requirement:expr) => {
+ use semver::{Version, VersionReq};
+
+ let version_requirement = VersionReq::parse($version_requirement)
+ .expect("Bad match_version provided");
+
+ let uname = nix::sys::utsname::uname().unwrap();
+ println!("{}", uname.sysname().to_str().unwrap());
+ println!("{}", uname.nodename().to_str().unwrap());
+ println!("{}", uname.release().to_str().unwrap());
+ println!("{}", uname.version().to_str().unwrap());
+ println!("{}", uname.machine().to_str().unwrap());
+
+ // Fix stuff that the semver parser can't handle
+ let fixed_release = &uname.release().to_str().unwrap().to_string()
+ // Fedora 33 reports version as 4.18.el8_2.x86_64 or
+ // 5.18.200-fc33.x86_64. Remove the underscore.
+ .replace("_", "-")
+ // Cirrus-CI reports version as 4.19.112+ . Remove the +
+ .replace("+", "");
+ let mut version = Version::parse(fixed_release).unwrap();
+
+ //Keep only numeric parts
+ version.pre = semver::Prerelease::EMPTY;
+ version.build = semver::BuildMetadata::EMPTY;
+
+ if !version_requirement.matches(&version) {
+ skip!("Skip {} because kernel version `{}` doesn't match the requirement `{}`",
+ stringify!($name), version, version_requirement);
+ }
+ }
+ }
+ }
+}
diff --git a/third_party/rust/nix/test/sys/mod.rs b/third_party/rust/nix/test/sys/mod.rs
new file mode 100644
index 0000000000..20312120a6
--- /dev/null
+++ b/third_party/rust/nix/test/sys/mod.rs
@@ -0,0 +1,60 @@
+mod test_signal;
+
+// NOTE: DragonFly lacks a kernel-level implementation of Posix AIO as of
+// this writing. There is an user-level implementation, but whether aio
+// works or not heavily depends on which pthread implementation is chosen
+// by the user at link time. For this reason we do not want to run aio test
+// cases on DragonFly.
+#[cfg(any(
+ target_os = "freebsd",
+ target_os = "ios",
+ all(target_os = "linux", not(target_env = "uclibc")),
+ target_os = "macos",
+ target_os = "netbsd"
+))]
+mod test_aio;
+#[cfg(not(any(
+ target_os = "redox",
+ target_os = "fuchsia",
+ target_os = "haiku"
+)))]
+mod test_ioctl;
+#[cfg(not(target_os = "redox"))]
+mod test_mman;
+#[cfg(not(target_os = "redox"))]
+mod test_select;
+#[cfg(target_os = "linux")]
+mod test_signalfd;
+#[cfg(not(any(target_os = "redox", target_os = "haiku")))]
+mod test_socket;
+#[cfg(not(any(target_os = "redox")))]
+mod test_sockopt;
+mod test_stat;
+#[cfg(any(target_os = "android", target_os = "linux"))]
+mod test_sysinfo;
+#[cfg(not(any(
+ target_os = "redox",
+ target_os = "fuchsia",
+ target_os = "haiku"
+)))]
+mod test_termios;
+mod test_uio;
+mod test_wait;
+
+#[cfg(any(target_os = "android", target_os = "linux"))]
+mod test_epoll;
+#[cfg(target_os = "linux")]
+mod test_inotify;
+mod test_pthread;
+#[cfg(any(
+ target_os = "android",
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "linux",
+ target_os = "macos",
+ target_os = "netbsd",
+ target_os = "openbsd"
+))]
+mod test_ptrace;
+#[cfg(any(target_os = "android", target_os = "linux"))]
+mod test_timerfd;
diff --git a/third_party/rust/nix/test/sys/test_aio.rs b/third_party/rust/nix/test/sys/test_aio.rs
new file mode 100644
index 0000000000..84086f80ce
--- /dev/null
+++ b/third_party/rust/nix/test/sys/test_aio.rs
@@ -0,0 +1,626 @@
+use std::{
+ io::{Read, Seek, Write},
+ ops::Deref,
+ os::unix::io::AsRawFd,
+ pin::Pin,
+ sync::atomic::{AtomicBool, Ordering},
+ thread, time,
+};
+
+use libc::c_int;
+use nix::{
+ errno::*,
+ sys::{
+ aio::*,
+ signal::{
+ sigaction, SaFlags, SigAction, SigHandler, SigSet, SigevNotify,
+ Signal,
+ },
+ time::{TimeSpec, TimeValLike},
+ },
+};
+use tempfile::tempfile;
+
+lazy_static! {
+ pub static ref SIGNALED: AtomicBool = AtomicBool::new(false);
+}
+
+extern "C" fn sigfunc(_: c_int) {
+ SIGNALED.store(true, Ordering::Relaxed);
+}
+
+// Helper that polls an AioCb for completion or error
+macro_rules! poll_aio {
+ ($aiocb: expr) => {
+ loop {
+ let err = $aiocb.as_mut().error();
+ if err != Err(Errno::EINPROGRESS) {
+ break err;
+ };
+ thread::sleep(time::Duration::from_millis(10));
+ }
+ };
+}
+
+mod aio_fsync {
+ use super::*;
+
+ #[test]
+ fn test_accessors() {
+ let aiocb = AioFsync::new(
+ 1001,
+ AioFsyncMode::O_SYNC,
+ 42,
+ SigevNotify::SigevSignal {
+ signal: Signal::SIGUSR2,
+ si_value: 99,
+ },
+ );
+ assert_eq!(1001, aiocb.fd());
+ assert_eq!(AioFsyncMode::O_SYNC, aiocb.mode());
+ assert_eq!(42, aiocb.priority());
+ let sev = aiocb.sigevent().sigevent();
+ assert_eq!(Signal::SIGUSR2 as i32, sev.sigev_signo);
+ assert_eq!(99, sev.sigev_value.sival_ptr as i64);
+ }
+
+ /// `AioFsync::submit` should not modify the `AioCb` object if
+ /// `libc::aio_fsync` returns an error
+ // Skip on Linux, because Linux's AIO implementation can't detect errors
+ // synchronously
+ #[test]
+ #[cfg(any(target_os = "freebsd", target_os = "macos"))]
+ fn error() {
+ use std::mem;
+
+ const INITIAL: &[u8] = b"abcdef123456";
+ // Create an invalid AioFsyncMode
+ let mode = unsafe { mem::transmute(666) };
+ let mut f = tempfile().unwrap();
+ f.write_all(INITIAL).unwrap();
+ let mut aiof = Box::pin(AioFsync::new(
+ f.as_raw_fd(),
+ mode,
+ 0,
+ SigevNotify::SigevNone,
+ ));
+ let err = aiof.as_mut().submit();
+ err.expect_err("assertion failed");
+ }
+
+ #[test]
+ #[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)]
+ fn ok() {
+ const INITIAL: &[u8] = b"abcdef123456";
+ let mut f = tempfile().unwrap();
+ f.write_all(INITIAL).unwrap();
+ let fd = f.as_raw_fd();
+ let mut aiof = Box::pin(AioFsync::new(
+ fd,
+ AioFsyncMode::O_SYNC,
+ 0,
+ SigevNotify::SigevNone,
+ ));
+ aiof.as_mut().submit().unwrap();
+ poll_aio!(&mut aiof).unwrap();
+ aiof.as_mut().aio_return().unwrap();
+ }
+}
+
+mod aio_read {
+ use super::*;
+
+ #[test]
+ fn test_accessors() {
+ let mut rbuf = vec![0; 4];
+ let aiocb = AioRead::new(
+ 1001,
+ 2, //offset
+ &mut rbuf,
+ 42, //priority
+ SigevNotify::SigevSignal {
+ signal: Signal::SIGUSR2,
+ si_value: 99,
+ },
+ );
+ assert_eq!(1001, aiocb.fd());
+ assert_eq!(4, aiocb.nbytes());
+ assert_eq!(2, aiocb.offset());
+ assert_eq!(42, aiocb.priority());
+ let sev = aiocb.sigevent().sigevent();
+ assert_eq!(Signal::SIGUSR2 as i32, sev.sigev_signo);
+ assert_eq!(99, sev.sigev_value.sival_ptr as i64);
+ }
+
+ // Tests AioWrite.cancel. We aren't trying to test the OS's implementation,
+ // only our bindings. So it's sufficient to check that cancel
+ // returned any AioCancelStat value.
+ #[test]
+ #[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)]
+ fn cancel() {
+ const INITIAL: &[u8] = b"abcdef123456";
+ let mut rbuf = vec![0; 4];
+ let mut f = tempfile().unwrap();
+ f.write_all(INITIAL).unwrap();
+ let fd = f.as_raw_fd();
+ let mut aior =
+ Box::pin(AioRead::new(fd, 2, &mut rbuf, 0, SigevNotify::SigevNone));
+ aior.as_mut().submit().unwrap();
+
+ aior.as_mut().cancel().unwrap();
+
+ // Wait for aiow to complete, but don't care whether it succeeded
+ let _ = poll_aio!(&mut aior);
+ let _ = aior.as_mut().aio_return();
+ }
+
+ /// `AioRead::submit` should not modify the `AioCb` object if
+ /// `libc::aio_read` returns an error
+ // Skip on Linux, because Linux's AIO implementation can't detect errors
+ // synchronously
+ #[test]
+ #[cfg(any(target_os = "freebsd", target_os = "macos"))]
+ fn error() {
+ const INITIAL: &[u8] = b"abcdef123456";
+ let mut rbuf = vec![0; 4];
+ let mut f = tempfile().unwrap();
+ f.write_all(INITIAL).unwrap();
+ let mut aior = Box::pin(AioRead::new(
+ f.as_raw_fd(),
+ -1, //an invalid offset
+ &mut rbuf,
+ 0, //priority
+ SigevNotify::SigevNone,
+ ));
+ aior.as_mut().submit().expect_err("assertion failed");
+ }
+
+ // Test a simple aio operation with no completion notification. We must
+ // poll for completion
+ #[test]
+ #[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)]
+ fn ok() {
+ const INITIAL: &[u8] = b"abcdef123456";
+ let mut rbuf = vec![0; 4];
+ const EXPECT: &[u8] = b"cdef";
+ let mut f = tempfile().unwrap();
+ f.write_all(INITIAL).unwrap();
+ {
+ let fd = f.as_raw_fd();
+ let mut aior = Box::pin(AioRead::new(
+ fd,
+ 2,
+ &mut rbuf,
+ 0,
+ SigevNotify::SigevNone,
+ ));
+ aior.as_mut().submit().unwrap();
+
+ let err = poll_aio!(&mut aior);
+ assert_eq!(err, Ok(()));
+ assert_eq!(aior.as_mut().aio_return().unwrap(), EXPECT.len());
+ }
+ assert_eq!(EXPECT, rbuf.deref().deref());
+ }
+
+ // Like ok, but allocates the structure on the stack.
+ #[test]
+ #[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)]
+ fn on_stack() {
+ const INITIAL: &[u8] = b"abcdef123456";
+ let mut rbuf = vec![0; 4];
+ const EXPECT: &[u8] = b"cdef";
+ let mut f = tempfile().unwrap();
+ f.write_all(INITIAL).unwrap();
+ {
+ let fd = f.as_raw_fd();
+ let mut aior =
+ AioRead::new(fd, 2, &mut rbuf, 0, SigevNotify::SigevNone);
+ let mut aior = unsafe { Pin::new_unchecked(&mut aior) };
+ aior.as_mut().submit().unwrap();
+
+ let err = poll_aio!(&mut aior);
+ assert_eq!(err, Ok(()));
+ assert_eq!(aior.as_mut().aio_return().unwrap(), EXPECT.len());
+ }
+ assert_eq!(EXPECT, rbuf.deref().deref());
+ }
+}
+
+#[cfg(target_os = "freebsd")]
+#[cfg(fbsd14)]
+mod aio_readv {
+ use std::io::IoSliceMut;
+
+ use super::*;
+
+ #[test]
+ fn test_accessors() {
+ let mut rbuf0 = vec![0; 4];
+ let mut rbuf1 = vec![0; 8];
+ let mut rbufs =
+ [IoSliceMut::new(&mut rbuf0), IoSliceMut::new(&mut rbuf1)];
+ let aiocb = AioReadv::new(
+ 1001,
+ 2, //offset
+ &mut rbufs,
+ 42, //priority
+ SigevNotify::SigevSignal {
+ signal: Signal::SIGUSR2,
+ si_value: 99,
+ },
+ );
+ assert_eq!(1001, aiocb.fd());
+ assert_eq!(2, aiocb.iovlen());
+ assert_eq!(2, aiocb.offset());
+ assert_eq!(42, aiocb.priority());
+ let sev = aiocb.sigevent().sigevent();
+ assert_eq!(Signal::SIGUSR2 as i32, sev.sigev_signo);
+ assert_eq!(99, sev.sigev_value.sival_ptr as i64);
+ }
+
+ #[test]
+ #[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)]
+ fn ok() {
+ const INITIAL: &[u8] = b"abcdef123456";
+ let mut rbuf0 = vec![0; 4];
+ let mut rbuf1 = vec![0; 2];
+ let mut rbufs =
+ [IoSliceMut::new(&mut rbuf0), IoSliceMut::new(&mut rbuf1)];
+ const EXPECT0: &[u8] = b"cdef";
+ const EXPECT1: &[u8] = b"12";
+ let mut f = tempfile().unwrap();
+ f.write_all(INITIAL).unwrap();
+ {
+ let fd = f.as_raw_fd();
+ let mut aior = Box::pin(AioReadv::new(
+ fd,
+ 2,
+ &mut rbufs,
+ 0,
+ SigevNotify::SigevNone,
+ ));
+ aior.as_mut().submit().unwrap();
+
+ let err = poll_aio!(&mut aior);
+ assert_eq!(err, Ok(()));
+ assert_eq!(
+ aior.as_mut().aio_return().unwrap(),
+ EXPECT0.len() + EXPECT1.len()
+ );
+ }
+ assert_eq!(&EXPECT0, &rbuf0);
+ assert_eq!(&EXPECT1, &rbuf1);
+ }
+}
+
+mod aio_write {
+ use super::*;
+
+ #[test]
+ fn test_accessors() {
+ let wbuf = vec![0; 4];
+ let aiocb = AioWrite::new(
+ 1001,
+ 2, //offset
+ &wbuf,
+ 42, //priority
+ SigevNotify::SigevSignal {
+ signal: Signal::SIGUSR2,
+ si_value: 99,
+ },
+ );
+ assert_eq!(1001, aiocb.fd());
+ assert_eq!(4, aiocb.nbytes());
+ assert_eq!(2, aiocb.offset());
+ assert_eq!(42, aiocb.priority());
+ let sev = aiocb.sigevent().sigevent();
+ assert_eq!(Signal::SIGUSR2 as i32, sev.sigev_signo);
+ assert_eq!(99, sev.sigev_value.sival_ptr as i64);
+ }
+
+ // Tests AioWrite.cancel. We aren't trying to test the OS's implementation,
+ // only our bindings. So it's sufficient to check that cancel
+ // returned any AioCancelStat value.
+ #[test]
+ #[cfg_attr(target_env = "musl", ignore)]
+ fn cancel() {
+ let wbuf: &[u8] = b"CDEF";
+
+ let f = tempfile().unwrap();
+ let mut aiow = Box::pin(AioWrite::new(
+ f.as_raw_fd(),
+ 0,
+ wbuf,
+ 0,
+ SigevNotify::SigevNone,
+ ));
+ aiow.as_mut().submit().unwrap();
+ let err = aiow.as_mut().error();
+ assert!(err == Ok(()) || err == Err(Errno::EINPROGRESS));
+
+ aiow.as_mut().cancel().unwrap();
+
+ // Wait for aiow to complete, but don't care whether it succeeded
+ let _ = poll_aio!(&mut aiow);
+ let _ = aiow.as_mut().aio_return();
+ }
+
+ // Test a simple aio operation with no completion notification. We must
+ // poll for completion.
+ #[test]
+ #[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)]
+ fn ok() {
+ const INITIAL: &[u8] = b"abcdef123456";
+ let wbuf = "CDEF".to_string().into_bytes();
+ let mut rbuf = Vec::new();
+ const EXPECT: &[u8] = b"abCDEF123456";
+
+ let mut f = tempfile().unwrap();
+ f.write_all(INITIAL).unwrap();
+ let mut aiow = Box::pin(AioWrite::new(
+ f.as_raw_fd(),
+ 2,
+ &wbuf,
+ 0,
+ SigevNotify::SigevNone,
+ ));
+ aiow.as_mut().submit().unwrap();
+
+ let err = poll_aio!(&mut aiow);
+ assert_eq!(err, Ok(()));
+ assert_eq!(aiow.as_mut().aio_return().unwrap(), wbuf.len());
+
+ f.rewind().unwrap();
+ let len = f.read_to_end(&mut rbuf).unwrap();
+ assert_eq!(len, EXPECT.len());
+ assert_eq!(rbuf, EXPECT);
+ }
+
+ // Like ok, but allocates the structure on the stack.
+ #[test]
+ #[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)]
+ fn on_stack() {
+ const INITIAL: &[u8] = b"abcdef123456";
+ let wbuf = "CDEF".to_string().into_bytes();
+ let mut rbuf = Vec::new();
+ const EXPECT: &[u8] = b"abCDEF123456";
+
+ let mut f = tempfile().unwrap();
+ f.write_all(INITIAL).unwrap();
+ let mut aiow = AioWrite::new(
+ f.as_raw_fd(),
+ 2, //offset
+ &wbuf,
+ 0, //priority
+ SigevNotify::SigevNone,
+ );
+ let mut aiow = unsafe { Pin::new_unchecked(&mut aiow) };
+ aiow.as_mut().submit().unwrap();
+
+ let err = poll_aio!(&mut aiow);
+ assert_eq!(err, Ok(()));
+ assert_eq!(aiow.as_mut().aio_return().unwrap(), wbuf.len());
+
+ f.rewind().unwrap();
+ let len = f.read_to_end(&mut rbuf).unwrap();
+ assert_eq!(len, EXPECT.len());
+ assert_eq!(rbuf, EXPECT);
+ }
+
+ /// `AioWrite::write` should not modify the `AioCb` object if
+ /// `libc::aio_write` returns an error.
+ // Skip on Linux, because Linux's AIO implementation can't detect errors
+ // synchronously
+ #[test]
+ #[cfg(any(target_os = "freebsd", target_os = "macos"))]
+ fn error() {
+ let wbuf = "CDEF".to_string().into_bytes();
+ let mut aiow = Box::pin(AioWrite::new(
+ 666, // An invalid file descriptor
+ 0, //offset
+ &wbuf,
+ 0, //priority
+ SigevNotify::SigevNone,
+ ));
+ aiow.as_mut().submit().expect_err("assertion failed");
+ // Dropping the AioWrite at this point should not panic
+ }
+}
+
+#[cfg(target_os = "freebsd")]
+#[cfg(fbsd14)]
+mod aio_writev {
+ use std::io::IoSlice;
+
+ use super::*;
+
+ #[test]
+ fn test_accessors() {
+ let wbuf0 = vec![0; 4];
+ let wbuf1 = vec![0; 8];
+ let wbufs = [IoSlice::new(&wbuf0), IoSlice::new(&wbuf1)];
+ let aiocb = AioWritev::new(
+ 1001,
+ 2, //offset
+ &wbufs,
+ 42, //priority
+ SigevNotify::SigevSignal {
+ signal: Signal::SIGUSR2,
+ si_value: 99,
+ },
+ );
+ assert_eq!(1001, aiocb.fd());
+ assert_eq!(2, aiocb.iovlen());
+ assert_eq!(2, aiocb.offset());
+ assert_eq!(42, aiocb.priority());
+ let sev = aiocb.sigevent().sigevent();
+ assert_eq!(Signal::SIGUSR2 as i32, sev.sigev_signo);
+ assert_eq!(99, sev.sigev_value.sival_ptr as i64);
+ }
+
+ // Test a simple aio operation with no completion notification. We must
+ // poll for completion.
+ #[test]
+ #[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)]
+ fn ok() {
+ const INITIAL: &[u8] = b"abcdef123456";
+ let wbuf0 = b"BC";
+ let wbuf1 = b"DEF";
+ let wbufs = [IoSlice::new(wbuf0), IoSlice::new(wbuf1)];
+ let wlen = wbuf0.len() + wbuf1.len();
+ let mut rbuf = Vec::new();
+ const EXPECT: &[u8] = b"aBCDEF123456";
+
+ let mut f = tempfile().unwrap();
+ f.write_all(INITIAL).unwrap();
+ let mut aiow = Box::pin(AioWritev::new(
+ f.as_raw_fd(),
+ 1,
+ &wbufs,
+ 0,
+ SigevNotify::SigevNone,
+ ));
+ aiow.as_mut().submit().unwrap();
+
+ let err = poll_aio!(&mut aiow);
+ assert_eq!(err, Ok(()));
+ assert_eq!(aiow.as_mut().aio_return().unwrap(), wlen);
+
+ f.rewind().unwrap();
+ let len = f.read_to_end(&mut rbuf).unwrap();
+ assert_eq!(len, EXPECT.len());
+ assert_eq!(rbuf, EXPECT);
+ }
+}
+
+// Test an aio operation with completion delivered by a signal
+#[test]
+#[cfg_attr(
+ any(
+ all(target_env = "musl", target_arch = "x86_64"),
+ target_arch = "mips",
+ target_arch = "mips64"
+ ),
+ ignore
+)]
+fn sigev_signal() {
+ let _m = crate::SIGNAL_MTX.lock();
+ let sa = SigAction::new(
+ SigHandler::Handler(sigfunc),
+ SaFlags::SA_RESETHAND,
+ SigSet::empty(),
+ );
+ SIGNALED.store(false, Ordering::Relaxed);
+ unsafe { sigaction(Signal::SIGUSR2, &sa) }.unwrap();
+
+ const INITIAL: &[u8] = b"abcdef123456";
+ const WBUF: &[u8] = b"CDEF";
+ let mut rbuf = Vec::new();
+ const EXPECT: &[u8] = b"abCDEF123456";
+
+ let mut f = tempfile().unwrap();
+ f.write_all(INITIAL).unwrap();
+ let mut aiow = Box::pin(AioWrite::new(
+ f.as_raw_fd(),
+ 2, //offset
+ WBUF,
+ 0, //priority
+ SigevNotify::SigevSignal {
+ signal: Signal::SIGUSR2,
+ si_value: 0, //TODO: validate in sigfunc
+ },
+ ));
+ aiow.as_mut().submit().unwrap();
+ while !SIGNALED.load(Ordering::Relaxed) {
+ thread::sleep(time::Duration::from_millis(10));
+ }
+
+ assert_eq!(aiow.as_mut().aio_return().unwrap(), WBUF.len());
+ f.rewind().unwrap();
+ let len = f.read_to_end(&mut rbuf).unwrap();
+ assert_eq!(len, EXPECT.len());
+ assert_eq!(rbuf, EXPECT);
+}
+
+// Tests using aio_cancel_all for all outstanding IOs.
+#[test]
+#[cfg_attr(target_env = "musl", ignore)]
+fn test_aio_cancel_all() {
+ let wbuf: &[u8] = b"CDEF";
+
+ let f = tempfile().unwrap();
+ let mut aiocb = Box::pin(AioWrite::new(
+ f.as_raw_fd(),
+ 0, //offset
+ wbuf,
+ 0, //priority
+ SigevNotify::SigevNone,
+ ));
+ aiocb.as_mut().submit().unwrap();
+ let err = aiocb.as_mut().error();
+ assert!(err == Ok(()) || err == Err(Errno::EINPROGRESS));
+
+ aio_cancel_all(f.as_raw_fd()).unwrap();
+
+ // Wait for aiocb to complete, but don't care whether it succeeded
+ let _ = poll_aio!(&mut aiocb);
+ let _ = aiocb.as_mut().aio_return();
+}
+
+#[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
+// https://github.com/nix-rust/nix/issues/1361
+#[cfg_attr(target_os = "macos", ignore)]
+fn test_aio_suspend() {
+ const INITIAL: &[u8] = b"abcdef123456";
+ const WBUF: &[u8] = b"CDEFG";
+ let timeout = TimeSpec::seconds(10);
+ let mut rbuf = vec![0; 4];
+ let rlen = rbuf.len();
+ let mut f = tempfile().unwrap();
+ f.write_all(INITIAL).unwrap();
+
+ let mut wcb = Box::pin(AioWrite::new(
+ f.as_raw_fd(),
+ 2, //offset
+ WBUF,
+ 0, //priority
+ SigevNotify::SigevNone,
+ ));
+
+ let mut rcb = Box::pin(AioRead::new(
+ f.as_raw_fd(),
+ 8, //offset
+ &mut rbuf,
+ 0, //priority
+ SigevNotify::SigevNone,
+ ));
+ wcb.as_mut().submit().unwrap();
+ rcb.as_mut().submit().unwrap();
+ loop {
+ {
+ let cbbuf = [
+ &*wcb as &dyn AsRef<libc::aiocb>,
+ &*rcb as &dyn AsRef<libc::aiocb>,
+ ];
+ let r = aio_suspend(&cbbuf[..], Some(timeout));
+ match r {
+ Err(Errno::EINTR) => continue,
+ Err(e) => panic!("aio_suspend returned {:?}", e),
+ Ok(_) => (),
+ };
+ }
+ if rcb.as_mut().error() != Err(Errno::EINPROGRESS)
+ && wcb.as_mut().error() != Err(Errno::EINPROGRESS)
+ {
+ break;
+ }
+ }
+
+ assert_eq!(wcb.as_mut().aio_return().unwrap(), WBUF.len());
+ assert_eq!(rcb.as_mut().aio_return().unwrap(), rlen);
+}
diff --git a/third_party/rust/nix/test/sys/test_aio_drop.rs b/third_party/rust/nix/test/sys/test_aio_drop.rs
new file mode 100644
index 0000000000..bbe6623fd7
--- /dev/null
+++ b/third_party/rust/nix/test/sys/test_aio_drop.rs
@@ -0,0 +1,35 @@
+// Test dropping an AioCb that hasn't yet finished.
+// This must happen in its own process, because on OSX this test seems to hose
+// the AIO subsystem and causes subsequent tests to fail
+#[test]
+#[should_panic(expected = "Dropped an in-progress AioCb")]
+#[cfg(all(
+ not(target_env = "musl"),
+ not(target_env = "uclibc"),
+ any(
+ target_os = "linux",
+ target_os = "ios",
+ target_os = "macos",
+ target_os = "freebsd",
+ target_os = "netbsd"
+ )
+))]
+fn test_drop() {
+ use nix::sys::aio::*;
+ use nix::sys::signal::*;
+ use std::os::unix::io::AsRawFd;
+ use tempfile::tempfile;
+
+ const WBUF: &[u8] = b"CDEF";
+
+ let f = tempfile().unwrap();
+ f.set_len(6).unwrap();
+ let mut aiocb = Box::pin(AioWrite::new(
+ f.as_raw_fd(),
+ 2, //offset
+ WBUF,
+ 0, //priority
+ SigevNotify::SigevNone,
+ ));
+ aiocb.as_mut().submit().unwrap();
+}
diff --git a/third_party/rust/nix/test/sys/test_epoll.rs b/third_party/rust/nix/test/sys/test_epoll.rs
new file mode 100644
index 0000000000..915691595c
--- /dev/null
+++ b/third_party/rust/nix/test/sys/test_epoll.rs
@@ -0,0 +1,24 @@
+use nix::errno::Errno;
+use nix::sys::epoll::{epoll_create1, epoll_ctl};
+use nix::sys::epoll::{EpollCreateFlags, EpollEvent, EpollFlags, EpollOp};
+
+#[test]
+pub fn test_epoll_errno() {
+ let efd = epoll_create1(EpollCreateFlags::empty()).unwrap();
+ let result = epoll_ctl(efd, EpollOp::EpollCtlDel, 1, None);
+ result.expect_err("assertion failed");
+ assert_eq!(result.unwrap_err(), Errno::ENOENT);
+
+ let result = epoll_ctl(efd, EpollOp::EpollCtlAdd, 1, None);
+ result.expect_err("assertion failed");
+ assert_eq!(result.unwrap_err(), Errno::EINVAL);
+}
+
+#[test]
+pub fn test_epoll_ctl() {
+ let efd = epoll_create1(EpollCreateFlags::empty()).unwrap();
+ let mut event =
+ EpollEvent::new(EpollFlags::EPOLLIN | EpollFlags::EPOLLERR, 1);
+ epoll_ctl(efd, EpollOp::EpollCtlAdd, 1, &mut event).unwrap();
+ epoll_ctl(efd, EpollOp::EpollCtlDel, 1, None).unwrap();
+}
diff --git a/third_party/rust/nix/test/sys/test_inotify.rs b/third_party/rust/nix/test/sys/test_inotify.rs
new file mode 100644
index 0000000000..bb5851a903
--- /dev/null
+++ b/third_party/rust/nix/test/sys/test_inotify.rs
@@ -0,0 +1,65 @@
+use nix::errno::Errno;
+use nix::sys::inotify::{AddWatchFlags, InitFlags, Inotify};
+use std::ffi::OsString;
+use std::fs::{rename, File};
+
+#[test]
+pub fn test_inotify() {
+ let instance = Inotify::init(InitFlags::IN_NONBLOCK).unwrap();
+ let tempdir = tempfile::tempdir().unwrap();
+
+ instance
+ .add_watch(tempdir.path(), AddWatchFlags::IN_ALL_EVENTS)
+ .unwrap();
+
+ let events = instance.read_events();
+ assert_eq!(events.unwrap_err(), Errno::EAGAIN);
+
+ File::create(tempdir.path().join("test")).unwrap();
+
+ let events = instance.read_events().unwrap();
+ assert_eq!(events[0].name, Some(OsString::from("test")));
+}
+
+#[test]
+pub fn test_inotify_multi_events() {
+ let instance = Inotify::init(InitFlags::IN_NONBLOCK).unwrap();
+ let tempdir = tempfile::tempdir().unwrap();
+
+ instance
+ .add_watch(tempdir.path(), AddWatchFlags::IN_ALL_EVENTS)
+ .unwrap();
+
+ let events = instance.read_events();
+ assert_eq!(events.unwrap_err(), Errno::EAGAIN);
+
+ File::create(tempdir.path().join("test")).unwrap();
+ rename(tempdir.path().join("test"), tempdir.path().join("test2")).unwrap();
+
+ // Now there should be 5 events in queue:
+ // - IN_CREATE on test
+ // - IN_OPEN on test
+ // - IN_CLOSE_WRITE on test
+ // - IN_MOVED_FROM on test with a cookie
+ // - IN_MOVED_TO on test2 with the same cookie
+
+ let events = instance.read_events().unwrap();
+ assert_eq!(events.len(), 5);
+
+ assert_eq!(events[0].mask, AddWatchFlags::IN_CREATE);
+ assert_eq!(events[0].name, Some(OsString::from("test")));
+
+ assert_eq!(events[1].mask, AddWatchFlags::IN_OPEN);
+ assert_eq!(events[1].name, Some(OsString::from("test")));
+
+ assert_eq!(events[2].mask, AddWatchFlags::IN_CLOSE_WRITE);
+ assert_eq!(events[2].name, Some(OsString::from("test")));
+
+ assert_eq!(events[3].mask, AddWatchFlags::IN_MOVED_FROM);
+ assert_eq!(events[3].name, Some(OsString::from("test")));
+
+ assert_eq!(events[4].mask, AddWatchFlags::IN_MOVED_TO);
+ assert_eq!(events[4].name, Some(OsString::from("test2")));
+
+ assert_eq!(events[3].cookie, events[4].cookie);
+}
diff --git a/third_party/rust/nix/test/sys/test_ioctl.rs b/third_party/rust/nix/test/sys/test_ioctl.rs
new file mode 100644
index 0000000000..40f60cfdbc
--- /dev/null
+++ b/third_party/rust/nix/test/sys/test_ioctl.rs
@@ -0,0 +1,376 @@
+#![allow(dead_code)]
+
+// Simple tests to ensure macro generated fns compile
+ioctl_none_bad!(do_bad, 0x1234);
+ioctl_read_bad!(do_bad_read, 0x1234, u16);
+ioctl_write_int_bad!(do_bad_write_int, 0x1234);
+ioctl_write_ptr_bad!(do_bad_write_ptr, 0x1234, u8);
+ioctl_readwrite_bad!(do_bad_readwrite, 0x1234, u32);
+ioctl_none!(do_none, 0, 0);
+ioctl_read!(read_test, 0, 0, u32);
+ioctl_write_int!(write_ptr_int, 0, 0);
+ioctl_write_ptr!(write_ptr_u8, 0, 0, u8);
+ioctl_write_ptr!(write_ptr_u32, 0, 0, u32);
+ioctl_write_ptr!(write_ptr_u64, 0, 0, u64);
+ioctl_readwrite!(readwrite_test, 0, 0, u64);
+ioctl_read_buf!(readbuf_test, 0, 0, u32);
+const SPI_IOC_MAGIC: u8 = b'k';
+const SPI_IOC_MESSAGE: u8 = 0;
+ioctl_write_buf!(writebuf_test_consts, SPI_IOC_MAGIC, SPI_IOC_MESSAGE, u8);
+ioctl_write_buf!(writebuf_test_u8, 0, 0, u8);
+ioctl_write_buf!(writebuf_test_u32, 0, 0, u32);
+ioctl_write_buf!(writebuf_test_u64, 0, 0, u64);
+ioctl_readwrite_buf!(readwritebuf_test, 0, 0, u32);
+
+// See C code for source of values for op calculations (does NOT work for mips/powerpc):
+// https://gist.github.com/posborne/83ea6880770a1aef332e
+//
+// TODO: Need a way to compute these constants at test time. Using precomputed
+// values is fragile and needs to be maintained.
+
+#[cfg(any(target_os = "linux", target_os = "android"))]
+mod linux {
+ // The cast is not unnecessary on all platforms.
+ #[allow(clippy::unnecessary_cast)]
+ #[test]
+ fn test_op_none() {
+ if cfg!(any(
+ target_arch = "mips",
+ target_arch = "mips64",
+ target_arch = "powerpc",
+ target_arch = "powerpc64"
+ )) {
+ assert_eq!(request_code_none!(b'q', 10) as u32, 0x2000_710A);
+ assert_eq!(request_code_none!(b'a', 255) as u32, 0x2000_61FF);
+ } else {
+ assert_eq!(request_code_none!(b'q', 10) as u32, 0x0000_710A);
+ assert_eq!(request_code_none!(b'a', 255) as u32, 0x0000_61FF);
+ }
+ }
+
+ // The cast is not unnecessary on all platforms.
+ #[allow(clippy::unnecessary_cast)]
+ #[test]
+ fn test_op_write() {
+ if cfg!(any(
+ target_arch = "mips",
+ target_arch = "mips64",
+ target_arch = "powerpc",
+ target_arch = "powerpc64"
+ )) {
+ assert_eq!(request_code_write!(b'z', 10, 1) as u32, 0x8001_7A0A);
+ assert_eq!(request_code_write!(b'z', 10, 512) as u32, 0x8200_7A0A);
+ } else {
+ assert_eq!(request_code_write!(b'z', 10, 1) as u32, 0x4001_7A0A);
+ assert_eq!(request_code_write!(b'z', 10, 512) as u32, 0x4200_7A0A);
+ }
+ }
+
+ #[cfg(target_pointer_width = "64")]
+ #[test]
+ fn test_op_write_64() {
+ if cfg!(any(target_arch = "mips64", target_arch = "powerpc64")) {
+ assert_eq!(
+ request_code_write!(b'z', 10, 1u64 << 32) as u32,
+ 0x8000_7A0A
+ );
+ } else {
+ assert_eq!(
+ request_code_write!(b'z', 10, 1u64 << 32) as u32,
+ 0x4000_7A0A
+ );
+ }
+ }
+
+ // The cast is not unnecessary on all platforms.
+ #[allow(clippy::unnecessary_cast)]
+ #[test]
+ fn test_op_read() {
+ if cfg!(any(
+ target_arch = "mips",
+ target_arch = "mips64",
+ target_arch = "powerpc",
+ target_arch = "powerpc64"
+ )) {
+ assert_eq!(request_code_read!(b'z', 10, 1) as u32, 0x4001_7A0A);
+ assert_eq!(request_code_read!(b'z', 10, 512) as u32, 0x4200_7A0A);
+ } else {
+ assert_eq!(request_code_read!(b'z', 10, 1) as u32, 0x8001_7A0A);
+ assert_eq!(request_code_read!(b'z', 10, 512) as u32, 0x8200_7A0A);
+ }
+ }
+
+ #[cfg(target_pointer_width = "64")]
+ #[test]
+ fn test_op_read_64() {
+ if cfg!(any(target_arch = "mips64", target_arch = "powerpc64")) {
+ assert_eq!(
+ request_code_read!(b'z', 10, 1u64 << 32) as u32,
+ 0x4000_7A0A
+ );
+ } else {
+ assert_eq!(
+ request_code_read!(b'z', 10, 1u64 << 32) as u32,
+ 0x8000_7A0A
+ );
+ }
+ }
+
+ // The cast is not unnecessary on all platforms.
+ #[allow(clippy::unnecessary_cast)]
+ #[test]
+ fn test_op_read_write() {
+ assert_eq!(request_code_readwrite!(b'z', 10, 1) as u32, 0xC001_7A0A);
+ assert_eq!(request_code_readwrite!(b'z', 10, 512) as u32, 0xC200_7A0A);
+ }
+
+ #[cfg(target_pointer_width = "64")]
+ #[test]
+ fn test_op_read_write_64() {
+ assert_eq!(
+ request_code_readwrite!(b'z', 10, 1u64 << 32) as u32,
+ 0xC000_7A0A
+ );
+ }
+}
+
+#[cfg(any(
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "ios",
+ target_os = "macos",
+ target_os = "netbsd",
+ target_os = "openbsd"
+))]
+mod bsd {
+ #[test]
+ fn test_op_none() {
+ assert_eq!(request_code_none!(b'q', 10), 0x2000_710A);
+ assert_eq!(request_code_none!(b'a', 255), 0x2000_61FF);
+ }
+
+ #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))]
+ #[test]
+ fn test_op_write_int() {
+ assert_eq!(request_code_write_int!(b'v', 4), 0x2004_7604);
+ assert_eq!(request_code_write_int!(b'p', 2), 0x2004_7002);
+ }
+
+ #[test]
+ fn test_op_write() {
+ assert_eq!(request_code_write!(b'z', 10, 1), 0x8001_7A0A);
+ assert_eq!(request_code_write!(b'z', 10, 512), 0x8200_7A0A);
+ }
+
+ #[cfg(target_pointer_width = "64")]
+ #[test]
+ fn test_op_write_64() {
+ assert_eq!(request_code_write!(b'z', 10, 1u64 << 32), 0x8000_7A0A);
+ }
+
+ #[test]
+ fn test_op_read() {
+ assert_eq!(request_code_read!(b'z', 10, 1), 0x4001_7A0A);
+ assert_eq!(request_code_read!(b'z', 10, 512), 0x4200_7A0A);
+ }
+
+ #[cfg(target_pointer_width = "64")]
+ #[test]
+ fn test_op_read_64() {
+ assert_eq!(request_code_read!(b'z', 10, 1u64 << 32), 0x4000_7A0A);
+ }
+
+ #[test]
+ fn test_op_read_write() {
+ assert_eq!(request_code_readwrite!(b'z', 10, 1), 0xC001_7A0A);
+ assert_eq!(request_code_readwrite!(b'z', 10, 512), 0xC200_7A0A);
+ }
+
+ #[cfg(target_pointer_width = "64")]
+ #[test]
+ fn test_op_read_write_64() {
+ assert_eq!(request_code_readwrite!(b'z', 10, 1u64 << 32), 0xC000_7A0A);
+ }
+}
+
+#[cfg(any(target_os = "android", target_os = "linux"))]
+mod linux_ioctls {
+ use std::mem;
+ use std::os::unix::io::AsRawFd;
+
+ use libc::{termios, TCGETS, TCSBRK, TCSETS, TIOCNXCL};
+ use tempfile::tempfile;
+
+ 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(Errno::ENOTTY));
+ }
+
+ ioctl_read_bad!(tcgets, TCGETS, termios);
+ #[test]
+ fn test_ioctl_read_bad() {
+ let file = tempfile().unwrap();
+ let mut termios = unsafe { mem::zeroed() };
+ let res = unsafe { tcgets(file.as_raw_fd(), &mut termios) };
+ assert_eq!(res, Err(Errno::ENOTTY));
+ }
+
+ ioctl_write_int_bad!(tcsbrk, TCSBRK);
+ #[test]
+ fn test_ioctl_write_int_bad() {
+ let file = tempfile().unwrap();
+ let res = unsafe { tcsbrk(file.as_raw_fd(), 0) };
+ assert_eq!(res, Err(Errno::ENOTTY));
+ }
+
+ ioctl_write_ptr_bad!(tcsets, TCSETS, termios);
+ #[test]
+ fn test_ioctl_write_ptr_bad() {
+ let file = tempfile().unwrap();
+ let termios: termios = unsafe { mem::zeroed() };
+ let res = unsafe { tcsets(file.as_raw_fd(), &termios) };
+ assert_eq!(res, Err(Errno::ENOTTY));
+ }
+
+ // FIXME: Find a suitable example for `ioctl_readwrite_bad`
+
+ // From linux/videodev2.h
+ ioctl_none!(log_status, b'V', 70);
+ #[test]
+ fn test_ioctl_none() {
+ let file = tempfile().unwrap();
+ let res = unsafe { log_status(file.as_raw_fd()) };
+ assert!(res == Err(Errno::ENOTTY) || res == Err(Errno::ENOSYS));
+ }
+
+ #[repr(C)]
+ pub struct v4l2_audio {
+ index: u32,
+ name: [u8; 32],
+ capability: u32,
+ mode: u32,
+ reserved: [u32; 2],
+ }
+
+ // From linux/videodev2.h
+ ioctl_write_ptr!(s_audio, b'V', 34, v4l2_audio);
+ #[test]
+ fn test_ioctl_write_ptr() {
+ let file = tempfile().unwrap();
+ let data: v4l2_audio = unsafe { mem::zeroed() };
+ let res = unsafe { s_audio(file.as_raw_fd(), &data) };
+ assert!(res == Err(Errno::ENOTTY) || res == Err(Errno::ENOSYS));
+ }
+
+ // From linux/net/bluetooth/hci_sock.h
+ const HCI_IOC_MAGIC: u8 = b'H';
+ const HCI_IOC_HCIDEVUP: u8 = 201;
+ ioctl_write_int!(hcidevup, HCI_IOC_MAGIC, HCI_IOC_HCIDEVUP);
+ #[test]
+ fn test_ioctl_write_int() {
+ let file = tempfile().unwrap();
+ let res = unsafe { hcidevup(file.as_raw_fd(), 0) };
+ assert!(res == Err(Errno::ENOTTY) || res == Err(Errno::ENOSYS));
+ }
+
+ // From linux/videodev2.h
+ ioctl_read!(g_audio, b'V', 33, v4l2_audio);
+ #[test]
+ fn test_ioctl_read() {
+ 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(Errno::ENOTTY) || res == Err(Errno::ENOSYS));
+ }
+
+ // From linux/videodev2.h
+ ioctl_readwrite!(enum_audio, b'V', 65, v4l2_audio);
+ #[test]
+ fn test_ioctl_readwrite() {
+ 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(Errno::ENOTTY) || res == Err(Errno::ENOSYS));
+ }
+
+ // FIXME: Find a suitable example for `ioctl_read_buf`.
+
+ #[repr(C)]
+ pub struct spi_ioc_transfer {
+ tx_buf: u64,
+ rx_buf: u64,
+ len: u32,
+ speed_hz: u32,
+ delay_usecs: u16,
+ bits_per_word: u8,
+ cs_change: u8,
+ tx_nbits: u8,
+ rx_nbits: u8,
+ pad: u16,
+ }
+
+ // From linux/spi/spidev.h
+ ioctl_write_buf!(
+ spi_ioc_message,
+ super::SPI_IOC_MAGIC,
+ super::SPI_IOC_MESSAGE,
+ spi_ioc_transfer
+ );
+ #[test]
+ fn test_ioctl_write_buf() {
+ 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(Errno::ENOTTY) || res == Err(Errno::ENOSYS));
+ }
+
+ // FIXME: Find a suitable example for `ioctl_readwrite_buf`.
+}
+
+#[cfg(target_os = "freebsd")]
+mod freebsd_ioctls {
+ use std::mem;
+ use std::os::unix::io::AsRawFd;
+
+ use libc::termios;
+ use tempfile::tempfile;
+
+ use nix::errno::Errno;
+
+ // From sys/sys/ttycom.h
+ const TTY_IOC_MAGIC: u8 = b't';
+ const TTY_IOC_TYPE_NXCL: u8 = 14;
+ const TTY_IOC_TYPE_GETA: u8 = 19;
+ const TTY_IOC_TYPE_SETA: u8 = 20;
+
+ ioctl_none!(tiocnxcl, TTY_IOC_MAGIC, TTY_IOC_TYPE_NXCL);
+ #[test]
+ fn test_ioctl_none() {
+ let file = tempfile().unwrap();
+ let res = unsafe { tiocnxcl(file.as_raw_fd()) };
+ assert_eq!(res, Err(Errno::ENOTTY));
+ }
+
+ ioctl_read!(tiocgeta, TTY_IOC_MAGIC, TTY_IOC_TYPE_GETA, termios);
+ #[test]
+ fn test_ioctl_read() {
+ let file = tempfile().unwrap();
+ let mut termios = unsafe { mem::zeroed() };
+ let res = unsafe { tiocgeta(file.as_raw_fd(), &mut termios) };
+ assert_eq!(res, Err(Errno::ENOTTY));
+ }
+
+ ioctl_write_ptr!(tiocseta, TTY_IOC_MAGIC, TTY_IOC_TYPE_SETA, termios);
+ #[test]
+ fn test_ioctl_write_ptr() {
+ let file = tempfile().unwrap();
+ let termios: termios = unsafe { mem::zeroed() };
+ let res = unsafe { tiocseta(file.as_raw_fd(), &termios) };
+ assert_eq!(res, Err(Errno::ENOTTY));
+ }
+}
diff --git a/third_party/rust/nix/test/sys/test_mman.rs b/third_party/rust/nix/test/sys/test_mman.rs
new file mode 100644
index 0000000000..e748427bcd
--- /dev/null
+++ b/third_party/rust/nix/test/sys/test_mman.rs
@@ -0,0 +1,122 @@
+use nix::sys::mman::{mmap, MapFlags, ProtFlags};
+use std::num::NonZeroUsize;
+
+#[test]
+fn test_mmap_anonymous() {
+ unsafe {
+ let ptr = mmap(
+ None,
+ NonZeroUsize::new(1).unwrap(),
+ ProtFlags::PROT_READ | ProtFlags::PROT_WRITE,
+ MapFlags::MAP_PRIVATE | MapFlags::MAP_ANONYMOUS,
+ -1,
+ 0,
+ )
+ .unwrap() as *mut u8;
+ assert_eq!(*ptr, 0x00u8);
+ *ptr = 0xffu8;
+ assert_eq!(*ptr, 0xffu8);
+ }
+}
+
+#[test]
+#[cfg(any(target_os = "linux", target_os = "netbsd"))]
+fn test_mremap_grow() {
+ use nix::libc::{c_void, size_t};
+ use nix::sys::mman::{mremap, MRemapFlags};
+
+ const ONE_K: size_t = 1024;
+ let one_k_non_zero = NonZeroUsize::new(ONE_K).unwrap();
+
+ let slice: &mut [u8] = unsafe {
+ let mem = mmap(
+ None,
+ one_k_non_zero,
+ ProtFlags::PROT_READ | ProtFlags::PROT_WRITE,
+ MapFlags::MAP_ANONYMOUS | MapFlags::MAP_PRIVATE,
+ -1,
+ 0,
+ )
+ .unwrap();
+ std::slice::from_raw_parts_mut(mem as *mut u8, ONE_K)
+ };
+ assert_eq!(slice[ONE_K - 1], 0x00);
+ slice[ONE_K - 1] = 0xFF;
+ assert_eq!(slice[ONE_K - 1], 0xFF);
+
+ let slice: &mut [u8] = unsafe {
+ #[cfg(target_os = "linux")]
+ let mem = mremap(
+ slice.as_mut_ptr() as *mut c_void,
+ ONE_K,
+ 10 * ONE_K,
+ MRemapFlags::MREMAP_MAYMOVE,
+ None,
+ )
+ .unwrap();
+ #[cfg(target_os = "netbsd")]
+ let mem = mremap(
+ slice.as_mut_ptr() as *mut c_void,
+ ONE_K,
+ 10 * ONE_K,
+ MRemapFlags::MAP_REMAPDUP,
+ None,
+ )
+ .unwrap();
+ std::slice::from_raw_parts_mut(mem as *mut u8, 10 * ONE_K)
+ };
+
+ // The first KB should still have the old data in it.
+ assert_eq!(slice[ONE_K - 1], 0xFF);
+
+ // The additional range should be zero-init'd and accessible.
+ assert_eq!(slice[10 * ONE_K - 1], 0x00);
+ slice[10 * ONE_K - 1] = 0xFF;
+ assert_eq!(slice[10 * ONE_K - 1], 0xFF);
+}
+
+#[test]
+#[cfg(any(target_os = "linux", target_os = "netbsd"))]
+// Segfaults for unknown reasons under QEMU for 32-bit targets
+#[cfg_attr(all(target_pointer_width = "32", qemu), ignore)]
+fn test_mremap_shrink() {
+ use nix::libc::{c_void, size_t};
+ use nix::sys::mman::{mremap, MRemapFlags};
+ use std::num::NonZeroUsize;
+
+ const ONE_K: size_t = 1024;
+ let ten_one_k = NonZeroUsize::new(10 * ONE_K).unwrap();
+ let slice: &mut [u8] = unsafe {
+ let mem = mmap(
+ None,
+ ten_one_k,
+ ProtFlags::PROT_READ | ProtFlags::PROT_WRITE,
+ MapFlags::MAP_ANONYMOUS | MapFlags::MAP_PRIVATE,
+ -1,
+ 0,
+ )
+ .unwrap();
+ std::slice::from_raw_parts_mut(mem as *mut u8, ONE_K)
+ };
+ assert_eq!(slice[ONE_K - 1], 0x00);
+ slice[ONE_K - 1] = 0xFF;
+ assert_eq!(slice[ONE_K - 1], 0xFF);
+
+ let slice: &mut [u8] = unsafe {
+ let mem = mremap(
+ slice.as_mut_ptr() as *mut c_void,
+ ten_one_k.into(),
+ ONE_K,
+ MRemapFlags::empty(),
+ None,
+ )
+ .unwrap();
+ // Since we didn't supply MREMAP_MAYMOVE, the address should be the
+ // same.
+ assert_eq!(mem, slice.as_mut_ptr() as *mut c_void);
+ std::slice::from_raw_parts_mut(mem as *mut u8, ONE_K)
+ };
+
+ // The first KB should still be accessible and have the old data in it.
+ assert_eq!(slice[ONE_K - 1], 0xFF);
+}
diff --git a/third_party/rust/nix/test/sys/test_pthread.rs b/third_party/rust/nix/test/sys/test_pthread.rs
new file mode 100644
index 0000000000..ce048bae60
--- /dev/null
+++ b/third_party/rust/nix/test/sys/test_pthread.rs
@@ -0,0 +1,22 @@
+use nix::sys::pthread::*;
+
+#[cfg(any(target_env = "musl", target_os = "redox"))]
+#[test]
+fn test_pthread_self() {
+ let tid = pthread_self();
+ assert!(!tid.is_null());
+}
+
+#[cfg(not(any(target_env = "musl", target_os = "redox")))]
+#[test]
+fn test_pthread_self() {
+ let tid = pthread_self();
+ assert_ne!(tid, 0);
+}
+
+#[test]
+#[cfg(not(target_os = "redox"))]
+fn test_pthread_kill_none() {
+ pthread_kill(pthread_self(), None)
+ .expect("Should be able to send signal to my thread.");
+}
diff --git a/third_party/rust/nix/test/sys/test_ptrace.rs b/third_party/rust/nix/test/sys/test_ptrace.rs
new file mode 100644
index 0000000000..530560fe17
--- /dev/null
+++ b/third_party/rust/nix/test/sys/test_ptrace.rs
@@ -0,0 +1,275 @@
+#[cfg(all(
+ target_os = "linux",
+ any(target_arch = "x86_64", target_arch = "x86"),
+ target_env = "gnu"
+))]
+use memoffset::offset_of;
+use nix::errno::Errno;
+use nix::sys::ptrace;
+#[cfg(any(target_os = "android", target_os = "linux"))]
+use nix::sys::ptrace::Options;
+use nix::unistd::getpid;
+
+#[cfg(any(target_os = "android", target_os = "linux"))]
+use std::mem;
+
+use crate::*;
+
+#[test]
+fn test_ptrace() {
+ // Just make sure ptrace can be called at all, for now.
+ // FIXME: qemu-user doesn't implement ptrace on all arches, so permit ENOSYS
+ require_capability!("test_ptrace", CAP_SYS_PTRACE);
+ let err = ptrace::attach(getpid()).unwrap_err();
+ assert!(
+ err == Errno::EPERM || err == Errno::EINVAL || err == Errno::ENOSYS
+ );
+}
+
+// Just make sure ptrace_setoptions can be called at all, for now.
+#[test]
+#[cfg(any(target_os = "android", target_os = "linux"))]
+fn test_ptrace_setoptions() {
+ require_capability!("test_ptrace_setoptions", CAP_SYS_PTRACE);
+ let err = ptrace::setoptions(getpid(), Options::PTRACE_O_TRACESYSGOOD)
+ .unwrap_err();
+ assert_ne!(err, Errno::EOPNOTSUPP);
+}
+
+// Just make sure ptrace_getevent can be called at all, for now.
+#[test]
+#[cfg(any(target_os = "android", target_os = "linux"))]
+fn test_ptrace_getevent() {
+ require_capability!("test_ptrace_getevent", CAP_SYS_PTRACE);
+ let err = ptrace::getevent(getpid()).unwrap_err();
+ assert_ne!(err, Errno::EOPNOTSUPP);
+}
+
+// Just make sure ptrace_getsiginfo can be called at all, for now.
+#[test]
+#[cfg(any(target_os = "android", target_os = "linux"))]
+fn test_ptrace_getsiginfo() {
+ require_capability!("test_ptrace_getsiginfo", CAP_SYS_PTRACE);
+ if let Err(Errno::EOPNOTSUPP) = ptrace::getsiginfo(getpid()) {
+ panic!("ptrace_getsiginfo returns Errno::EOPNOTSUPP!");
+ }
+}
+
+// Just make sure ptrace_setsiginfo can be called at all, for now.
+#[test]
+#[cfg(any(target_os = "android", target_os = "linux"))]
+fn test_ptrace_setsiginfo() {
+ require_capability!("test_ptrace_setsiginfo", CAP_SYS_PTRACE);
+ let siginfo = unsafe { mem::zeroed() };
+ if let Err(Errno::EOPNOTSUPP) = ptrace::setsiginfo(getpid(), &siginfo) {
+ panic!("ptrace_setsiginfo returns Errno::EOPNOTSUPP!");
+ }
+}
+
+#[test]
+fn test_ptrace_cont() {
+ use nix::sys::ptrace;
+ use nix::sys::signal::{raise, Signal};
+ use nix::sys::wait::{waitpid, WaitPidFlag, WaitStatus};
+ use nix::unistd::fork;
+ use nix::unistd::ForkResult::*;
+
+ require_capability!("test_ptrace_cont", CAP_SYS_PTRACE);
+
+ let _m = crate::FORK_MTX.lock();
+
+ // FIXME: qemu-user doesn't implement ptrace on all architectures
+ // and returns ENOSYS in this case.
+ // We (ab)use this behavior to detect the affected platforms
+ // and skip the test then.
+ // 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 == Errno::ENOSYS {
+ return;
+ }
+
+ match unsafe { fork() }.expect("Error: Fork Failed") {
+ Child => {
+ ptrace::traceme().unwrap();
+ // As recommended by ptrace(2), raise SIGTRAP to pause the child
+ // until the parent is ready to continue
+ loop {
+ raise(Signal::SIGTRAP).unwrap();
+ }
+ }
+ Parent { child } => {
+ assert_eq!(
+ waitpid(child, None),
+ Ok(WaitStatus::Stopped(child, Signal::SIGTRAP))
+ );
+ ptrace::cont(child, None).unwrap();
+ assert_eq!(
+ waitpid(child, None),
+ Ok(WaitStatus::Stopped(child, Signal::SIGTRAP))
+ );
+ ptrace::cont(child, Some(Signal::SIGKILL)).unwrap();
+ match waitpid(child, None) {
+ Ok(WaitStatus::Signaled(pid, Signal::SIGKILL, _))
+ if pid == child =>
+ {
+ // FIXME It's been observed on some systems (apple) the
+ // tracee may not be killed but remain as a zombie process
+ // affecting other wait based tests. Add an extra kill just
+ // to make sure there are no zombies.
+ let _ = waitpid(child, Some(WaitPidFlag::WNOHANG));
+ while ptrace::cont(child, Some(Signal::SIGKILL)).is_ok() {
+ let _ = waitpid(child, Some(WaitPidFlag::WNOHANG));
+ }
+ }
+ _ => panic!("The process should have been killed"),
+ }
+ }
+ }
+}
+
+#[cfg(target_os = "linux")]
+#[test]
+fn test_ptrace_interrupt() {
+ use nix::sys::ptrace;
+ use nix::sys::signal::Signal;
+ use nix::sys::wait::{waitpid, WaitPidFlag, WaitStatus};
+ use nix::unistd::fork;
+ use nix::unistd::ForkResult::*;
+ use std::thread::sleep;
+ use std::time::Duration;
+
+ require_capability!("test_ptrace_interrupt", CAP_SYS_PTRACE);
+
+ let _m = crate::FORK_MTX.lock();
+
+ match unsafe { fork() }.expect("Error: Fork Failed") {
+ Child => loop {
+ sleep(Duration::from_millis(1000));
+ },
+ Parent { child } => {
+ ptrace::seize(child, ptrace::Options::PTRACE_O_TRACESYSGOOD)
+ .unwrap();
+ ptrace::interrupt(child).unwrap();
+ assert_eq!(
+ waitpid(child, None),
+ Ok(WaitStatus::PtraceEvent(child, Signal::SIGTRAP, 128))
+ );
+ ptrace::syscall(child, None).unwrap();
+ assert_eq!(
+ waitpid(child, None),
+ Ok(WaitStatus::PtraceSyscall(child))
+ );
+ ptrace::detach(child, Some(Signal::SIGKILL)).unwrap();
+ match waitpid(child, None) {
+ Ok(WaitStatus::Signaled(pid, Signal::SIGKILL, _))
+ if pid == child =>
+ {
+ let _ = waitpid(child, Some(WaitPidFlag::WNOHANG));
+ while ptrace::cont(child, Some(Signal::SIGKILL)).is_ok() {
+ let _ = waitpid(child, Some(WaitPidFlag::WNOHANG));
+ }
+ }
+ _ => panic!("The process should have been killed"),
+ }
+ }
+ }
+}
+
+// ptrace::{setoptions, getregs} are only available in these platforms
+#[cfg(all(
+ target_os = "linux",
+ any(target_arch = "x86_64", target_arch = "x86"),
+ target_env = "gnu"
+))]
+#[test]
+fn test_ptrace_syscall() {
+ use nix::sys::ptrace;
+ use nix::sys::signal::kill;
+ use nix::sys::signal::Signal;
+ use nix::sys::wait::{waitpid, WaitStatus};
+ use nix::unistd::fork;
+ use nix::unistd::getpid;
+ use nix::unistd::ForkResult::*;
+
+ require_capability!("test_ptrace_syscall", CAP_SYS_PTRACE);
+
+ let _m = crate::FORK_MTX.lock();
+
+ match unsafe { fork() }.expect("Error: Fork Failed") {
+ Child => {
+ ptrace::traceme().unwrap();
+ // first sigstop until parent is ready to continue
+ let pid = getpid();
+ kill(pid, Signal::SIGSTOP).unwrap();
+ kill(pid, Signal::SIGTERM).unwrap();
+ unsafe {
+ ::libc::_exit(0);
+ }
+ }
+
+ Parent { child } => {
+ assert_eq!(
+ waitpid(child, None),
+ Ok(WaitStatus::Stopped(child, Signal::SIGSTOP))
+ );
+
+ // set this option to recognize syscall-stops
+ ptrace::setoptions(child, ptrace::Options::PTRACE_O_TRACESYSGOOD)
+ .unwrap();
+
+ #[cfg(target_arch = "x86_64")]
+ let get_syscall_id =
+ || ptrace::getregs(child).unwrap().orig_rax as libc::c_long;
+
+ #[cfg(target_arch = "x86")]
+ let get_syscall_id =
+ || ptrace::getregs(child).unwrap().orig_eax as libc::c_long;
+
+ // this duplicates `get_syscall_id` for the purpose of testing `ptrace::read_user`.
+ #[cfg(target_arch = "x86_64")]
+ let rax_offset = offset_of!(libc::user_regs_struct, orig_rax);
+ #[cfg(target_arch = "x86")]
+ let rax_offset = offset_of!(libc::user_regs_struct, orig_eax);
+
+ let get_syscall_from_user_area = || {
+ // Find the offset of `user.regs.rax` (or `user.regs.eax` for x86)
+ let rax_offset = offset_of!(libc::user, regs) + rax_offset;
+ ptrace::read_user(child, rax_offset as _).unwrap()
+ as libc::c_long
+ };
+
+ // kill entry
+ ptrace::syscall(child, None).unwrap();
+ assert_eq!(
+ waitpid(child, None),
+ Ok(WaitStatus::PtraceSyscall(child))
+ );
+ assert_eq!(get_syscall_id(), ::libc::SYS_kill);
+ assert_eq!(get_syscall_from_user_area(), ::libc::SYS_kill);
+
+ // kill exit
+ ptrace::syscall(child, None).unwrap();
+ assert_eq!(
+ waitpid(child, None),
+ Ok(WaitStatus::PtraceSyscall(child))
+ );
+ assert_eq!(get_syscall_id(), ::libc::SYS_kill);
+ assert_eq!(get_syscall_from_user_area(), ::libc::SYS_kill);
+
+ // receive signal
+ ptrace::syscall(child, None).unwrap();
+ assert_eq!(
+ waitpid(child, None),
+ Ok(WaitStatus::Stopped(child, Signal::SIGTERM))
+ );
+
+ // inject signal
+ ptrace::syscall(child, Signal::SIGTERM).unwrap();
+ assert_eq!(
+ waitpid(child, None),
+ Ok(WaitStatus::Signaled(child, Signal::SIGTERM, false))
+ );
+ }
+ }
+}
diff --git a/third_party/rust/nix/test/sys/test_select.rs b/third_party/rust/nix/test/sys/test_select.rs
new file mode 100644
index 0000000000..40bda4d90a
--- /dev/null
+++ b/third_party/rust/nix/test/sys/test_select.rs
@@ -0,0 +1,81 @@
+use nix::sys::select::*;
+use nix::sys::signal::SigSet;
+use nix::sys::time::{TimeSpec, TimeValLike};
+use nix::unistd::{pipe, write};
+
+#[test]
+pub fn test_pselect() {
+ let _mtx = crate::SIGNAL_MTX.lock();
+
+ let (r1, w1) = pipe().unwrap();
+ write(w1, b"hi!").unwrap();
+ let (r2, _w2) = pipe().unwrap();
+
+ let mut fd_set = FdSet::new();
+ fd_set.insert(r1);
+ fd_set.insert(r2);
+
+ let timeout = TimeSpec::seconds(10);
+ let sigmask = SigSet::empty();
+ assert_eq!(
+ 1,
+ pselect(None, &mut fd_set, None, None, &timeout, &sigmask).unwrap()
+ );
+ assert!(fd_set.contains(r1));
+ assert!(!fd_set.contains(r2));
+}
+
+#[test]
+pub fn test_pselect_nfds2() {
+ let (r1, w1) = pipe().unwrap();
+ write(w1, b"hi!").unwrap();
+ let (r2, _w2) = pipe().unwrap();
+
+ let mut fd_set = FdSet::new();
+ fd_set.insert(r1);
+ fd_set.insert(r2);
+
+ let timeout = TimeSpec::seconds(10);
+ assert_eq!(
+ 1,
+ pselect(
+ ::std::cmp::max(r1, r2) + 1,
+ &mut fd_set,
+ None,
+ None,
+ &timeout,
+ None
+ )
+ .unwrap()
+ );
+ assert!(fd_set.contains(r1));
+ assert!(!fd_set.contains(r2));
+}
+
+macro_rules! generate_fdset_bad_fd_tests {
+ ($fd:expr, $($method:ident),* $(,)?) => {
+ $(
+ #[test]
+ #[should_panic]
+ fn $method() {
+ FdSet::new().$method($fd);
+ }
+ )*
+ }
+}
+
+mod test_fdset_negative_fd {
+ use super::*;
+ generate_fdset_bad_fd_tests!(-1, insert, remove, contains);
+}
+
+mod test_fdset_too_large_fd {
+ use super::*;
+ use std::convert::TryInto;
+ generate_fdset_bad_fd_tests!(
+ FD_SETSIZE.try_into().unwrap(),
+ insert,
+ remove,
+ contains,
+ );
+}
diff --git a/third_party/rust/nix/test/sys/test_signal.rs b/third_party/rust/nix/test/sys/test_signal.rs
new file mode 100644
index 0000000000..3ad14f40c7
--- /dev/null
+++ b/third_party/rust/nix/test/sys/test_signal.rs
@@ -0,0 +1,147 @@
+#[cfg(not(target_os = "redox"))]
+use nix::errno::Errno;
+use nix::sys::signal::*;
+use nix::unistd::*;
+use std::convert::TryFrom;
+use std::sync::atomic::{AtomicBool, Ordering};
+
+#[test]
+fn test_kill_none() {
+ kill(getpid(), None).expect("Should be able to send signal to myself.");
+}
+
+#[test]
+#[cfg(not(target_os = "fuchsia"))]
+fn test_killpg_none() {
+ killpg(getpgrp(), None)
+ .expect("Should be able to send signal to my process group.");
+}
+
+#[test]
+fn test_old_sigaction_flags() {
+ let _m = crate::SIGNAL_MTX.lock();
+
+ extern "C" fn handler(_: ::libc::c_int) {}
+ let act = SigAction::new(
+ SigHandler::Handler(handler),
+ SaFlags::empty(),
+ SigSet::empty(),
+ );
+ let oact = unsafe { sigaction(SIGINT, &act) }.unwrap();
+ let _flags = oact.flags();
+ let oact = unsafe { sigaction(SIGINT, &act) }.unwrap();
+ let _flags = oact.flags();
+}
+
+#[test]
+fn test_sigprocmask_noop() {
+ sigprocmask(SigmaskHow::SIG_BLOCK, None, None)
+ .expect("this should be an effective noop");
+}
+
+#[test]
+fn test_sigprocmask() {
+ let _m = crate::SIGNAL_MTX.lock();
+
+ // This needs to be a signal that rust doesn't use in the test harness.
+ const SIGNAL: Signal = Signal::SIGCHLD;
+
+ let mut old_signal_set = SigSet::empty();
+ sigprocmask(SigmaskHow::SIG_BLOCK, None, Some(&mut old_signal_set))
+ .expect("expect to be able to retrieve old signals");
+
+ // Make sure the old set doesn't contain the signal, otherwise the following
+ // test don't make sense.
+ assert!(
+ !old_signal_set.contains(SIGNAL),
+ "the {:?} signal is already blocked, please change to a \
+ different one",
+ SIGNAL
+ );
+
+ // Now block the signal.
+ let mut signal_set = SigSet::empty();
+ signal_set.add(SIGNAL);
+ sigprocmask(SigmaskHow::SIG_BLOCK, Some(&signal_set), None)
+ .expect("expect to be able to block signals");
+
+ // And test it again, to make sure the change was effective.
+ old_signal_set.clear();
+ sigprocmask(SigmaskHow::SIG_BLOCK, None, Some(&mut old_signal_set))
+ .expect("expect to be able to retrieve old signals");
+ assert!(
+ old_signal_set.contains(SIGNAL),
+ "expected the {:?} to be blocked",
+ SIGNAL
+ );
+
+ // Reset the signal.
+ sigprocmask(SigmaskHow::SIG_UNBLOCK, Some(&signal_set), None)
+ .expect("expect to be able to block signals");
+}
+
+lazy_static! {
+ static ref SIGNALED: AtomicBool = AtomicBool::new(false);
+}
+
+extern "C" fn test_sigaction_handler(signal: libc::c_int) {
+ let signal = Signal::try_from(signal).unwrap();
+ SIGNALED.store(signal == Signal::SIGINT, Ordering::Relaxed);
+}
+
+#[cfg(not(target_os = "redox"))]
+extern "C" fn test_sigaction_action(
+ _: libc::c_int,
+ _: *mut libc::siginfo_t,
+ _: *mut libc::c_void,
+) {
+}
+
+#[test]
+#[cfg(not(target_os = "redox"))]
+fn test_signal_sigaction() {
+ let _m = crate::SIGNAL_MTX.lock();
+
+ let action_handler = SigHandler::SigAction(test_sigaction_action);
+ assert_eq!(
+ unsafe { signal(Signal::SIGINT, action_handler) }.unwrap_err(),
+ Errno::ENOTSUP
+ );
+}
+
+#[test]
+fn test_signal() {
+ let _m = crate::SIGNAL_MTX.lock();
+
+ unsafe { signal(Signal::SIGINT, SigHandler::SigIgn) }.unwrap();
+ raise(Signal::SIGINT).unwrap();
+ assert_eq!(
+ unsafe { signal(Signal::SIGINT, SigHandler::SigDfl) }.unwrap(),
+ SigHandler::SigIgn
+ );
+
+ let handler = SigHandler::Handler(test_sigaction_handler);
+ assert_eq!(
+ unsafe { signal(Signal::SIGINT, handler) }.unwrap(),
+ SigHandler::SigDfl
+ );
+ raise(Signal::SIGINT).unwrap();
+ assert!(SIGNALED.load(Ordering::Relaxed));
+
+ #[cfg(not(any(target_os = "illumos", target_os = "solaris")))]
+ assert_eq!(
+ unsafe { signal(Signal::SIGINT, SigHandler::SigDfl) }.unwrap(),
+ handler
+ );
+
+ // System V based OSes (e.g. illumos and Solaris) always resets the
+ // disposition to SIG_DFL prior to calling the signal handler
+ #[cfg(any(target_os = "illumos", target_os = "solaris"))]
+ assert_eq!(
+ unsafe { signal(Signal::SIGINT, SigHandler::SigDfl) }.unwrap(),
+ SigHandler::SigDfl
+ );
+
+ // Restore default signal handler
+ unsafe { signal(Signal::SIGINT, SigHandler::SigDfl) }.unwrap();
+}
diff --git a/third_party/rust/nix/test/sys/test_signalfd.rs b/third_party/rust/nix/test/sys/test_signalfd.rs
new file mode 100644
index 0000000000..87153c9572
--- /dev/null
+++ b/third_party/rust/nix/test/sys/test_signalfd.rs
@@ -0,0 +1,27 @@
+use std::convert::TryFrom;
+
+#[test]
+fn test_signalfd() {
+ use nix::sys::signal::{self, raise, SigSet, Signal};
+ use nix::sys::signalfd::SignalFd;
+
+ // Grab the mutex for altering signals so we don't interfere with other tests.
+ let _m = crate::SIGNAL_MTX.lock();
+
+ // Block the SIGUSR1 signal from automatic processing for this thread
+ let mut mask = SigSet::empty();
+ mask.add(signal::SIGUSR1);
+ mask.thread_block().unwrap();
+
+ let mut fd = SignalFd::new(&mask).unwrap();
+
+ // Send a SIGUSR1 signal to the current process. Note that this uses `raise` instead of `kill`
+ // because `kill` with `getpid` isn't correct during multi-threaded execution like during a
+ // cargo test session. Instead use `raise` which does the correct thing by default.
+ raise(signal::SIGUSR1).expect("Error: raise(SIGUSR1) failed");
+
+ // And now catch that same signal.
+ let res = fd.read_signal().unwrap().unwrap();
+ let signo = Signal::try_from(res.ssi_signo as i32).unwrap();
+ assert_eq!(signo, signal::SIGUSR1);
+}
diff --git a/third_party/rust/nix/test/sys/test_socket.rs b/third_party/rust/nix/test/sys/test_socket.rs
new file mode 100644
index 0000000000..5adc77ed6b
--- /dev/null
+++ b/third_party/rust/nix/test/sys/test_socket.rs
@@ -0,0 +1,2628 @@
+#[cfg(any(target_os = "linux", target_os = "android"))]
+use crate::*;
+use libc::{c_char, sockaddr_storage};
+#[allow(deprecated)]
+use nix::sys::socket::InetAddr;
+use nix::sys::socket::{
+ getsockname, sockaddr, sockaddr_in6, AddressFamily, UnixAddr,
+};
+use std::collections::hash_map::DefaultHasher;
+use std::hash::{Hash, Hasher};
+use std::mem::{self, MaybeUninit};
+use std::net::{self, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
+use std::os::unix::io::RawFd;
+use std::path::Path;
+use std::slice;
+use std::str::FromStr;
+
+#[allow(deprecated)]
+#[test]
+pub fn test_inetv4_addr_to_sock_addr() {
+ let actual: net::SocketAddr = FromStr::from_str("127.0.0.1:3000").unwrap();
+ let addr = InetAddr::from_std(&actual);
+
+ match addr {
+ InetAddr::V4(addr) => {
+ let ip: u32 = 0x7f00_0001;
+ let port: u16 = 3000;
+ let saddr = addr.sin_addr.s_addr;
+
+ assert_eq!(saddr, ip.to_be());
+ assert_eq!(addr.sin_port, port.to_be());
+ }
+ _ => panic!("nope"),
+ }
+
+ assert_eq!(addr.to_string(), "127.0.0.1:3000");
+
+ let inet = addr.to_std();
+ assert_eq!(actual, inet);
+}
+
+#[allow(deprecated)]
+#[test]
+pub fn test_inetv4_addr_roundtrip_sockaddr_storage_to_addr() {
+ use nix::sys::socket::{sockaddr_storage_to_addr, SockAddr};
+
+ let actual: net::SocketAddr = FromStr::from_str("127.0.0.1:3000").unwrap();
+ let addr = InetAddr::from_std(&actual);
+ let sockaddr = SockAddr::new_inet(addr);
+
+ let (storage, ffi_size) = {
+ let mut storage = MaybeUninit::<sockaddr_storage>::zeroed();
+ let storage_ptr = storage.as_mut_ptr().cast::<sockaddr>();
+ let (ffi_ptr, ffi_size) = sockaddr.as_ffi_pair();
+ assert_eq!(mem::size_of::<sockaddr>(), ffi_size as usize);
+ unsafe {
+ storage_ptr.copy_from_nonoverlapping(ffi_ptr as *const sockaddr, 1);
+ (storage.assume_init(), ffi_size)
+ }
+ };
+
+ let from_storage =
+ sockaddr_storage_to_addr(&storage, ffi_size as usize).unwrap();
+ assert_eq!(from_storage, sockaddr);
+ let from_storage =
+ sockaddr_storage_to_addr(&storage, mem::size_of::<sockaddr_storage>())
+ .unwrap();
+ assert_eq!(from_storage, sockaddr);
+}
+
+#[cfg(any(target_os = "linux"))]
+#[cfg_attr(qemu, ignore)]
+#[test]
+pub fn test_timestamping() {
+ use nix::sys::socket::{
+ recvmsg, sendmsg, setsockopt, socket, sockopt::Timestamping,
+ ControlMessageOwned, MsgFlags, SockFlag, SockType, SockaddrIn,
+ TimestampingFlag,
+ };
+ use std::io::{IoSlice, IoSliceMut};
+
+ let sock_addr = SockaddrIn::from_str("127.0.0.1:6790").unwrap();
+
+ let ssock = socket(
+ AddressFamily::Inet,
+ SockType::Datagram,
+ SockFlag::empty(),
+ None,
+ )
+ .expect("send socket failed");
+
+ let rsock = socket(
+ AddressFamily::Inet,
+ SockType::Datagram,
+ SockFlag::empty(),
+ None,
+ )
+ .unwrap();
+ nix::sys::socket::bind(rsock, &sock_addr).unwrap();
+
+ setsockopt(rsock, Timestamping, &TimestampingFlag::all()).unwrap();
+
+ let sbuf = [0u8; 2048];
+ let mut rbuf = [0u8; 2048];
+ let flags = MsgFlags::empty();
+ let iov1 = [IoSlice::new(&sbuf)];
+ let mut iov2 = [IoSliceMut::new(&mut rbuf)];
+
+ let mut cmsg = cmsg_space!(nix::sys::socket::Timestamps);
+ sendmsg(ssock, &iov1, &[], flags, Some(&sock_addr)).unwrap();
+ let recv = recvmsg::<()>(rsock, &mut iov2, Some(&mut cmsg), flags).unwrap();
+
+ let mut ts = None;
+ for c in recv.cmsgs() {
+ if let ControlMessageOwned::ScmTimestampsns(timestamps) = c {
+ ts = Some(timestamps.system);
+ }
+ }
+ let ts = ts.expect("ScmTimestampns is present");
+ let sys_time =
+ ::nix::time::clock_gettime(::nix::time::ClockId::CLOCK_REALTIME)
+ .unwrap();
+ let diff = if ts > sys_time {
+ ts - sys_time
+ } else {
+ sys_time - ts
+ };
+ assert!(std::time::Duration::from(diff).as_secs() < 60);
+}
+
+#[allow(deprecated)]
+#[test]
+pub fn test_inetv6_addr_roundtrip_sockaddr_storage_to_addr() {
+ use nix::sys::socket::{sockaddr_storage_to_addr, SockAddr};
+
+ let port: u16 = 3000;
+ let flowinfo: u32 = 1;
+ let scope_id: u32 = 2;
+ let ip: Ipv6Addr = "fe80::1".parse().unwrap();
+
+ let actual =
+ SocketAddr::V6(SocketAddrV6::new(ip, port, flowinfo, scope_id));
+ let addr = InetAddr::from_std(&actual);
+ let sockaddr = SockAddr::new_inet(addr);
+
+ let (storage, ffi_size) = {
+ let mut storage = MaybeUninit::<sockaddr_storage>::zeroed();
+ let storage_ptr = storage.as_mut_ptr().cast::<sockaddr_in6>();
+ let (ffi_ptr, ffi_size) = sockaddr.as_ffi_pair();
+ assert_eq!(mem::size_of::<sockaddr_in6>(), ffi_size as usize);
+ unsafe {
+ storage_ptr.copy_from_nonoverlapping(
+ (ffi_ptr as *const sockaddr).cast::<sockaddr_in6>(),
+ 1,
+ );
+ (storage.assume_init(), ffi_size)
+ }
+ };
+
+ let from_storage =
+ sockaddr_storage_to_addr(&storage, ffi_size as usize).unwrap();
+ assert_eq!(from_storage, sockaddr);
+ let from_storage =
+ sockaddr_storage_to_addr(&storage, mem::size_of::<sockaddr_storage>())
+ .unwrap();
+ assert_eq!(from_storage, sockaddr);
+}
+
+#[test]
+pub fn test_path_to_sock_addr() {
+ let path = "/foo/bar";
+ let actual = Path::new(path);
+ let addr = UnixAddr::new(actual).unwrap();
+
+ let expect: &[c_char] = unsafe {
+ slice::from_raw_parts(path.as_ptr() as *const c_char, path.len())
+ };
+ assert_eq!(unsafe { &(*addr.as_ptr()).sun_path[..8] }, expect);
+
+ assert_eq!(addr.path(), Some(actual));
+}
+
+fn calculate_hash<T: Hash>(t: &T) -> u64 {
+ let mut s = DefaultHasher::new();
+ t.hash(&mut s);
+ s.finish()
+}
+
+#[test]
+pub fn test_addr_equality_path() {
+ let path = "/foo/bar";
+ let actual = Path::new(path);
+ let addr1 = UnixAddr::new(actual).unwrap();
+ let mut addr2 = addr1;
+
+ unsafe { (*addr2.as_mut_ptr()).sun_path[10] = 127 };
+
+ assert_eq!(addr1, addr2);
+ assert_eq!(calculate_hash(&addr1), calculate_hash(&addr2));
+}
+
+#[cfg(any(target_os = "android", target_os = "linux"))]
+#[test]
+pub fn test_abstract_sun_path_too_long() {
+ let name = String::from("nix\0abstract\0tesnix\0abstract\0tesnix\0abstract\0tesnix\0abstract\0tesnix\0abstract\0testttttnix\0abstract\0test\0make\0sure\0this\0is\0long\0enough");
+ let addr = UnixAddr::new_abstract(name.as_bytes());
+ addr.expect_err("assertion failed");
+}
+
+#[cfg(any(target_os = "android", target_os = "linux"))]
+#[test]
+pub fn test_addr_equality_abstract() {
+ let name = String::from("nix\0abstract\0test");
+ let addr1 = UnixAddr::new_abstract(name.as_bytes()).unwrap();
+ let mut addr2 = addr1;
+
+ assert_eq!(addr1, addr2);
+ assert_eq!(calculate_hash(&addr1), calculate_hash(&addr2));
+
+ unsafe { (*addr2.as_mut_ptr()).sun_path[17] = 127 };
+ assert_ne!(addr1, addr2);
+ assert_ne!(calculate_hash(&addr1), calculate_hash(&addr2));
+}
+
+// Test getting/setting abstract addresses (without unix socket creation)
+#[cfg(any(target_os = "android", target_os = "linux"))]
+#[test]
+pub fn test_abstract_uds_addr() {
+ let empty = String::new();
+ let addr = UnixAddr::new_abstract(empty.as_bytes()).unwrap();
+ let sun_path: [u8; 0] = [];
+ assert_eq!(addr.as_abstract(), Some(&sun_path[..]));
+
+ let name = String::from("nix\0abstract\0test");
+ let addr = UnixAddr::new_abstract(name.as_bytes()).unwrap();
+ let sun_path = [
+ 110u8, 105, 120, 0, 97, 98, 115, 116, 114, 97, 99, 116, 0, 116, 101,
+ 115, 116,
+ ];
+ assert_eq!(addr.as_abstract(), Some(&sun_path[..]));
+ assert_eq!(addr.path(), None);
+
+ // Internally, name is null-prefixed (abstract namespace)
+ assert_eq!(unsafe { (*addr.as_ptr()).sun_path[0] }, 0);
+}
+
+// Test getting an unnamed address (without unix socket creation)
+#[cfg(any(target_os = "android", target_os = "linux"))]
+#[test]
+pub fn test_unnamed_uds_addr() {
+ use crate::nix::sys::socket::SockaddrLike;
+
+ let addr = UnixAddr::new_unnamed();
+
+ assert!(addr.is_unnamed());
+ assert_eq!(addr.len(), 2);
+ assert!(addr.path().is_none());
+ assert_eq!(addr.path_len(), 0);
+
+ assert!(addr.as_abstract().is_none());
+}
+
+#[test]
+pub fn test_getsockname() {
+ use nix::sys::socket::bind;
+ use nix::sys::socket::{socket, AddressFamily, SockFlag, SockType};
+
+ let tempdir = tempfile::tempdir().unwrap();
+ let sockname = tempdir.path().join("sock");
+ let sock = socket(
+ AddressFamily::Unix,
+ SockType::Stream,
+ SockFlag::empty(),
+ None,
+ )
+ .expect("socket failed");
+ let sockaddr = UnixAddr::new(&sockname).unwrap();
+ bind(sock, &sockaddr).expect("bind failed");
+ assert_eq!(sockaddr, getsockname(sock).expect("getsockname failed"));
+}
+
+#[test]
+pub fn test_socketpair() {
+ use nix::sys::socket::{socketpair, AddressFamily, SockFlag, SockType};
+ use nix::unistd::{read, write};
+
+ let (fd1, fd2) = socketpair(
+ AddressFamily::Unix,
+ SockType::Stream,
+ None,
+ SockFlag::empty(),
+ )
+ .unwrap();
+ write(fd1, b"hello").unwrap();
+ let mut buf = [0; 5];
+ read(fd2, &mut buf).unwrap();
+
+ assert_eq!(&buf[..], b"hello");
+}
+
+#[test]
+pub fn test_std_conversions() {
+ use nix::sys::socket::*;
+
+ let std_sa = SocketAddrV4::from_str("127.0.0.1:6789").unwrap();
+ let sock_addr = SockaddrIn::from(std_sa);
+ assert_eq!(std_sa, sock_addr.into());
+
+ let std_sa = SocketAddrV6::from_str("[::1]:6000").unwrap();
+ let sock_addr: SockaddrIn6 = SockaddrIn6::from(std_sa);
+ assert_eq!(std_sa, sock_addr.into());
+}
+
+mod recvfrom {
+ use super::*;
+ use nix::sys::socket::*;
+ use nix::{errno::Errno, Result};
+ use std::thread;
+
+ const MSG: &[u8] = b"Hello, World!";
+
+ fn sendrecv<Fs, Fr>(
+ rsock: RawFd,
+ ssock: RawFd,
+ f_send: Fs,
+ mut f_recv: Fr,
+ ) -> Option<SockaddrStorage>
+ where
+ Fs: Fn(RawFd, &[u8], MsgFlags) -> Result<usize> + Send + 'static,
+ Fr: FnMut(usize, Option<SockaddrStorage>),
+ {
+ let mut buf: [u8; 13] = [0u8; 13];
+ let mut l = 0;
+ let mut from = None;
+
+ let send_thread = thread::spawn(move || {
+ let mut l = 0;
+ while l < std::mem::size_of_val(MSG) {
+ l += f_send(ssock, &MSG[l..], MsgFlags::empty()).unwrap();
+ }
+ });
+
+ while l < std::mem::size_of_val(MSG) {
+ let (len, from_) = recvfrom(rsock, &mut buf[l..]).unwrap();
+ f_recv(len, from_);
+ from = from_;
+ l += len;
+ }
+ assert_eq!(&buf, MSG);
+ send_thread.join().unwrap();
+ from
+ }
+
+ #[test]
+ pub fn stream() {
+ let (fd2, fd1) = socketpair(
+ AddressFamily::Unix,
+ SockType::Stream,
+ None,
+ SockFlag::empty(),
+ )
+ .unwrap();
+ // Ignore from for stream sockets
+ let _ = sendrecv(fd1, fd2, send, |_, _| {});
+ }
+
+ #[test]
+ pub fn udp() {
+ let std_sa = SocketAddrV4::from_str("127.0.0.1:6789").unwrap();
+ let sock_addr = SockaddrIn::from(std_sa);
+ let rsock = socket(
+ AddressFamily::Inet,
+ SockType::Datagram,
+ SockFlag::empty(),
+ None,
+ )
+ .unwrap();
+ bind(rsock, &sock_addr).unwrap();
+ let ssock = socket(
+ AddressFamily::Inet,
+ SockType::Datagram,
+ SockFlag::empty(),
+ None,
+ )
+ .expect("send socket failed");
+ let from = sendrecv(
+ rsock,
+ ssock,
+ move |s, m, flags| sendto(s, m, &sock_addr, flags),
+ |_, _| {},
+ );
+ // UDP sockets should set the from address
+ assert_eq!(AddressFamily::Inet, from.unwrap().family().unwrap());
+ }
+
+ #[cfg(target_os = "linux")]
+ mod udp_offload {
+ use super::*;
+ use nix::sys::socket::sockopt::{UdpGroSegment, UdpGsoSegment};
+ use std::io::IoSlice;
+
+ #[test]
+ // Disable the test under emulation because it fails in Cirrus-CI. Lack
+ // of QEMU support is suspected.
+ #[cfg_attr(qemu, ignore)]
+ pub fn gso() {
+ require_kernel_version!(udp_offload::gso, ">= 4.18");
+
+ // In this test, we send the data and provide a GSO segment size.
+ // Since we are sending the buffer of size 13, six UDP packets
+ // with size 2 and two UDP packet with size 1 will be sent.
+ let segment_size: u16 = 2;
+
+ let sock_addr = SockaddrIn::new(127, 0, 0, 1, 6791);
+ let rsock = socket(
+ AddressFamily::Inet,
+ SockType::Datagram,
+ SockFlag::empty(),
+ None,
+ )
+ .unwrap();
+
+ setsockopt(rsock, UdpGsoSegment, &(segment_size as _))
+ .expect("setsockopt UDP_SEGMENT failed");
+
+ bind(rsock, &sock_addr).unwrap();
+ let ssock = socket(
+ AddressFamily::Inet,
+ SockType::Datagram,
+ SockFlag::empty(),
+ None,
+ )
+ .expect("send socket failed");
+
+ let mut num_packets_received: i32 = 0;
+
+ sendrecv(
+ rsock,
+ ssock,
+ move |s, m, flags| {
+ let iov = [IoSlice::new(m)];
+ let cmsg = ControlMessage::UdpGsoSegments(&segment_size);
+ sendmsg(s, &iov, &[cmsg], flags, Some(&sock_addr))
+ },
+ {
+ let num_packets_received_ref = &mut num_packets_received;
+
+ move |len, _| {
+ // check that we receive UDP packets with payload size
+ // less or equal to segment size
+ assert!(len <= segment_size as usize);
+ *num_packets_received_ref += 1;
+ }
+ },
+ );
+
+ // Buffer size is 13, we will receive six packets of size 2,
+ // and one packet of size 1.
+ assert_eq!(7, num_packets_received);
+ }
+
+ #[test]
+ // Disable the test on emulated platforms because it fails in Cirrus-CI.
+ // Lack of QEMU support is suspected.
+ #[cfg_attr(qemu, ignore)]
+ pub fn gro() {
+ require_kernel_version!(udp_offload::gro, ">= 5.3");
+
+ // It's hard to guarantee receiving GRO packets. Just checking
+ // that `setsockopt` doesn't fail with error
+
+ let rsock = socket(
+ AddressFamily::Inet,
+ SockType::Datagram,
+ SockFlag::empty(),
+ None,
+ )
+ .unwrap();
+
+ setsockopt(rsock, UdpGroSegment, &true)
+ .expect("setsockopt UDP_GRO failed");
+ }
+ }
+
+ #[cfg(any(
+ target_os = "linux",
+ target_os = "android",
+ target_os = "freebsd",
+ target_os = "netbsd",
+ ))]
+ #[test]
+ pub fn udp_sendmmsg() {
+ use std::io::IoSlice;
+
+ let std_sa = SocketAddrV4::from_str("127.0.0.1:6793").unwrap();
+ let std_sa2 = SocketAddrV4::from_str("127.0.0.1:6794").unwrap();
+ let sock_addr = SockaddrIn::from(std_sa);
+ let sock_addr2 = SockaddrIn::from(std_sa2);
+
+ let rsock = socket(
+ AddressFamily::Inet,
+ SockType::Datagram,
+ SockFlag::empty(),
+ None,
+ )
+ .unwrap();
+ bind(rsock, &sock_addr).unwrap();
+ let ssock = socket(
+ AddressFamily::Inet,
+ SockType::Datagram,
+ SockFlag::empty(),
+ None,
+ )
+ .expect("send socket failed");
+
+ let from = sendrecv(
+ rsock,
+ ssock,
+ move |s, m, flags| {
+ let batch_size = 15;
+ let mut iovs = Vec::with_capacity(1 + batch_size);
+ let mut addrs = Vec::with_capacity(1 + batch_size);
+ let mut data = MultiHeaders::preallocate(1 + batch_size, None);
+ let iov = IoSlice::new(m);
+ // first chunk:
+ iovs.push([iov]);
+ addrs.push(Some(sock_addr));
+
+ for _ in 0..batch_size {
+ iovs.push([iov]);
+ addrs.push(Some(sock_addr2));
+ }
+
+ let res = sendmmsg(s, &mut data, &iovs, addrs, [], flags)?;
+ let mut sent_messages = 0;
+ let mut sent_bytes = 0;
+ for item in res {
+ sent_messages += 1;
+ sent_bytes += item.bytes;
+ }
+ //
+ assert_eq!(sent_messages, iovs.len());
+ assert_eq!(sent_bytes, sent_messages * m.len());
+ Ok(sent_messages)
+ },
+ |_, _| {},
+ );
+ // UDP sockets should set the from address
+ assert_eq!(AddressFamily::Inet, from.unwrap().family().unwrap());
+ }
+
+ #[cfg(any(
+ target_os = "linux",
+ target_os = "android",
+ target_os = "freebsd",
+ target_os = "netbsd",
+ ))]
+ #[test]
+ pub fn udp_recvmmsg() {
+ use nix::sys::socket::{recvmmsg, MsgFlags};
+ use std::io::IoSliceMut;
+
+ const NUM_MESSAGES_SENT: usize = 2;
+ const DATA: [u8; 2] = [1, 2];
+
+ let inet_addr = SocketAddrV4::from_str("127.0.0.1:6798").unwrap();
+ let sock_addr = SockaddrIn::from(inet_addr);
+
+ let rsock = socket(
+ AddressFamily::Inet,
+ SockType::Datagram,
+ SockFlag::empty(),
+ None,
+ )
+ .unwrap();
+ bind(rsock, &sock_addr).unwrap();
+ let ssock = socket(
+ AddressFamily::Inet,
+ SockType::Datagram,
+ SockFlag::empty(),
+ None,
+ )
+ .expect("send socket failed");
+
+ let send_thread = thread::spawn(move || {
+ for _ in 0..NUM_MESSAGES_SENT {
+ sendto(ssock, &DATA[..], &sock_addr, MsgFlags::empty())
+ .unwrap();
+ }
+ });
+
+ let mut msgs = std::collections::LinkedList::new();
+
+ // Buffers to receive exactly `NUM_MESSAGES_SENT` messages
+ let mut receive_buffers = [[0u8; 32]; NUM_MESSAGES_SENT];
+ msgs.extend(
+ receive_buffers
+ .iter_mut()
+ .map(|buf| [IoSliceMut::new(&mut buf[..])]),
+ );
+
+ let mut data =
+ MultiHeaders::<SockaddrIn>::preallocate(msgs.len(), None);
+
+ let res: Vec<RecvMsg<SockaddrIn>> =
+ recvmmsg(rsock, &mut data, msgs.iter(), MsgFlags::empty(), None)
+ .expect("recvmmsg")
+ .collect();
+ assert_eq!(res.len(), DATA.len());
+
+ for RecvMsg { address, bytes, .. } in res.into_iter() {
+ assert_eq!(AddressFamily::Inet, address.unwrap().family().unwrap());
+ assert_eq!(DATA.len(), bytes);
+ }
+
+ for buf in &receive_buffers {
+ assert_eq!(&buf[..DATA.len()], DATA);
+ }
+
+ send_thread.join().unwrap();
+ }
+
+ #[cfg(any(
+ target_os = "linux",
+ target_os = "android",
+ target_os = "freebsd",
+ target_os = "netbsd",
+ ))]
+ #[test]
+ pub fn udp_recvmmsg_dontwait_short_read() {
+ use nix::sys::socket::{recvmmsg, MsgFlags};
+ use std::io::IoSliceMut;
+
+ const NUM_MESSAGES_SENT: usize = 2;
+ const DATA: [u8; 4] = [1, 2, 3, 4];
+
+ let inet_addr = SocketAddrV4::from_str("127.0.0.1:6799").unwrap();
+ let sock_addr = SockaddrIn::from(inet_addr);
+
+ let rsock = socket(
+ AddressFamily::Inet,
+ SockType::Datagram,
+ SockFlag::empty(),
+ None,
+ )
+ .unwrap();
+ bind(rsock, &sock_addr).unwrap();
+ let ssock = socket(
+ AddressFamily::Inet,
+ SockType::Datagram,
+ SockFlag::empty(),
+ None,
+ )
+ .expect("send socket failed");
+
+ let send_thread = thread::spawn(move || {
+ for _ in 0..NUM_MESSAGES_SENT {
+ sendto(ssock, &DATA[..], &sock_addr, MsgFlags::empty())
+ .unwrap();
+ }
+ });
+ // Ensure we've sent all the messages before continuing so `recvmmsg`
+ // will return right away
+ send_thread.join().unwrap();
+
+ let mut msgs = std::collections::LinkedList::new();
+
+ // Buffers to receive >`NUM_MESSAGES_SENT` messages to ensure `recvmmsg`
+ // will return when there are fewer than requested messages in the
+ // kernel buffers when using `MSG_DONTWAIT`.
+ let mut receive_buffers = [[0u8; 32]; NUM_MESSAGES_SENT + 2];
+ msgs.extend(
+ receive_buffers
+ .iter_mut()
+ .map(|buf| [IoSliceMut::new(&mut buf[..])]),
+ );
+
+ let mut data = MultiHeaders::<SockaddrIn>::preallocate(
+ NUM_MESSAGES_SENT + 2,
+ None,
+ );
+
+ let res: Vec<RecvMsg<SockaddrIn>> = recvmmsg(
+ rsock,
+ &mut data,
+ msgs.iter(),
+ MsgFlags::MSG_DONTWAIT,
+ None,
+ )
+ .expect("recvmmsg")
+ .collect();
+ assert_eq!(res.len(), NUM_MESSAGES_SENT);
+
+ for RecvMsg { address, bytes, .. } in res.into_iter() {
+ assert_eq!(AddressFamily::Inet, address.unwrap().family().unwrap());
+ assert_eq!(DATA.len(), bytes);
+ }
+
+ for buf in &receive_buffers[..NUM_MESSAGES_SENT] {
+ assert_eq!(&buf[..DATA.len()], DATA);
+ }
+ }
+
+ #[test]
+ pub fn udp_inet6() {
+ let addr = std::net::Ipv6Addr::from_str("::1").unwrap();
+ let rport = 6789;
+ let rstd_sa = SocketAddrV6::new(addr, rport, 0, 0);
+ let raddr = SockaddrIn6::from(rstd_sa);
+ let sport = 6790;
+ let sstd_sa = SocketAddrV6::new(addr, sport, 0, 0);
+ let saddr = SockaddrIn6::from(sstd_sa);
+ let rsock = socket(
+ AddressFamily::Inet6,
+ SockType::Datagram,
+ SockFlag::empty(),
+ None,
+ )
+ .expect("receive socket failed");
+ match bind(rsock, &raddr) {
+ Err(Errno::EADDRNOTAVAIL) => {
+ println!("IPv6 not available, skipping test.");
+ return;
+ }
+ Err(e) => panic!("bind: {}", e),
+ Ok(()) => (),
+ }
+ let ssock = socket(
+ AddressFamily::Inet6,
+ SockType::Datagram,
+ SockFlag::empty(),
+ None,
+ )
+ .expect("send socket failed");
+ bind(ssock, &saddr).unwrap();
+ let from = sendrecv(
+ rsock,
+ ssock,
+ move |s, m, flags| sendto(s, m, &raddr, flags),
+ |_, _| {},
+ );
+ assert_eq!(AddressFamily::Inet6, from.unwrap().family().unwrap());
+ let osent_addr = from.unwrap();
+ let sent_addr = osent_addr.as_sockaddr_in6().unwrap();
+ assert_eq!(sent_addr.ip(), addr);
+ assert_eq!(sent_addr.port(), sport);
+ }
+}
+
+// Test error handling of our recvmsg wrapper
+#[test]
+pub fn test_recvmsg_ebadf() {
+ use nix::errno::Errno;
+ use nix::sys::socket::{recvmsg, MsgFlags};
+ use std::io::IoSliceMut;
+
+ let mut buf = [0u8; 5];
+ let mut iov = [IoSliceMut::new(&mut buf[..])];
+
+ let fd = -1; // Bad file descriptor
+ let r = recvmsg::<()>(fd, &mut iov, None, MsgFlags::empty());
+
+ assert_eq!(r.err().unwrap(), Errno::EBADF);
+}
+
+// Disable the test on emulated platforms due to a bug in QEMU versions <
+// 2.12.0. https://bugs.launchpad.net/qemu/+bug/1701808
+#[cfg_attr(qemu, ignore)]
+#[test]
+pub fn test_scm_rights() {
+ use nix::sys::socket::{
+ recvmsg, sendmsg, socketpair, AddressFamily, ControlMessage,
+ ControlMessageOwned, MsgFlags, SockFlag, SockType,
+ };
+ use nix::unistd::{close, pipe, read, write};
+ use std::io::{IoSlice, IoSliceMut};
+
+ let (fd1, fd2) = socketpair(
+ AddressFamily::Unix,
+ SockType::Stream,
+ None,
+ SockFlag::empty(),
+ )
+ .unwrap();
+ let (r, w) = pipe().unwrap();
+ let mut received_r: Option<RawFd> = None;
+
+ {
+ let iov = [IoSlice::new(b"hello")];
+ let fds = [r];
+ let cmsg = ControlMessage::ScmRights(&fds);
+ assert_eq!(
+ sendmsg::<()>(fd1, &iov, &[cmsg], MsgFlags::empty(), None).unwrap(),
+ 5
+ );
+ close(r).unwrap();
+ close(fd1).unwrap();
+ }
+
+ {
+ let mut buf = [0u8; 5];
+
+ let mut iov = [IoSliceMut::new(&mut buf[..])];
+ let mut cmsgspace = cmsg_space!([RawFd; 1]);
+ let msg = recvmsg::<()>(
+ fd2,
+ &mut iov,
+ Some(&mut cmsgspace),
+ MsgFlags::empty(),
+ )
+ .unwrap();
+
+ for cmsg in msg.cmsgs() {
+ if let ControlMessageOwned::ScmRights(fd) = cmsg {
+ assert_eq!(received_r, None);
+ assert_eq!(fd.len(), 1);
+ received_r = Some(fd[0]);
+ } else {
+ panic!("unexpected cmsg");
+ }
+ }
+ assert_eq!(msg.bytes, 5);
+ assert!(!msg
+ .flags
+ .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC));
+ close(fd2).unwrap();
+ }
+
+ let received_r = received_r.expect("Did not receive passed fd");
+ // Ensure that the received file descriptor works
+ write(w, b"world").unwrap();
+ let mut buf = [0u8; 5];
+ read(received_r, &mut buf).unwrap();
+ assert_eq!(&buf[..], b"world");
+ close(received_r).unwrap();
+ close(w).unwrap();
+}
+
+// Disable the test on emulated platforms due to not enabled support of AF_ALG in QEMU from rust cross
+#[cfg(any(target_os = "linux", target_os = "android"))]
+#[cfg_attr(qemu, ignore)]
+#[test]
+pub fn test_af_alg_cipher() {
+ use nix::sys::socket::sockopt::AlgSetKey;
+ use nix::sys::socket::{
+ accept, bind, sendmsg, setsockopt, socket, AddressFamily, AlgAddr,
+ ControlMessage, MsgFlags, SockFlag, SockType,
+ };
+ use nix::unistd::read;
+ use std::io::IoSlice;
+
+ skip_if_cirrus!("Fails for an unknown reason Cirrus CI. Bug #1352");
+ // Travis's seccomp profile blocks AF_ALG
+ // https://docs.docker.com/engine/security/seccomp/
+ skip_if_seccomp!(test_af_alg_cipher);
+
+ let alg_type = "skcipher";
+ let alg_name = "ctr-aes-aesni";
+ // 256-bits secret key
+ let key = vec![0u8; 32];
+ // 16-bytes IV
+ let iv_len = 16;
+ let iv = vec![1u8; iv_len];
+ // 256-bytes plain payload
+ let payload_len = 256;
+ let payload = vec![2u8; payload_len];
+
+ let sock = socket(
+ AddressFamily::Alg,
+ SockType::SeqPacket,
+ SockFlag::empty(),
+ None,
+ )
+ .expect("socket failed");
+
+ let sockaddr = AlgAddr::new(alg_type, alg_name);
+ bind(sock, &sockaddr).expect("bind failed");
+
+ assert_eq!(sockaddr.alg_name().to_string_lossy(), alg_name);
+ assert_eq!(sockaddr.alg_type().to_string_lossy(), alg_type);
+
+ setsockopt(sock, AlgSetKey::default(), &key).expect("setsockopt");
+ let session_socket = accept(sock).expect("accept failed");
+
+ let msgs = [
+ ControlMessage::AlgSetOp(&libc::ALG_OP_ENCRYPT),
+ ControlMessage::AlgSetIv(iv.as_slice()),
+ ];
+ let iov = IoSlice::new(&payload);
+ sendmsg::<()>(session_socket, &[iov], &msgs, MsgFlags::empty(), None)
+ .expect("sendmsg encrypt");
+
+ // allocate buffer for encrypted data
+ let mut encrypted = vec![0u8; payload_len];
+ let num_bytes = read(session_socket, &mut encrypted).expect("read encrypt");
+ assert_eq!(num_bytes, payload_len);
+
+ let iov = IoSlice::new(&encrypted);
+
+ let iv = vec![1u8; iv_len];
+
+ let msgs = [
+ ControlMessage::AlgSetOp(&libc::ALG_OP_DECRYPT),
+ ControlMessage::AlgSetIv(iv.as_slice()),
+ ];
+ sendmsg::<()>(session_socket, &[iov], &msgs, MsgFlags::empty(), None)
+ .expect("sendmsg decrypt");
+
+ // allocate buffer for decrypted data
+ let mut decrypted = vec![0u8; payload_len];
+ let num_bytes = read(session_socket, &mut decrypted).expect("read decrypt");
+
+ assert_eq!(num_bytes, payload_len);
+ assert_eq!(decrypted, payload);
+}
+
+// Disable the test on emulated platforms due to not enabled support of AF_ALG
+// in QEMU from rust cross
+#[cfg(any(target_os = "linux", target_os = "android"))]
+#[cfg_attr(qemu, ignore)]
+#[test]
+pub fn test_af_alg_aead() {
+ use libc::{ALG_OP_DECRYPT, ALG_OP_ENCRYPT};
+ use nix::fcntl::{fcntl, FcntlArg, OFlag};
+ use nix::sys::socket::sockopt::{AlgSetAeadAuthSize, AlgSetKey};
+ use nix::sys::socket::{
+ accept, bind, sendmsg, setsockopt, socket, AddressFamily, AlgAddr,
+ ControlMessage, MsgFlags, SockFlag, SockType,
+ };
+ use nix::unistd::{close, read};
+ use std::io::IoSlice;
+
+ skip_if_cirrus!("Fails for an unknown reason Cirrus CI. Bug #1352");
+ // Travis's seccomp profile blocks AF_ALG
+ // https://docs.docker.com/engine/security/seccomp/
+ skip_if_seccomp!(test_af_alg_aead);
+
+ let auth_size = 4usize;
+ let assoc_size = 16u32;
+
+ let alg_type = "aead";
+ let alg_name = "gcm(aes)";
+ // 256-bits secret key
+ let key = vec![0u8; 32];
+ // 12-bytes IV
+ let iv_len = 12;
+ let iv = vec![1u8; iv_len];
+ // 256-bytes plain payload
+ let payload_len = 256;
+ let mut payload =
+ vec![2u8; payload_len + (assoc_size as usize) + auth_size];
+
+ for i in 0..assoc_size {
+ payload[i as usize] = 10;
+ }
+
+ let len = payload.len();
+
+ for i in 0..auth_size {
+ payload[len - 1 - i] = 0;
+ }
+
+ let sock = socket(
+ AddressFamily::Alg,
+ SockType::SeqPacket,
+ SockFlag::empty(),
+ None,
+ )
+ .expect("socket failed");
+
+ let sockaddr = AlgAddr::new(alg_type, alg_name);
+ bind(sock, &sockaddr).expect("bind failed");
+
+ setsockopt(sock, AlgSetAeadAuthSize, &auth_size)
+ .expect("setsockopt AlgSetAeadAuthSize");
+ setsockopt(sock, AlgSetKey::default(), &key).expect("setsockopt AlgSetKey");
+ let session_socket = accept(sock).expect("accept failed");
+
+ let msgs = [
+ ControlMessage::AlgSetOp(&ALG_OP_ENCRYPT),
+ ControlMessage::AlgSetIv(iv.as_slice()),
+ ControlMessage::AlgSetAeadAssoclen(&assoc_size),
+ ];
+
+ let iov = IoSlice::new(&payload);
+ sendmsg::<()>(session_socket, &[iov], &msgs, MsgFlags::empty(), None)
+ .expect("sendmsg encrypt");
+
+ // allocate buffer for encrypted data
+ let mut encrypted =
+ vec![0u8; (assoc_size as usize) + payload_len + auth_size];
+ let num_bytes = read(session_socket, &mut encrypted).expect("read encrypt");
+ assert_eq!(num_bytes, payload_len + auth_size + (assoc_size as usize));
+ close(session_socket).expect("close");
+
+ for i in 0..assoc_size {
+ encrypted[i as usize] = 10;
+ }
+
+ let iov = IoSlice::new(&encrypted);
+
+ let iv = vec![1u8; iv_len];
+
+ let session_socket = accept(sock).expect("accept failed");
+
+ let msgs = [
+ ControlMessage::AlgSetOp(&ALG_OP_DECRYPT),
+ ControlMessage::AlgSetIv(iv.as_slice()),
+ ControlMessage::AlgSetAeadAssoclen(&assoc_size),
+ ];
+ sendmsg::<()>(session_socket, &[iov], &msgs, MsgFlags::empty(), None)
+ .expect("sendmsg decrypt");
+
+ // allocate buffer for decrypted data
+ let mut decrypted =
+ vec![0u8; payload_len + (assoc_size as usize) + auth_size];
+ // Starting with kernel 4.9, the interface changed slightly such that the
+ // authentication tag memory is only needed in the output buffer for encryption
+ // and in the input buffer for decryption.
+ // Do not block on read, as we may have fewer bytes than buffer size
+ fcntl(session_socket, FcntlArg::F_SETFL(OFlag::O_NONBLOCK))
+ .expect("fcntl non_blocking");
+ let num_bytes = read(session_socket, &mut decrypted).expect("read decrypt");
+
+ assert!(num_bytes >= payload_len + (assoc_size as usize));
+ assert_eq!(
+ decrypted[(assoc_size as usize)..(payload_len + (assoc_size as usize))],
+ payload[(assoc_size as usize)..payload_len + (assoc_size as usize)]
+ );
+}
+
+// Verify `ControlMessage::Ipv4PacketInfo` for `sendmsg`.
+// This creates a (udp) socket bound to localhost, then sends a message to
+// itself but uses Ipv4PacketInfo to force the source address to be localhost.
+//
+// This would be a more interesting test if we could assume that the test host
+// has more than one IP address (since we could select a different address to
+// test from).
+#[cfg(any(target_os = "linux", target_os = "macos", target_os = "netbsd"))]
+#[test]
+pub fn test_sendmsg_ipv4packetinfo() {
+ use cfg_if::cfg_if;
+ use nix::sys::socket::{
+ bind, sendmsg, socket, AddressFamily, ControlMessage, MsgFlags,
+ SockFlag, SockType, SockaddrIn,
+ };
+ use std::io::IoSlice;
+
+ let sock = socket(
+ AddressFamily::Inet,
+ SockType::Datagram,
+ SockFlag::empty(),
+ None,
+ )
+ .expect("socket failed");
+
+ let sock_addr = SockaddrIn::new(127, 0, 0, 1, 4000);
+
+ bind(sock, &sock_addr).expect("bind failed");
+
+ let slice = [1u8, 2, 3, 4, 5, 6, 7, 8];
+ let iov = [IoSlice::new(&slice)];
+
+ cfg_if! {
+ if #[cfg(target_os = "netbsd")] {
+ let pi = libc::in_pktinfo {
+ ipi_ifindex: 0, /* Unspecified interface */
+ ipi_addr: libc::in_addr { s_addr: 0 },
+ };
+ } else {
+ let pi = libc::in_pktinfo {
+ ipi_ifindex: 0, /* Unspecified interface */
+ ipi_addr: libc::in_addr { s_addr: 0 },
+ ipi_spec_dst: sock_addr.as_ref().sin_addr,
+ };
+ }
+ }
+
+ let cmsg = [ControlMessage::Ipv4PacketInfo(&pi)];
+
+ sendmsg(sock, &iov, &cmsg, MsgFlags::empty(), Some(&sock_addr))
+ .expect("sendmsg");
+}
+
+// Verify `ControlMessage::Ipv6PacketInfo` for `sendmsg`.
+// This creates a (udp) socket bound to ip6-localhost, then sends a message to
+// itself but uses Ipv6PacketInfo to force the source address to be
+// ip6-localhost.
+//
+// This would be a more interesting test if we could assume that the test host
+// has more than one IP address (since we could select a different address to
+// test from).
+#[cfg(any(
+ target_os = "linux",
+ target_os = "macos",
+ target_os = "netbsd",
+ target_os = "freebsd"
+))]
+#[test]
+pub fn test_sendmsg_ipv6packetinfo() {
+ use nix::errno::Errno;
+ use nix::sys::socket::{
+ bind, sendmsg, socket, AddressFamily, ControlMessage, MsgFlags,
+ SockFlag, SockType, SockaddrIn6,
+ };
+ use std::io::IoSlice;
+
+ let sock = socket(
+ AddressFamily::Inet6,
+ SockType::Datagram,
+ SockFlag::empty(),
+ None,
+ )
+ .expect("socket failed");
+
+ let std_sa = SocketAddrV6::from_str("[::1]:6000").unwrap();
+ let sock_addr: SockaddrIn6 = SockaddrIn6::from(std_sa);
+
+ if let Err(Errno::EADDRNOTAVAIL) = bind(sock, &sock_addr) {
+ println!("IPv6 not available, skipping test.");
+ return;
+ }
+
+ let slice = [1u8, 2, 3, 4, 5, 6, 7, 8];
+ let iov = [IoSlice::new(&slice)];
+
+ let pi = libc::in6_pktinfo {
+ ipi6_ifindex: 0, /* Unspecified interface */
+ ipi6_addr: sock_addr.as_ref().sin6_addr,
+ };
+
+ let cmsg = [ControlMessage::Ipv6PacketInfo(&pi)];
+
+ sendmsg::<SockaddrIn6>(
+ sock,
+ &iov,
+ &cmsg,
+ MsgFlags::empty(),
+ Some(&sock_addr),
+ )
+ .expect("sendmsg");
+}
+
+// Verify that ControlMessage::Ipv4SendSrcAddr works for sendmsg. This
+// creates a UDP socket bound to all local interfaces (0.0.0.0). It then
+// sends message to itself at 127.0.0.1 while explicitly specifying
+// 127.0.0.1 as the source address through an Ipv4SendSrcAddr
+// (IP_SENDSRCADDR) control message.
+//
+// Note that binding to 0.0.0.0 is *required* on FreeBSD; sendmsg
+// returns EINVAL otherwise. (See FreeBSD's ip(4) man page.)
+#[cfg(any(
+ target_os = "netbsd",
+ target_os = "freebsd",
+ target_os = "openbsd",
+ target_os = "dragonfly",
+))]
+#[test]
+pub fn test_sendmsg_ipv4sendsrcaddr() {
+ use nix::sys::socket::{
+ bind, sendmsg, socket, AddressFamily, ControlMessage, MsgFlags,
+ SockFlag, SockType, SockaddrIn,
+ };
+ use std::io::IoSlice;
+
+ let sock = socket(
+ AddressFamily::Inet,
+ SockType::Datagram,
+ SockFlag::empty(),
+ None,
+ )
+ .expect("socket failed");
+
+ let unspec_sock_addr = SockaddrIn::new(0, 0, 0, 0, 0);
+ bind(sock, &unspec_sock_addr).expect("bind failed");
+ let bound_sock_addr: SockaddrIn = getsockname(sock).unwrap();
+ let localhost_sock_addr: SockaddrIn =
+ SockaddrIn::new(127, 0, 0, 1, bound_sock_addr.port());
+
+ let slice = [1u8, 2, 3, 4, 5, 6, 7, 8];
+ let iov = [IoSlice::new(&slice)];
+ let cmsg = [ControlMessage::Ipv4SendSrcAddr(
+ &localhost_sock_addr.as_ref().sin_addr,
+ )];
+
+ sendmsg(
+ sock,
+ &iov,
+ &cmsg,
+ MsgFlags::empty(),
+ Some(&localhost_sock_addr),
+ )
+ .expect("sendmsg");
+}
+
+/// Tests that passing multiple fds using a single `ControlMessage` works.
+// Disable the test on emulated platforms due to a bug in QEMU versions <
+// 2.12.0. https://bugs.launchpad.net/qemu/+bug/1701808
+#[cfg_attr(qemu, ignore)]
+#[test]
+fn test_scm_rights_single_cmsg_multiple_fds() {
+ use nix::sys::socket::{
+ recvmsg, sendmsg, ControlMessage, ControlMessageOwned, MsgFlags,
+ };
+ use std::io::{IoSlice, IoSliceMut};
+ use std::os::unix::io::{AsRawFd, RawFd};
+ use std::os::unix::net::UnixDatagram;
+ use std::thread;
+
+ let (send, receive) = UnixDatagram::pair().unwrap();
+ let thread = thread::spawn(move || {
+ let mut buf = [0u8; 8];
+ let mut iovec = [IoSliceMut::new(&mut buf)];
+
+ let mut space = cmsg_space!([RawFd; 2]);
+ let msg = recvmsg::<()>(
+ receive.as_raw_fd(),
+ &mut iovec,
+ Some(&mut space),
+ MsgFlags::empty(),
+ )
+ .unwrap();
+ assert!(!msg
+ .flags
+ .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC));
+
+ let mut cmsgs = msg.cmsgs();
+ match cmsgs.next() {
+ Some(ControlMessageOwned::ScmRights(fds)) => {
+ assert_eq!(
+ fds.len(),
+ 2,
+ "unexpected fd count (expected 2 fds, got {})",
+ fds.len()
+ );
+ }
+ _ => panic!(),
+ }
+ assert!(cmsgs.next().is_none(), "unexpected control msg");
+
+ assert_eq!(msg.bytes, 8);
+ assert_eq!(*iovec[0], [1u8, 2, 3, 4, 5, 6, 7, 8]);
+ });
+
+ let slice = [1u8, 2, 3, 4, 5, 6, 7, 8];
+ let iov = [IoSlice::new(&slice)];
+ let fds = [libc::STDIN_FILENO, libc::STDOUT_FILENO]; // pass stdin and stdout
+ let cmsg = [ControlMessage::ScmRights(&fds)];
+ sendmsg::<()>(send.as_raw_fd(), &iov, &cmsg, MsgFlags::empty(), None)
+ .unwrap();
+ thread.join().unwrap();
+}
+
+// Verify `sendmsg` builds a valid `msghdr` when passing an empty
+// `cmsgs` argument. This should result in a msghdr with a nullptr
+// msg_control field and a msg_controllen of 0 when calling into the
+// raw `sendmsg`.
+#[test]
+pub fn test_sendmsg_empty_cmsgs() {
+ use nix::sys::socket::{
+ recvmsg, sendmsg, socketpair, AddressFamily, MsgFlags, SockFlag,
+ SockType,
+ };
+ use nix::unistd::close;
+ use std::io::{IoSlice, IoSliceMut};
+
+ let (fd1, fd2) = socketpair(
+ AddressFamily::Unix,
+ SockType::Stream,
+ None,
+ SockFlag::empty(),
+ )
+ .unwrap();
+
+ {
+ let iov = [IoSlice::new(b"hello")];
+ assert_eq!(
+ sendmsg::<()>(fd1, &iov, &[], MsgFlags::empty(), None).unwrap(),
+ 5
+ );
+ close(fd1).unwrap();
+ }
+
+ {
+ let mut buf = [0u8; 5];
+ let mut iov = [IoSliceMut::new(&mut buf[..])];
+
+ let mut cmsgspace = cmsg_space!([RawFd; 1]);
+ let msg = recvmsg::<()>(
+ fd2,
+ &mut iov,
+ Some(&mut cmsgspace),
+ MsgFlags::empty(),
+ )
+ .unwrap();
+
+ for _ in msg.cmsgs() {
+ panic!("unexpected cmsg");
+ }
+ assert!(!msg
+ .flags
+ .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC));
+ assert_eq!(msg.bytes, 5);
+ close(fd2).unwrap();
+ }
+}
+
+#[cfg(any(
+ target_os = "android",
+ target_os = "linux",
+ target_os = "freebsd",
+ target_os = "dragonfly",
+))]
+#[test]
+fn test_scm_credentials() {
+ use nix::sys::socket::{
+ recvmsg, sendmsg, socketpair, AddressFamily, ControlMessage,
+ ControlMessageOwned, MsgFlags, SockFlag, SockType, UnixCredentials,
+ };
+ #[cfg(any(target_os = "android", target_os = "linux"))]
+ use nix::sys::socket::{setsockopt, sockopt::PassCred};
+ use nix::unistd::{close, getgid, getpid, getuid};
+ use std::io::{IoSlice, IoSliceMut};
+
+ let (send, recv) = socketpair(
+ AddressFamily::Unix,
+ SockType::Stream,
+ None,
+ SockFlag::empty(),
+ )
+ .unwrap();
+ #[cfg(any(target_os = "android", target_os = "linux"))]
+ setsockopt(recv, PassCred, &true).unwrap();
+
+ {
+ let iov = [IoSlice::new(b"hello")];
+ #[cfg(any(target_os = "android", target_os = "linux"))]
+ let cred = UnixCredentials::new();
+ #[cfg(any(target_os = "android", target_os = "linux"))]
+ let cmsg = ControlMessage::ScmCredentials(&cred);
+ #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
+ let cmsg = ControlMessage::ScmCreds;
+ assert_eq!(
+ sendmsg::<()>(send, &iov, &[cmsg], MsgFlags::empty(), None)
+ .unwrap(),
+ 5
+ );
+ close(send).unwrap();
+ }
+
+ {
+ let mut buf = [0u8; 5];
+ let mut iov = [IoSliceMut::new(&mut buf[..])];
+
+ let mut cmsgspace = cmsg_space!(UnixCredentials);
+ let msg = recvmsg::<()>(
+ recv,
+ &mut iov,
+ Some(&mut cmsgspace),
+ MsgFlags::empty(),
+ )
+ .unwrap();
+ let mut received_cred = None;
+
+ for cmsg in msg.cmsgs() {
+ let cred = match cmsg {
+ #[cfg(any(target_os = "android", target_os = "linux"))]
+ ControlMessageOwned::ScmCredentials(cred) => cred,
+ #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
+ ControlMessageOwned::ScmCreds(cred) => cred,
+ other => panic!("unexpected cmsg {:?}", other),
+ };
+ assert!(received_cred.is_none());
+ assert_eq!(cred.pid(), getpid().as_raw());
+ assert_eq!(cred.uid(), getuid().as_raw());
+ assert_eq!(cred.gid(), getgid().as_raw());
+ received_cred = Some(cred);
+ }
+ received_cred.expect("no creds received");
+ assert_eq!(msg.bytes, 5);
+ assert!(!msg
+ .flags
+ .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC));
+ close(recv).unwrap();
+ }
+}
+
+/// Ensure that we can send `SCM_CREDENTIALS` and `SCM_RIGHTS` with a single
+/// `sendmsg` call.
+#[cfg(any(target_os = "android", target_os = "linux"))]
+// qemu's handling of multiple cmsgs is bugged, ignore tests under emulation
+// see https://bugs.launchpad.net/qemu/+bug/1781280
+#[cfg_attr(qemu, ignore)]
+#[test]
+fn test_scm_credentials_and_rights() {
+ let space = cmsg_space!(libc::ucred, RawFd);
+ test_impl_scm_credentials_and_rights(space);
+}
+
+/// Ensure that passing a an oversized control message buffer to recvmsg
+/// still works.
+#[cfg(any(target_os = "android", target_os = "linux"))]
+// qemu's handling of multiple cmsgs is bugged, ignore tests under emulation
+// see https://bugs.launchpad.net/qemu/+bug/1781280
+#[cfg_attr(qemu, ignore)]
+#[test]
+fn test_too_large_cmsgspace() {
+ let space = vec![0u8; 1024];
+ test_impl_scm_credentials_and_rights(space);
+}
+
+#[cfg(any(target_os = "android", target_os = "linux"))]
+fn test_impl_scm_credentials_and_rights(mut space: Vec<u8>) {
+ use libc::ucred;
+ use nix::sys::socket::sockopt::PassCred;
+ use nix::sys::socket::{
+ recvmsg, sendmsg, setsockopt, socketpair, ControlMessage,
+ ControlMessageOwned, MsgFlags, SockFlag, SockType,
+ };
+ use nix::unistd::{close, getgid, getpid, getuid, pipe, write};
+ use std::io::{IoSlice, IoSliceMut};
+
+ let (send, recv) = socketpair(
+ AddressFamily::Unix,
+ SockType::Stream,
+ None,
+ SockFlag::empty(),
+ )
+ .unwrap();
+ setsockopt(recv, PassCred, &true).unwrap();
+
+ let (r, w) = pipe().unwrap();
+ let mut received_r: Option<RawFd> = None;
+
+ {
+ let iov = [IoSlice::new(b"hello")];
+ let cred = ucred {
+ pid: getpid().as_raw(),
+ uid: getuid().as_raw(),
+ gid: getgid().as_raw(),
+ }
+ .into();
+ let fds = [r];
+ let cmsgs = [
+ ControlMessage::ScmCredentials(&cred),
+ ControlMessage::ScmRights(&fds),
+ ];
+ assert_eq!(
+ sendmsg::<()>(send, &iov, &cmsgs, MsgFlags::empty(), None).unwrap(),
+ 5
+ );
+ close(r).unwrap();
+ close(send).unwrap();
+ }
+
+ {
+ let mut buf = [0u8; 5];
+ let mut iov = [IoSliceMut::new(&mut buf[..])];
+ let msg =
+ recvmsg::<()>(recv, &mut iov, Some(&mut space), MsgFlags::empty())
+ .unwrap();
+ let mut received_cred = None;
+
+ assert_eq!(msg.cmsgs().count(), 2, "expected 2 cmsgs");
+
+ for cmsg in msg.cmsgs() {
+ match cmsg {
+ ControlMessageOwned::ScmRights(fds) => {
+ assert_eq!(received_r, None, "already received fd");
+ assert_eq!(fds.len(), 1);
+ received_r = Some(fds[0]);
+ }
+ ControlMessageOwned::ScmCredentials(cred) => {
+ assert!(received_cred.is_none());
+ assert_eq!(cred.pid(), getpid().as_raw());
+ assert_eq!(cred.uid(), getuid().as_raw());
+ assert_eq!(cred.gid(), getgid().as_raw());
+ received_cred = Some(cred);
+ }
+ _ => panic!("unexpected cmsg"),
+ }
+ }
+ received_cred.expect("no creds received");
+ assert_eq!(msg.bytes, 5);
+ assert!(!msg
+ .flags
+ .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC));
+ close(recv).unwrap();
+ }
+
+ let received_r = received_r.expect("Did not receive passed fd");
+ // Ensure that the received file descriptor works
+ write(w, b"world").unwrap();
+ let mut buf = [0u8; 5];
+ read(received_r, &mut buf).unwrap();
+ assert_eq!(&buf[..], b"world");
+ close(received_r).unwrap();
+ close(w).unwrap();
+}
+
+// Test creating and using named unix domain sockets
+#[test]
+pub fn test_named_unixdomain() {
+ use nix::sys::socket::{accept, bind, connect, listen, socket, UnixAddr};
+ use nix::sys::socket::{SockFlag, SockType};
+ use nix::unistd::{close, read, write};
+ use std::thread;
+
+ let tempdir = tempfile::tempdir().unwrap();
+ let sockname = tempdir.path().join("sock");
+ let s1 = socket(
+ AddressFamily::Unix,
+ SockType::Stream,
+ SockFlag::empty(),
+ None,
+ )
+ .expect("socket failed");
+ let sockaddr = UnixAddr::new(&sockname).unwrap();
+ bind(s1, &sockaddr).expect("bind failed");
+ listen(s1, 10).expect("listen failed");
+
+ let thr = thread::spawn(move || {
+ let s2 = socket(
+ AddressFamily::Unix,
+ SockType::Stream,
+ SockFlag::empty(),
+ None,
+ )
+ .expect("socket failed");
+ connect(s2, &sockaddr).expect("connect failed");
+ write(s2, b"hello").expect("write failed");
+ close(s2).unwrap();
+ });
+
+ let s3 = accept(s1).expect("accept failed");
+
+ let mut buf = [0; 5];
+ read(s3, &mut buf).unwrap();
+ close(s3).unwrap();
+ close(s1).unwrap();
+ thr.join().unwrap();
+
+ assert_eq!(&buf[..], b"hello");
+}
+
+// Test using unnamed unix domain addresses
+#[cfg(any(target_os = "android", target_os = "linux"))]
+#[test]
+pub fn test_unnamed_unixdomain() {
+ use nix::sys::socket::{getsockname, socketpair};
+ use nix::sys::socket::{SockFlag, SockType};
+ use nix::unistd::close;
+
+ let (fd_1, fd_2) = socketpair(
+ AddressFamily::Unix,
+ SockType::Stream,
+ None,
+ SockFlag::empty(),
+ )
+ .expect("socketpair failed");
+
+ let addr_1: UnixAddr = getsockname(fd_1).expect("getsockname failed");
+ assert!(addr_1.is_unnamed());
+
+ close(fd_1).unwrap();
+ close(fd_2).unwrap();
+}
+
+// Test creating and using unnamed unix domain addresses for autobinding sockets
+#[cfg(any(target_os = "android", target_os = "linux"))]
+#[test]
+pub fn test_unnamed_unixdomain_autobind() {
+ use nix::sys::socket::{bind, getsockname, socket};
+ use nix::sys::socket::{SockFlag, SockType};
+ use nix::unistd::close;
+
+ let fd = socket(
+ AddressFamily::Unix,
+ SockType::Stream,
+ SockFlag::empty(),
+ None,
+ )
+ .expect("socket failed");
+
+ // unix(7): "If a bind(2) call specifies addrlen as `sizeof(sa_family_t)`, or [...], then the
+ // socket is autobound to an abstract address"
+ bind(fd, &UnixAddr::new_unnamed()).expect("bind failed");
+
+ let addr: UnixAddr = getsockname(fd).expect("getsockname failed");
+ let addr = addr.as_abstract().unwrap();
+
+ // changed from 8 to 5 bytes in Linux 2.3.15, and rust's minimum supported Linux version is 3.2
+ // (as of 2022-11)
+ assert_eq!(addr.len(), 5);
+
+ close(fd).unwrap();
+}
+
+// Test creating and using named system control sockets
+#[cfg(any(target_os = "macos", target_os = "ios"))]
+#[test]
+pub fn test_syscontrol() {
+ use nix::errno::Errno;
+ use nix::sys::socket::{
+ socket, SockFlag, SockProtocol, SockType, SysControlAddr,
+ };
+
+ let fd = socket(
+ AddressFamily::System,
+ SockType::Datagram,
+ SockFlag::empty(),
+ SockProtocol::KextControl,
+ )
+ .expect("socket failed");
+ SysControlAddr::from_name(fd, "com.apple.net.utun_control", 0)
+ .expect("resolving sys_control name failed");
+ assert_eq!(
+ SysControlAddr::from_name(fd, "foo.bar.lol", 0).err(),
+ Some(Errno::ENOENT)
+ );
+
+ // requires root privileges
+ // connect(fd, &sockaddr).expect("connect failed");
+}
+
+#[cfg(any(
+ target_os = "android",
+ target_os = "freebsd",
+ target_os = "ios",
+ target_os = "linux",
+ target_os = "macos",
+ target_os = "netbsd",
+ target_os = "openbsd",
+))]
+fn loopback_address(
+ family: AddressFamily,
+) -> Option<nix::ifaddrs::InterfaceAddress> {
+ use nix::ifaddrs::getifaddrs;
+ use nix::net::if_::*;
+ use nix::sys::socket::SockaddrLike;
+ use std::io;
+ use std::io::Write;
+
+ let mut addrs = match getifaddrs() {
+ Ok(iter) => iter,
+ Err(e) => {
+ let stdioerr = io::stderr();
+ let mut handle = stdioerr.lock();
+ writeln!(handle, "getifaddrs: {:?}", e).unwrap();
+ return None;
+ }
+ };
+ // return first address matching family
+ addrs.find(|ifaddr| {
+ ifaddr.flags.contains(InterfaceFlags::IFF_LOOPBACK)
+ && ifaddr.address.as_ref().and_then(SockaddrLike::family)
+ == Some(family)
+ })
+}
+
+#[cfg(any(
+ target_os = "android",
+ target_os = "ios",
+ target_os = "linux",
+ target_os = "macos",
+ target_os = "netbsd",
+))]
+// qemu doesn't seem to be emulating this correctly in these architectures
+#[cfg_attr(
+ all(
+ qemu,
+ any(
+ target_arch = "mips",
+ target_arch = "mips64",
+ target_arch = "powerpc64",
+ )
+ ),
+ ignore
+)]
+#[test]
+pub fn test_recv_ipv4pktinfo() {
+ use nix::net::if_::*;
+ use nix::sys::socket::sockopt::Ipv4PacketInfo;
+ use nix::sys::socket::{bind, SockFlag, SockType, SockaddrIn};
+ use nix::sys::socket::{getsockname, setsockopt, socket};
+ use nix::sys::socket::{recvmsg, sendmsg, ControlMessageOwned, MsgFlags};
+ use std::io::{IoSlice, IoSliceMut};
+
+ let lo_ifaddr = loopback_address(AddressFamily::Inet);
+ let (lo_name, lo) = match lo_ifaddr {
+ Some(ifaddr) => (
+ ifaddr.interface_name,
+ ifaddr.address.expect("Expect IPv4 address on interface"),
+ ),
+ None => return,
+ };
+ let receive = socket(
+ AddressFamily::Inet,
+ SockType::Datagram,
+ SockFlag::empty(),
+ None,
+ )
+ .expect("receive socket failed");
+ bind(receive, &lo).expect("bind failed");
+ let sa: SockaddrIn = getsockname(receive).expect("getsockname failed");
+ setsockopt(receive, Ipv4PacketInfo, &true).expect("setsockopt failed");
+
+ {
+ let slice = [1u8, 2, 3, 4, 5, 6, 7, 8];
+ let iov = [IoSlice::new(&slice)];
+
+ let send = socket(
+ AddressFamily::Inet,
+ SockType::Datagram,
+ SockFlag::empty(),
+ None,
+ )
+ .expect("send socket failed");
+ sendmsg(send, &iov, &[], MsgFlags::empty(), Some(&sa))
+ .expect("sendmsg failed");
+ }
+
+ {
+ let mut buf = [0u8; 8];
+ let mut iovec = [IoSliceMut::new(&mut buf)];
+
+ let mut space = cmsg_space!(libc::in_pktinfo);
+ let msg = recvmsg::<()>(
+ receive,
+ &mut iovec,
+ Some(&mut space),
+ MsgFlags::empty(),
+ )
+ .expect("recvmsg failed");
+ assert!(!msg
+ .flags
+ .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC));
+
+ let mut cmsgs = msg.cmsgs();
+ if let Some(ControlMessageOwned::Ipv4PacketInfo(pktinfo)) = cmsgs.next()
+ {
+ let i = if_nametoindex(lo_name.as_bytes()).expect("if_nametoindex");
+ assert_eq!(
+ pktinfo.ipi_ifindex as libc::c_uint, i,
+ "unexpected ifindex (expected {}, got {})",
+ i, pktinfo.ipi_ifindex
+ );
+ }
+ assert!(cmsgs.next().is_none(), "unexpected additional control msg");
+ assert_eq!(msg.bytes, 8);
+ assert_eq!(*iovec[0], [1u8, 2, 3, 4, 5, 6, 7, 8]);
+ }
+}
+
+#[cfg(any(
+ target_os = "freebsd",
+ target_os = "ios",
+ target_os = "macos",
+ target_os = "netbsd",
+ target_os = "openbsd",
+))]
+// qemu doesn't seem to be emulating this correctly in these architectures
+#[cfg_attr(
+ all(
+ qemu,
+ any(
+ target_arch = "mips",
+ target_arch = "mips64",
+ target_arch = "powerpc64",
+ )
+ ),
+ ignore
+)]
+#[test]
+pub fn test_recvif() {
+ use nix::net::if_::*;
+ use nix::sys::socket::sockopt::{Ipv4RecvDstAddr, Ipv4RecvIf};
+ use nix::sys::socket::{bind, SockFlag, SockType, SockaddrIn};
+ use nix::sys::socket::{getsockname, setsockopt, socket};
+ use nix::sys::socket::{recvmsg, sendmsg, ControlMessageOwned, MsgFlags};
+ use std::io::{IoSlice, IoSliceMut};
+
+ let lo_ifaddr = loopback_address(AddressFamily::Inet);
+ let (lo_name, lo) = match lo_ifaddr {
+ Some(ifaddr) => (
+ ifaddr.interface_name,
+ ifaddr.address.expect("Expect IPv4 address on interface"),
+ ),
+ None => return,
+ };
+ let receive = socket(
+ AddressFamily::Inet,
+ SockType::Datagram,
+ SockFlag::empty(),
+ None,
+ )
+ .expect("receive socket failed");
+ bind(receive, &lo).expect("bind failed");
+ let sa: SockaddrIn = getsockname(receive).expect("getsockname failed");
+ setsockopt(receive, Ipv4RecvIf, &true)
+ .expect("setsockopt IP_RECVIF failed");
+ setsockopt(receive, Ipv4RecvDstAddr, &true)
+ .expect("setsockopt IP_RECVDSTADDR failed");
+
+ {
+ let slice = [1u8, 2, 3, 4, 5, 6, 7, 8];
+ let iov = [IoSlice::new(&slice)];
+
+ let send = socket(
+ AddressFamily::Inet,
+ SockType::Datagram,
+ SockFlag::empty(),
+ None,
+ )
+ .expect("send socket failed");
+ sendmsg(send, &iov, &[], MsgFlags::empty(), Some(&sa))
+ .expect("sendmsg failed");
+ }
+
+ {
+ let mut buf = [0u8; 8];
+ let mut iovec = [IoSliceMut::new(&mut buf)];
+ let mut space = cmsg_space!(libc::sockaddr_dl, libc::in_addr);
+ let msg = recvmsg::<()>(
+ receive,
+ &mut iovec,
+ Some(&mut space),
+ MsgFlags::empty(),
+ )
+ .expect("recvmsg failed");
+ assert!(!msg
+ .flags
+ .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC));
+ assert_eq!(msg.cmsgs().count(), 2, "expected 2 cmsgs");
+
+ let mut rx_recvif = false;
+ let mut rx_recvdstaddr = false;
+ for cmsg in msg.cmsgs() {
+ match cmsg {
+ ControlMessageOwned::Ipv4RecvIf(dl) => {
+ rx_recvif = true;
+ let i = if_nametoindex(lo_name.as_bytes())
+ .expect("if_nametoindex");
+ assert_eq!(
+ dl.sdl_index as libc::c_uint, i,
+ "unexpected ifindex (expected {}, got {})",
+ i, dl.sdl_index
+ );
+ }
+ ControlMessageOwned::Ipv4RecvDstAddr(addr) => {
+ rx_recvdstaddr = true;
+ if let Some(sin) = lo.as_sockaddr_in() {
+ assert_eq!(sin.as_ref().sin_addr.s_addr,
+ addr.s_addr,
+ "unexpected destination address (expected {}, got {})",
+ sin.as_ref().sin_addr.s_addr,
+ addr.s_addr);
+ } else {
+ panic!("unexpected Sockaddr");
+ }
+ }
+ _ => panic!("unexpected additional control msg"),
+ }
+ }
+ assert!(rx_recvif);
+ assert!(rx_recvdstaddr);
+ assert_eq!(msg.bytes, 8);
+ assert_eq!(*iovec[0], [1u8, 2, 3, 4, 5, 6, 7, 8]);
+ }
+}
+
+#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))]
+#[cfg_attr(qemu, ignore)]
+#[test]
+pub fn test_recvif_ipv4() {
+ use nix::sys::socket::sockopt::Ipv4OrigDstAddr;
+ use nix::sys::socket::{bind, SockFlag, SockType, SockaddrIn};
+ use nix::sys::socket::{getsockname, setsockopt, socket};
+ use nix::sys::socket::{recvmsg, sendmsg, ControlMessageOwned, MsgFlags};
+ use std::io::{IoSlice, IoSliceMut};
+
+ let lo_ifaddr = loopback_address(AddressFamily::Inet);
+ let (_lo_name, lo) = match lo_ifaddr {
+ Some(ifaddr) => (
+ ifaddr.interface_name,
+ ifaddr.address.expect("Expect IPv4 address on interface"),
+ ),
+ None => return,
+ };
+ let receive = socket(
+ AddressFamily::Inet,
+ SockType::Datagram,
+ SockFlag::empty(),
+ None,
+ )
+ .expect("receive socket failed");
+ bind(receive, &lo).expect("bind failed");
+ let sa: SockaddrIn = getsockname(receive).expect("getsockname failed");
+ setsockopt(receive, Ipv4OrigDstAddr, &true)
+ .expect("setsockopt IP_ORIGDSTADDR failed");
+
+ {
+ let slice = [1u8, 2, 3, 4, 5, 6, 7, 8];
+ let iov = [IoSlice::new(&slice)];
+
+ let send = socket(
+ AddressFamily::Inet,
+ SockType::Datagram,
+ SockFlag::empty(),
+ None,
+ )
+ .expect("send socket failed");
+ sendmsg(send, &iov, &[], MsgFlags::empty(), Some(&sa))
+ .expect("sendmsg failed");
+ }
+
+ {
+ let mut buf = [0u8; 8];
+ let mut iovec = [IoSliceMut::new(&mut buf)];
+ let mut space = cmsg_space!(libc::sockaddr_in);
+ let msg = recvmsg::<()>(
+ receive,
+ &mut iovec,
+ Some(&mut space),
+ MsgFlags::empty(),
+ )
+ .expect("recvmsg failed");
+ assert!(!msg
+ .flags
+ .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC));
+ assert_eq!(msg.cmsgs().count(), 1, "expected 1 cmsgs");
+
+ let mut rx_recvorigdstaddr = false;
+ for cmsg in msg.cmsgs() {
+ match cmsg {
+ ControlMessageOwned::Ipv4OrigDstAddr(addr) => {
+ rx_recvorigdstaddr = true;
+ if let Some(sin) = lo.as_sockaddr_in() {
+ assert_eq!(sin.as_ref().sin_addr.s_addr,
+ addr.sin_addr.s_addr,
+ "unexpected destination address (expected {}, got {})",
+ sin.as_ref().sin_addr.s_addr,
+ addr.sin_addr.s_addr);
+ } else {
+ panic!("unexpected Sockaddr");
+ }
+ }
+ _ => panic!("unexpected additional control msg"),
+ }
+ }
+ assert!(rx_recvorigdstaddr);
+ assert_eq!(msg.bytes, 8);
+ assert_eq!(*iovec[0], [1u8, 2, 3, 4, 5, 6, 7, 8]);
+ }
+}
+
+#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))]
+#[cfg_attr(qemu, ignore)]
+#[test]
+pub fn test_recvif_ipv6() {
+ use nix::sys::socket::sockopt::Ipv6OrigDstAddr;
+ use nix::sys::socket::{bind, SockFlag, SockType, SockaddrIn6};
+ use nix::sys::socket::{getsockname, setsockopt, socket};
+ use nix::sys::socket::{recvmsg, sendmsg, ControlMessageOwned, MsgFlags};
+ use std::io::{IoSlice, IoSliceMut};
+
+ let lo_ifaddr = loopback_address(AddressFamily::Inet6);
+ let (_lo_name, lo) = match lo_ifaddr {
+ Some(ifaddr) => (
+ ifaddr.interface_name,
+ ifaddr.address.expect("Expect IPv6 address on interface"),
+ ),
+ None => return,
+ };
+ let receive = socket(
+ AddressFamily::Inet6,
+ SockType::Datagram,
+ SockFlag::empty(),
+ None,
+ )
+ .expect("receive socket failed");
+ bind(receive, &lo).expect("bind failed");
+ let sa: SockaddrIn6 = getsockname(receive).expect("getsockname failed");
+ setsockopt(receive, Ipv6OrigDstAddr, &true)
+ .expect("setsockopt IP_ORIGDSTADDR failed");
+
+ {
+ let slice = [1u8, 2, 3, 4, 5, 6, 7, 8];
+ let iov = [IoSlice::new(&slice)];
+
+ let send = socket(
+ AddressFamily::Inet6,
+ SockType::Datagram,
+ SockFlag::empty(),
+ None,
+ )
+ .expect("send socket failed");
+ sendmsg(send, &iov, &[], MsgFlags::empty(), Some(&sa))
+ .expect("sendmsg failed");
+ }
+
+ {
+ let mut buf = [0u8; 8];
+ let mut iovec = [IoSliceMut::new(&mut buf)];
+ let mut space = cmsg_space!(libc::sockaddr_in6);
+ let msg = recvmsg::<()>(
+ receive,
+ &mut iovec,
+ Some(&mut space),
+ MsgFlags::empty(),
+ )
+ .expect("recvmsg failed");
+ assert!(!msg
+ .flags
+ .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC));
+ assert_eq!(msg.cmsgs().count(), 1, "expected 1 cmsgs");
+
+ let mut rx_recvorigdstaddr = false;
+ for cmsg in msg.cmsgs() {
+ match cmsg {
+ ControlMessageOwned::Ipv6OrigDstAddr(addr) => {
+ rx_recvorigdstaddr = true;
+ if let Some(sin) = lo.as_sockaddr_in6() {
+ assert_eq!(sin.as_ref().sin6_addr.s6_addr,
+ addr.sin6_addr.s6_addr,
+ "unexpected destination address (expected {:?}, got {:?})",
+ sin.as_ref().sin6_addr.s6_addr,
+ addr.sin6_addr.s6_addr);
+ } else {
+ panic!("unexpected Sockaddr");
+ }
+ }
+ _ => panic!("unexpected additional control msg"),
+ }
+ }
+ assert!(rx_recvorigdstaddr);
+ assert_eq!(msg.bytes, 8);
+ assert_eq!(*iovec[0], [1u8, 2, 3, 4, 5, 6, 7, 8]);
+ }
+}
+
+#[cfg(any(
+ target_os = "android",
+ target_os = "freebsd",
+ target_os = "ios",
+ target_os = "linux",
+ target_os = "macos",
+ target_os = "netbsd",
+ target_os = "openbsd",
+))]
+// qemu doesn't seem to be emulating this correctly in these architectures
+#[cfg_attr(
+ all(
+ qemu,
+ any(
+ target_arch = "mips",
+ target_arch = "mips64",
+ target_arch = "powerpc64",
+ )
+ ),
+ ignore
+)]
+#[test]
+pub fn test_recv_ipv6pktinfo() {
+ use nix::net::if_::*;
+ use nix::sys::socket::sockopt::Ipv6RecvPacketInfo;
+ use nix::sys::socket::{bind, SockFlag, SockType, SockaddrIn6};
+ use nix::sys::socket::{getsockname, setsockopt, socket};
+ use nix::sys::socket::{recvmsg, sendmsg, ControlMessageOwned, MsgFlags};
+ use std::io::{IoSlice, IoSliceMut};
+
+ let lo_ifaddr = loopback_address(AddressFamily::Inet6);
+ let (lo_name, lo) = match lo_ifaddr {
+ Some(ifaddr) => (
+ ifaddr.interface_name,
+ ifaddr.address.expect("Expect IPv6 address on interface"),
+ ),
+ None => return,
+ };
+ let receive = socket(
+ AddressFamily::Inet6,
+ SockType::Datagram,
+ SockFlag::empty(),
+ None,
+ )
+ .expect("receive socket failed");
+ bind(receive, &lo).expect("bind failed");
+ let sa: SockaddrIn6 = getsockname(receive).expect("getsockname failed");
+ setsockopt(receive, Ipv6RecvPacketInfo, &true).expect("setsockopt failed");
+
+ {
+ let slice = [1u8, 2, 3, 4, 5, 6, 7, 8];
+ let iov = [IoSlice::new(&slice)];
+
+ let send = socket(
+ AddressFamily::Inet6,
+ SockType::Datagram,
+ SockFlag::empty(),
+ None,
+ )
+ .expect("send socket failed");
+ sendmsg(send, &iov, &[], MsgFlags::empty(), Some(&sa))
+ .expect("sendmsg failed");
+ }
+
+ {
+ let mut buf = [0u8; 8];
+ let mut iovec = [IoSliceMut::new(&mut buf)];
+
+ let mut space = cmsg_space!(libc::in6_pktinfo);
+ let msg = recvmsg::<()>(
+ receive,
+ &mut iovec,
+ Some(&mut space),
+ MsgFlags::empty(),
+ )
+ .expect("recvmsg failed");
+ assert!(!msg
+ .flags
+ .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC));
+
+ let mut cmsgs = msg.cmsgs();
+ if let Some(ControlMessageOwned::Ipv6PacketInfo(pktinfo)) = cmsgs.next()
+ {
+ let i = if_nametoindex(lo_name.as_bytes()).expect("if_nametoindex");
+ assert_eq!(
+ pktinfo.ipi6_ifindex as libc::c_uint, i,
+ "unexpected ifindex (expected {}, got {})",
+ i, pktinfo.ipi6_ifindex
+ );
+ }
+ assert!(cmsgs.next().is_none(), "unexpected additional control msg");
+ assert_eq!(msg.bytes, 8);
+ assert_eq!(*iovec[0], [1u8, 2, 3, 4, 5, 6, 7, 8]);
+ }
+}
+
+#[cfg(any(target_os = "android", target_os = "linux"))]
+#[cfg_attr(graviton, ignore = "Not supported by the CI environment")]
+#[test]
+pub fn test_vsock() {
+ use nix::errno::Errno;
+ use nix::sys::socket::{
+ bind, connect, listen, socket, AddressFamily, SockFlag, SockType,
+ VsockAddr,
+ };
+ use nix::unistd::close;
+ use std::thread;
+
+ let port: u32 = 3000;
+
+ let s1 = socket(
+ AddressFamily::Vsock,
+ SockType::Stream,
+ SockFlag::empty(),
+ None,
+ )
+ .expect("socket failed");
+
+ // VMADDR_CID_HYPERVISOR is reserved, so we expect an EADDRNOTAVAIL error.
+ let sockaddr_hv = VsockAddr::new(libc::VMADDR_CID_HYPERVISOR, port);
+ assert_eq!(bind(s1, &sockaddr_hv).err(), Some(Errno::EADDRNOTAVAIL));
+
+ let sockaddr_any = VsockAddr::new(libc::VMADDR_CID_ANY, port);
+ assert_eq!(bind(s1, &sockaddr_any), Ok(()));
+ listen(s1, 10).expect("listen failed");
+
+ let thr = thread::spawn(move || {
+ let cid: u32 = libc::VMADDR_CID_HOST;
+
+ let s2 = socket(
+ AddressFamily::Vsock,
+ SockType::Stream,
+ SockFlag::empty(),
+ None,
+ )
+ .expect("socket failed");
+
+ let sockaddr_host = VsockAddr::new(cid, port);
+
+ // The current implementation does not support loopback devices, so,
+ // for now, we expect a failure on the connect.
+ assert_ne!(connect(s2, &sockaddr_host), Ok(()));
+
+ close(s2).unwrap();
+ });
+
+ close(s1).unwrap();
+ thr.join().unwrap();
+}
+
+// Disable the test on emulated platforms because it fails in Cirrus-CI. Lack
+// of QEMU support is suspected.
+#[cfg_attr(qemu, ignore)]
+#[cfg(all(target_os = "linux"))]
+#[test]
+fn test_recvmsg_timestampns() {
+ use nix::sys::socket::*;
+ use nix::sys::time::*;
+ use std::io::{IoSlice, IoSliceMut};
+ use std::time::*;
+
+ // Set up
+ let message = "Ohayō!".as_bytes();
+ let in_socket = socket(
+ AddressFamily::Inet,
+ SockType::Datagram,
+ SockFlag::empty(),
+ None,
+ )
+ .unwrap();
+ setsockopt(in_socket, sockopt::ReceiveTimestampns, &true).unwrap();
+ let localhost = SockaddrIn::new(127, 0, 0, 1, 0);
+ bind(in_socket, &localhost).unwrap();
+ let address: SockaddrIn = getsockname(in_socket).unwrap();
+ // Get initial time
+ let time0 = SystemTime::now();
+ // Send the message
+ let iov = [IoSlice::new(message)];
+ let flags = MsgFlags::empty();
+ let l = sendmsg(in_socket, &iov, &[], flags, Some(&address)).unwrap();
+ assert_eq!(message.len(), l);
+ // Receive the message
+ let mut buffer = vec![0u8; message.len()];
+ let mut cmsgspace = nix::cmsg_space!(TimeSpec);
+
+ let mut iov = [IoSliceMut::new(&mut buffer)];
+ let r = recvmsg::<()>(in_socket, &mut iov, Some(&mut cmsgspace), flags)
+ .unwrap();
+ let rtime = match r.cmsgs().next() {
+ Some(ControlMessageOwned::ScmTimestampns(rtime)) => rtime,
+ Some(_) => panic!("Unexpected control message"),
+ None => panic!("No control message"),
+ };
+ // Check the final time
+ let time1 = SystemTime::now();
+ // the packet's received timestamp should lie in-between the two system
+ // times, unless the system clock was adjusted in the meantime.
+ let rduration =
+ Duration::new(rtime.tv_sec() as u64, rtime.tv_nsec() as u32);
+ assert!(time0.duration_since(UNIX_EPOCH).unwrap() <= rduration);
+ assert!(rduration <= time1.duration_since(UNIX_EPOCH).unwrap());
+ // 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(qemu, ignore)]
+#[cfg(all(target_os = "linux"))]
+#[test]
+fn test_recvmmsg_timestampns() {
+ use nix::sys::socket::*;
+ use nix::sys::time::*;
+ use std::io::{IoSlice, IoSliceMut};
+ use std::time::*;
+
+ // Set up
+ let message = "Ohayō!".as_bytes();
+ let in_socket = socket(
+ AddressFamily::Inet,
+ SockType::Datagram,
+ SockFlag::empty(),
+ None,
+ )
+ .unwrap();
+ setsockopt(in_socket, sockopt::ReceiveTimestampns, &true).unwrap();
+ let localhost = SockaddrIn::from_str("127.0.0.1:0").unwrap();
+ bind(in_socket, &localhost).unwrap();
+ let address: SockaddrIn = getsockname(in_socket).unwrap();
+ // Get initial time
+ let time0 = SystemTime::now();
+ // Send the message
+ let iov = [IoSlice::new(message)];
+ let flags = MsgFlags::empty();
+ let l = sendmsg(in_socket, &iov, &[], flags, Some(&address)).unwrap();
+ assert_eq!(message.len(), l);
+ // Receive the message
+ let mut buffer = vec![0u8; message.len()];
+ let cmsgspace = nix::cmsg_space!(TimeSpec);
+ let iov = vec![[IoSliceMut::new(&mut buffer)]];
+ let mut data = MultiHeaders::preallocate(1, Some(cmsgspace));
+ let r: Vec<RecvMsg<()>> =
+ recvmmsg(in_socket, &mut data, iov.iter(), flags, None)
+ .unwrap()
+ .collect();
+ let rtime = match r[0].cmsgs().next() {
+ Some(ControlMessageOwned::ScmTimestampns(rtime)) => rtime,
+ Some(_) => panic!("Unexpected control message"),
+ None => panic!("No control message"),
+ };
+ // Check the final time
+ let time1 = SystemTime::now();
+ // the packet's received timestamp should lie in-between the two system
+ // times, unless the system clock was adjusted in the meantime.
+ let rduration =
+ Duration::new(rtime.tv_sec() as u64, rtime.tv_nsec() as u32);
+ assert!(time0.duration_since(UNIX_EPOCH).unwrap() <= rduration);
+ assert!(rduration <= time1.duration_since(UNIX_EPOCH).unwrap());
+ // 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(qemu, ignore)]
+#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
+#[test]
+fn test_recvmsg_rxq_ovfl() {
+ use nix::sys::socket::sockopt::{RcvBuf, RxqOvfl};
+ use nix::sys::socket::*;
+ use nix::Error;
+ use std::io::{IoSlice, IoSliceMut};
+
+ 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 = SockaddrIn::from_str("127.0.0.1:0").unwrap();
+ bind(in_socket, &localhost).unwrap();
+
+ let address: SockaddrIn = 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 = [IoSlice::new(&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 mut iov = [IoSliceMut::new(&mut buffer)];
+
+ match recvmsg::<()>(
+ in_socket,
+ &mut 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();
+}
+
+#[cfg(any(target_os = "linux", target_os = "android",))]
+mod linux_errqueue {
+ use super::FromStr;
+ use nix::sys::socket::*;
+
+ // Send a UDP datagram to a bogus destination address and observe an ICMP error (v4).
+ //
+ // Disable the test on QEMU because QEMU emulation of IP_RECVERR is broken (as documented on PR
+ // #1514).
+ #[cfg_attr(qemu, ignore)]
+ #[test]
+ fn test_recverr_v4() {
+ #[repr(u8)]
+ enum IcmpTypes {
+ DestUnreach = 3, // ICMP_DEST_UNREACH
+ }
+ #[repr(u8)]
+ enum IcmpUnreachCodes {
+ PortUnreach = 3, // ICMP_PORT_UNREACH
+ }
+
+ test_recverr_impl::<sockaddr_in, _, _>(
+ "127.0.0.1:6800",
+ AddressFamily::Inet,
+ sockopt::Ipv4RecvErr,
+ libc::SO_EE_ORIGIN_ICMP,
+ IcmpTypes::DestUnreach as u8,
+ IcmpUnreachCodes::PortUnreach as u8,
+ // Closure handles protocol-specific testing and returns generic sock_extended_err for
+ // protocol-independent test impl.
+ |cmsg| {
+ if let ControlMessageOwned::Ipv4RecvErr(ext_err, err_addr) =
+ cmsg
+ {
+ if let Some(origin) = err_addr {
+ // Validate that our network error originated from 127.0.0.1:0.
+ assert_eq!(origin.sin_family, AddressFamily::Inet as _);
+ assert_eq!(
+ origin.sin_addr.s_addr,
+ u32::from_be(0x7f000001)
+ );
+ assert_eq!(origin.sin_port, 0);
+ } else {
+ panic!("Expected some error origin");
+ }
+ *ext_err
+ } else {
+ panic!("Unexpected control message {:?}", cmsg);
+ }
+ },
+ )
+ }
+
+ // Essentially the same test as v4.
+ //
+ // Disable the test on QEMU because QEMU emulation of IPV6_RECVERR is broken (as documented on
+ // PR #1514).
+ #[cfg_attr(qemu, ignore)]
+ #[test]
+ fn test_recverr_v6() {
+ #[repr(u8)]
+ enum IcmpV6Types {
+ DestUnreach = 1, // ICMPV6_DEST_UNREACH
+ }
+ #[repr(u8)]
+ enum IcmpV6UnreachCodes {
+ PortUnreach = 4, // ICMPV6_PORT_UNREACH
+ }
+
+ test_recverr_impl::<sockaddr_in6, _, _>(
+ "[::1]:6801",
+ AddressFamily::Inet6,
+ sockopt::Ipv6RecvErr,
+ libc::SO_EE_ORIGIN_ICMP6,
+ IcmpV6Types::DestUnreach as u8,
+ IcmpV6UnreachCodes::PortUnreach as u8,
+ // Closure handles protocol-specific testing and returns generic sock_extended_err for
+ // protocol-independent test impl.
+ |cmsg| {
+ if let ControlMessageOwned::Ipv6RecvErr(ext_err, err_addr) =
+ cmsg
+ {
+ if let Some(origin) = err_addr {
+ // Validate that our network error originated from localhost:0.
+ assert_eq!(
+ origin.sin6_family,
+ AddressFamily::Inet6 as _
+ );
+ assert_eq!(
+ origin.sin6_addr.s6_addr,
+ std::net::Ipv6Addr::LOCALHOST.octets()
+ );
+ assert_eq!(origin.sin6_port, 0);
+ } else {
+ panic!("Expected some error origin");
+ }
+ *ext_err
+ } else {
+ panic!("Unexpected control message {:?}", cmsg);
+ }
+ },
+ )
+ }
+
+ fn test_recverr_impl<SA, OPT, TESTF>(
+ sa: &str,
+ af: AddressFamily,
+ opt: OPT,
+ ee_origin: u8,
+ ee_type: u8,
+ ee_code: u8,
+ testf: TESTF,
+ ) where
+ OPT: SetSockOpt<Val = bool>,
+ TESTF: FnOnce(&ControlMessageOwned) -> libc::sock_extended_err,
+ {
+ use nix::errno::Errno;
+ use std::io::IoSliceMut;
+
+ const MESSAGE_CONTENTS: &str = "ABCDEF";
+ let std_sa = std::net::SocketAddr::from_str(sa).unwrap();
+ let sock_addr = SockaddrStorage::from(std_sa);
+ let sock = socket(af, SockType::Datagram, SockFlag::SOCK_CLOEXEC, None)
+ .unwrap();
+ setsockopt(sock, opt, &true).unwrap();
+ if let Err(e) = sendto(
+ sock,
+ MESSAGE_CONTENTS.as_bytes(),
+ &sock_addr,
+ MsgFlags::empty(),
+ ) {
+ assert_eq!(e, Errno::EADDRNOTAVAIL);
+ println!("{:?} not available, skipping test.", af);
+ return;
+ }
+
+ let mut buf = [0u8; 8];
+ let mut iovec = [IoSliceMut::new(&mut buf)];
+ let mut cspace = cmsg_space!(libc::sock_extended_err, SA);
+
+ let msg = recvmsg(
+ sock,
+ &mut iovec,
+ Some(&mut cspace),
+ MsgFlags::MSG_ERRQUEUE,
+ )
+ .unwrap();
+ // The sent message / destination associated with the error is returned:
+ assert_eq!(msg.bytes, MESSAGE_CONTENTS.as_bytes().len());
+ // recvmsg(2): "The original destination address of the datagram that caused the error is
+ // supplied via msg_name;" however, this is not literally true. E.g., an earlier version
+ // of this test used 0.0.0.0 (::0) as the destination address, which was mutated into
+ // 127.0.0.1 (::1).
+ assert_eq!(msg.address, Some(sock_addr));
+
+ // Check for expected control message.
+ let ext_err = match msg.cmsgs().next() {
+ Some(cmsg) => testf(&cmsg),
+ None => panic!("No control message"),
+ };
+
+ assert_eq!(ext_err.ee_errno, libc::ECONNREFUSED as u32);
+ assert_eq!(ext_err.ee_origin, ee_origin);
+ // ip(7): ee_type and ee_code are set from the type and code fields of the ICMP (ICMPv6)
+ // header.
+ assert_eq!(ext_err.ee_type, ee_type);
+ assert_eq!(ext_err.ee_code, ee_code);
+ // ip(7): ee_info contains the discovered MTU for EMSGSIZE errors.
+ assert_eq!(ext_err.ee_info, 0);
+
+ let bytes = msg.bytes;
+ assert_eq!(&buf[..bytes], MESSAGE_CONTENTS.as_bytes());
+ }
+}
+
+// Disable the test on emulated platforms because it fails in Cirrus-CI. Lack
+// of QEMU support is suspected.
+#[cfg_attr(qemu, ignore)]
+#[cfg(target_os = "linux")]
+#[test]
+pub fn test_txtime() {
+ use nix::sys::socket::{
+ bind, recvmsg, sendmsg, setsockopt, socket, sockopt, ControlMessage,
+ MsgFlags, SockFlag, SockType, SockaddrIn,
+ };
+ use nix::sys::time::TimeValLike;
+ use nix::time::{clock_gettime, ClockId};
+
+ require_kernel_version!(test_txtime, ">= 5.8");
+
+ let sock_addr = SockaddrIn::from_str("127.0.0.1:6802").unwrap();
+
+ let ssock = socket(
+ AddressFamily::Inet,
+ SockType::Datagram,
+ SockFlag::empty(),
+ None,
+ )
+ .expect("send socket failed");
+
+ let txtime_cfg = libc::sock_txtime {
+ clockid: libc::CLOCK_MONOTONIC,
+ flags: 0,
+ };
+ setsockopt(ssock, sockopt::TxTime, &txtime_cfg).unwrap();
+
+ let rsock = socket(
+ AddressFamily::Inet,
+ SockType::Datagram,
+ SockFlag::empty(),
+ None,
+ )
+ .unwrap();
+ bind(rsock, &sock_addr).unwrap();
+
+ let sbuf = [0u8; 2048];
+ let iov1 = [std::io::IoSlice::new(&sbuf)];
+
+ let now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap();
+ let delay = std::time::Duration::from_secs(1).into();
+ let txtime = (now + delay).num_nanoseconds() as u64;
+
+ let cmsg = ControlMessage::TxTime(&txtime);
+ sendmsg(ssock, &iov1, &[cmsg], MsgFlags::empty(), Some(&sock_addr))
+ .unwrap();
+
+ let mut rbuf = [0u8; 2048];
+ let mut iov2 = [std::io::IoSliceMut::new(&mut rbuf)];
+ recvmsg::<()>(rsock, &mut iov2, None, MsgFlags::empty()).unwrap();
+}
diff --git a/third_party/rust/nix/test/sys/test_sockopt.rs b/third_party/rust/nix/test/sys/test_sockopt.rs
new file mode 100644
index 0000000000..34bef945e1
--- /dev/null
+++ b/third_party/rust/nix/test/sys/test_sockopt.rs
@@ -0,0 +1,431 @@
+#[cfg(any(target_os = "android", target_os = "linux"))]
+use crate::*;
+use nix::sys::socket::{
+ getsockopt, setsockopt, socket, sockopt, AddressFamily, SockFlag,
+ SockProtocol, SockType,
+};
+use rand::{thread_rng, Rng};
+
+// NB: FreeBSD supports LOCAL_PEERCRED for SOCK_SEQPACKET, but OSX does not.
+#[cfg(any(target_os = "dragonfly", target_os = "freebsd",))]
+#[test]
+pub fn test_local_peercred_seqpacket() {
+ use nix::{
+ sys::socket::socketpair,
+ unistd::{Gid, Uid},
+ };
+
+ let (fd1, _fd2) = socketpair(
+ AddressFamily::Unix,
+ SockType::SeqPacket,
+ None,
+ SockFlag::empty(),
+ )
+ .unwrap();
+ let xucred = getsockopt(fd1, sockopt::LocalPeerCred).unwrap();
+ assert_eq!(xucred.version(), 0);
+ assert_eq!(Uid::from_raw(xucred.uid()), Uid::current());
+ assert_eq!(Gid::from_raw(xucred.groups()[0]), Gid::current());
+}
+
+#[cfg(any(
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "macos",
+ target_os = "ios"
+))]
+#[test]
+pub fn test_local_peercred_stream() {
+ use nix::{
+ sys::socket::socketpair,
+ unistd::{Gid, Uid},
+ };
+
+ let (fd1, _fd2) = socketpair(
+ AddressFamily::Unix,
+ SockType::Stream,
+ None,
+ SockFlag::empty(),
+ )
+ .unwrap();
+ let xucred = getsockopt(fd1, sockopt::LocalPeerCred).unwrap();
+ assert_eq!(xucred.version(), 0);
+ assert_eq!(Uid::from_raw(xucred.uid()), Uid::current());
+ assert_eq!(Gid::from_raw(xucred.groups()[0]), Gid::current());
+}
+
+#[cfg(target_os = "linux")]
+#[test]
+fn is_so_mark_functional() {
+ use nix::sys::socket::sockopt;
+
+ require_capability!("is_so_mark_functional", CAP_NET_ADMIN);
+
+ let s = socket(
+ AddressFamily::Inet,
+ SockType::Stream,
+ SockFlag::empty(),
+ None,
+ )
+ .unwrap();
+ setsockopt(s, sockopt::Mark, &1337).unwrap();
+ let mark = getsockopt(s, sockopt::Mark).unwrap();
+ assert_eq!(mark, 1337);
+}
+
+#[test]
+fn test_so_buf() {
+ let fd = socket(
+ AddressFamily::Inet,
+ SockType::Datagram,
+ SockFlag::empty(),
+ SockProtocol::Udp,
+ )
+ .unwrap();
+ let bufsize: usize = thread_rng().gen_range(4096..131_072);
+ setsockopt(fd, sockopt::SndBuf, &bufsize).unwrap();
+ let actual = getsockopt(fd, sockopt::SndBuf).unwrap();
+ assert!(actual >= bufsize);
+ setsockopt(fd, sockopt::RcvBuf, &bufsize).unwrap();
+ let actual = getsockopt(fd, sockopt::RcvBuf).unwrap();
+ assert!(actual >= bufsize);
+}
+
+#[test]
+fn test_so_tcp_maxseg() {
+ use nix::sys::socket::{accept, bind, connect, listen, SockaddrIn};
+ use nix::unistd::{close, write};
+ use std::net::SocketAddrV4;
+ use std::str::FromStr;
+
+ let std_sa = SocketAddrV4::from_str("127.0.0.1:4001").unwrap();
+ let sock_addr = SockaddrIn::from(std_sa);
+
+ let rsock = socket(
+ AddressFamily::Inet,
+ SockType::Stream,
+ SockFlag::empty(),
+ SockProtocol::Tcp,
+ )
+ .unwrap();
+ bind(rsock, &sock_addr).unwrap();
+ listen(rsock, 10).unwrap();
+ let initial = getsockopt(rsock, sockopt::TcpMaxSeg).unwrap();
+ // Initial MSS is expected to be 536 (https://tools.ietf.org/html/rfc879#section-1) but some
+ // platforms keep it even lower. This might fail if you've tuned your initial MSS to be larger
+ // than 700
+ cfg_if! {
+ if #[cfg(any(target_os = "android", target_os = "linux"))] {
+ let segsize: u32 = 873;
+ assert!(initial < segsize);
+ setsockopt(rsock, sockopt::TcpMaxSeg, &segsize).unwrap();
+ } else {
+ assert!(initial < 700);
+ }
+ }
+
+ // Connect and check the MSS that was advertised
+ let ssock = socket(
+ AddressFamily::Inet,
+ SockType::Stream,
+ SockFlag::empty(),
+ SockProtocol::Tcp,
+ )
+ .unwrap();
+ connect(ssock, &sock_addr).unwrap();
+ let rsess = accept(rsock).unwrap();
+ write(rsess, b"hello").unwrap();
+ let actual = getsockopt(ssock, sockopt::TcpMaxSeg).unwrap();
+ // Actual max segment size takes header lengths into account, max IPv4 options (60 bytes) + max
+ // TCP options (40 bytes) are subtracted from the requested maximum as a lower boundary.
+ cfg_if! {
+ if #[cfg(any(target_os = "android", target_os = "linux"))] {
+ assert!((segsize - 100) <= actual);
+ assert!(actual <= segsize);
+ } else {
+ assert!(initial < actual);
+ assert!(536 < actual);
+ }
+ }
+ close(rsock).unwrap();
+ close(ssock).unwrap();
+}
+
+#[test]
+fn test_so_type() {
+ let sockfd = socket(
+ AddressFamily::Inet,
+ SockType::Stream,
+ SockFlag::empty(),
+ None,
+ )
+ .unwrap();
+
+ assert_eq!(Ok(SockType::Stream), getsockopt(sockfd, sockopt::SockType));
+}
+
+/// getsockopt(_, sockopt::SockType) should gracefully handle unknown socket
+/// types. Regression test for https://github.com/nix-rust/nix/issues/1819
+#[cfg(any(target_os = "android", target_os = "linux",))]
+#[test]
+fn test_so_type_unknown() {
+ use nix::errno::Errno;
+
+ require_capability!("test_so_type", CAP_NET_RAW);
+ let sockfd = unsafe { libc::socket(libc::AF_PACKET, libc::SOCK_PACKET, 0) };
+ assert!(sockfd >= 0, "Error opening socket: {}", nix::Error::last());
+
+ assert_eq!(Err(Errno::EINVAL), getsockopt(sockfd, sockopt::SockType));
+}
+
+// The CI doesn't supported getsockopt and setsockopt on emulated processors.
+// It's believed that a QEMU issue, the tests run ok on a fully emulated system.
+// Current CI just run the binary with QEMU but the Kernel remains the same as the host.
+// So the syscall doesn't work properly unless the kernel is also emulated.
+#[test]
+#[cfg(all(
+ any(target_arch = "x86", target_arch = "x86_64"),
+ any(target_os = "freebsd", target_os = "linux")
+))]
+fn test_tcp_congestion() {
+ use std::ffi::OsString;
+
+ let fd = socket(
+ AddressFamily::Inet,
+ SockType::Stream,
+ SockFlag::empty(),
+ None,
+ )
+ .unwrap();
+
+ let val = getsockopt(fd, sockopt::TcpCongestion).unwrap();
+ setsockopt(fd, sockopt::TcpCongestion, &val).unwrap();
+
+ setsockopt(
+ fd,
+ sockopt::TcpCongestion,
+ &OsString::from("tcp_congestion_does_not_exist"),
+ )
+ .unwrap_err();
+
+ assert_eq!(getsockopt(fd, sockopt::TcpCongestion).unwrap(), val);
+}
+
+#[test]
+#[cfg(any(target_os = "android", target_os = "linux"))]
+fn test_bindtodevice() {
+ skip_if_not_root!("test_bindtodevice");
+
+ let fd = socket(
+ AddressFamily::Inet,
+ SockType::Stream,
+ SockFlag::empty(),
+ None,
+ )
+ .unwrap();
+
+ let val = getsockopt(fd, sockopt::BindToDevice).unwrap();
+ setsockopt(fd, sockopt::BindToDevice, &val).unwrap();
+
+ assert_eq!(getsockopt(fd, sockopt::BindToDevice).unwrap(), val);
+}
+
+#[test]
+fn test_so_tcp_keepalive() {
+ let fd = socket(
+ AddressFamily::Inet,
+ SockType::Stream,
+ SockFlag::empty(),
+ SockProtocol::Tcp,
+ )
+ .unwrap();
+ setsockopt(fd, sockopt::KeepAlive, &true).unwrap();
+ assert!(getsockopt(fd, sockopt::KeepAlive).unwrap());
+
+ #[cfg(any(
+ target_os = "android",
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "linux"
+ ))]
+ {
+ let x = getsockopt(fd, sockopt::TcpKeepIdle).unwrap();
+ setsockopt(fd, sockopt::TcpKeepIdle, &(x + 1)).unwrap();
+ assert_eq!(getsockopt(fd, sockopt::TcpKeepIdle).unwrap(), x + 1);
+
+ let x = getsockopt(fd, sockopt::TcpKeepCount).unwrap();
+ setsockopt(fd, sockopt::TcpKeepCount, &(x + 1)).unwrap();
+ assert_eq!(getsockopt(fd, sockopt::TcpKeepCount).unwrap(), x + 1);
+
+ let x = getsockopt(fd, sockopt::TcpKeepInterval).unwrap();
+ setsockopt(fd, sockopt::TcpKeepInterval, &(x + 1)).unwrap();
+ assert_eq!(getsockopt(fd, sockopt::TcpKeepInterval).unwrap(), x + 1);
+ }
+}
+
+#[test]
+#[cfg(any(target_os = "android", target_os = "linux"))]
+#[cfg_attr(qemu, ignore)]
+fn test_get_mtu() {
+ use nix::sys::socket::{bind, connect, SockaddrIn};
+ use std::net::SocketAddrV4;
+ use std::str::FromStr;
+
+ let std_sa = SocketAddrV4::from_str("127.0.0.1:4001").unwrap();
+ let std_sb = SocketAddrV4::from_str("127.0.0.1:4002").unwrap();
+
+ let usock = socket(
+ AddressFamily::Inet,
+ SockType::Datagram,
+ SockFlag::empty(),
+ SockProtocol::Udp,
+ )
+ .unwrap();
+
+ // Bind and initiate connection
+ bind(usock, &SockaddrIn::from(std_sa)).unwrap();
+ connect(usock, &SockaddrIn::from(std_sb)).unwrap();
+
+ // Loopback connections have 2^16 - the maximum - MTU
+ assert_eq!(getsockopt(usock, sockopt::IpMtu), Ok(u16::MAX as i32))
+}
+
+#[test]
+#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))]
+fn test_ttl_opts() {
+ let fd4 = socket(
+ AddressFamily::Inet,
+ SockType::Datagram,
+ SockFlag::empty(),
+ None,
+ )
+ .unwrap();
+ setsockopt(fd4, sockopt::Ipv4Ttl, &1)
+ .expect("setting ipv4ttl on an inet socket should succeed");
+ let fd6 = socket(
+ AddressFamily::Inet6,
+ SockType::Datagram,
+ SockFlag::empty(),
+ None,
+ )
+ .unwrap();
+ setsockopt(fd6, sockopt::Ipv6Ttl, &1)
+ .expect("setting ipv6ttl on an inet6 socket should succeed");
+}
+
+#[test]
+#[cfg(any(target_os = "ios", target_os = "macos"))]
+fn test_dontfrag_opts() {
+ let fd4 = socket(
+ AddressFamily::Inet,
+ SockType::Stream,
+ SockFlag::empty(),
+ SockProtocol::Tcp,
+ )
+ .unwrap();
+ setsockopt(fd4, sockopt::IpDontFrag, &true)
+ .expect("setting IP_DONTFRAG on an inet stream socket should succeed");
+ setsockopt(fd4, sockopt::IpDontFrag, &false).expect(
+ "unsetting IP_DONTFRAG on an inet stream socket should succeed",
+ );
+ let fd4d = socket(
+ AddressFamily::Inet,
+ SockType::Datagram,
+ SockFlag::empty(),
+ None,
+ )
+ .unwrap();
+ setsockopt(fd4d, sockopt::IpDontFrag, &true).expect(
+ "setting IP_DONTFRAG on an inet datagram socket should succeed",
+ );
+ setsockopt(fd4d, sockopt::IpDontFrag, &false).expect(
+ "unsetting IP_DONTFRAG on an inet datagram socket should succeed",
+ );
+}
+
+#[test]
+#[cfg(any(
+ target_os = "android",
+ target_os = "ios",
+ target_os = "linux",
+ target_os = "macos",
+))]
+// Disable the test under emulation because it fails in Cirrus-CI. Lack
+// of QEMU support is suspected.
+#[cfg_attr(qemu, ignore)]
+fn test_v6dontfrag_opts() {
+ let fd6 = socket(
+ AddressFamily::Inet6,
+ SockType::Stream,
+ SockFlag::empty(),
+ SockProtocol::Tcp,
+ )
+ .unwrap();
+ setsockopt(fd6, sockopt::Ipv6DontFrag, &true).expect(
+ "setting IPV6_DONTFRAG on an inet6 stream socket should succeed",
+ );
+ setsockopt(fd6, sockopt::Ipv6DontFrag, &false).expect(
+ "unsetting IPV6_DONTFRAG on an inet6 stream socket should succeed",
+ );
+ let fd6d = socket(
+ AddressFamily::Inet6,
+ SockType::Datagram,
+ SockFlag::empty(),
+ None,
+ )
+ .unwrap();
+ setsockopt(fd6d, sockopt::Ipv6DontFrag, &true).expect(
+ "setting IPV6_DONTFRAG on an inet6 datagram socket should succeed",
+ );
+ setsockopt(fd6d, sockopt::Ipv6DontFrag, &false).expect(
+ "unsetting IPV6_DONTFRAG on an inet6 datagram socket should succeed",
+ );
+}
+
+#[test]
+#[cfg(target_os = "linux")]
+fn test_so_priority() {
+ let fd = socket(
+ AddressFamily::Inet,
+ SockType::Stream,
+ SockFlag::empty(),
+ SockProtocol::Tcp,
+ )
+ .unwrap();
+ let priority = 3;
+ setsockopt(fd, sockopt::Priority, &priority).unwrap();
+ assert_eq!(getsockopt(fd, sockopt::Priority).unwrap(), priority);
+}
+
+#[test]
+#[cfg(target_os = "linux")]
+fn test_ip_tos() {
+ let fd = socket(
+ AddressFamily::Inet,
+ SockType::Stream,
+ SockFlag::empty(),
+ SockProtocol::Tcp,
+ )
+ .unwrap();
+ let tos = 0x80; // CS4
+ setsockopt(fd, sockopt::IpTos, &tos).unwrap();
+ assert_eq!(getsockopt(fd, sockopt::IpTos).unwrap(), tos);
+}
+
+#[test]
+#[cfg(target_os = "linux")]
+// Disable the test under emulation because it fails in Cirrus-CI. Lack
+// of QEMU support is suspected.
+#[cfg_attr(qemu, ignore)]
+fn test_ipv6_tclass() {
+ let fd = socket(
+ AddressFamily::Inet6,
+ SockType::Stream,
+ SockFlag::empty(),
+ SockProtocol::Tcp,
+ )
+ .unwrap();
+ let class = 0x80; // CS4
+ setsockopt(fd, sockopt::Ipv6TClass, &class).unwrap();
+ assert_eq!(getsockopt(fd, sockopt::Ipv6TClass).unwrap(), class);
+}
diff --git a/third_party/rust/nix/test/sys/test_stat.rs b/third_party/rust/nix/test/sys/test_stat.rs
new file mode 100644
index 0000000000..426b4b6588
--- /dev/null
+++ b/third_party/rust/nix/test/sys/test_stat.rs
@@ -0,0 +1,29 @@
+// The conversion is not useless on all platforms.
+#[allow(clippy::useless_conversion)]
+#[cfg(target_os = "freebsd")]
+#[test]
+fn test_chflags() {
+ use nix::{
+ sys::stat::{fstat, FileFlag},
+ unistd::chflags,
+ };
+ use std::os::unix::io::AsRawFd;
+ use tempfile::NamedTempFile;
+
+ let f = NamedTempFile::new().unwrap();
+
+ let initial = FileFlag::from_bits_truncate(
+ fstat(f.as_raw_fd()).unwrap().st_flags.into(),
+ );
+ // UF_OFFLINE is preserved by all FreeBSD file systems, but not interpreted
+ // in any way, so it's handy for testing.
+ let commanded = initial ^ FileFlag::UF_OFFLINE;
+
+ chflags(f.path(), commanded).unwrap();
+
+ let changed = FileFlag::from_bits_truncate(
+ fstat(f.as_raw_fd()).unwrap().st_flags.into(),
+ );
+
+ assert_eq!(commanded, changed);
+}
diff --git a/third_party/rust/nix/test/sys/test_sysinfo.rs b/third_party/rust/nix/test/sys/test_sysinfo.rs
new file mode 100644
index 0000000000..2897366eff
--- /dev/null
+++ b/third_party/rust/nix/test/sys/test_sysinfo.rs
@@ -0,0 +1,20 @@
+use nix::sys::sysinfo::*;
+
+#[test]
+fn sysinfo_works() {
+ let info = sysinfo().unwrap();
+
+ let (l1, l5, l15) = info.load_average();
+ assert!(l1 >= 0.0);
+ assert!(l5 >= 0.0);
+ assert!(l15 >= 0.0);
+
+ info.uptime(); // just test Duration construction
+
+ assert!(
+ info.swap_free() <= info.swap_total(),
+ "more swap available than installed (free: {}, total: {})",
+ info.swap_free(),
+ info.swap_total()
+ );
+}
diff --git a/third_party/rust/nix/test/sys/test_termios.rs b/third_party/rust/nix/test/sys/test_termios.rs
new file mode 100644
index 0000000000..aaf00084fa
--- /dev/null
+++ b/third_party/rust/nix/test/sys/test_termios.rs
@@ -0,0 +1,136 @@
+use std::os::unix::prelude::*;
+use tempfile::tempfile;
+
+use nix::errno::Errno;
+use nix::fcntl;
+use nix::pty::openpty;
+use nix::sys::termios::{self, tcgetattr, LocalFlags, OutputFlags};
+use nix::unistd::{close, read, write};
+
+/// Helper function analogous to `std::io::Write::write_all`, but for `RawFd`s
+fn write_all(f: RawFd, buf: &[u8]) {
+ let mut len = 0;
+ while len < buf.len() {
+ len += write(f, &buf[len..]).unwrap();
+ }
+}
+
+// Test tcgetattr on a terminal
+#[test]
+fn test_tcgetattr_pty() {
+ // openpty uses ptname(3) internally
+ let _m = crate::PTSNAME_MTX.lock();
+
+ let pty = openpty(None, None).expect("openpty failed");
+ termios::tcgetattr(pty.slave).unwrap();
+ close(pty.master).expect("closing the master failed");
+ close(pty.slave).expect("closing the slave failed");
+}
+
+// Test tcgetattr on something that isn't a terminal
+#[test]
+fn test_tcgetattr_enotty() {
+ let file = tempfile().unwrap();
+ assert_eq!(
+ termios::tcgetattr(file.as_raw_fd()).err(),
+ Some(Errno::ENOTTY)
+ );
+}
+
+// Test tcgetattr on an invalid file descriptor
+#[test]
+fn test_tcgetattr_ebadf() {
+ assert_eq!(termios::tcgetattr(-1).err(), Some(Errno::EBADF));
+}
+
+// Test modifying output flags
+#[test]
+fn test_output_flags() {
+ // openpty uses ptname(3) internally
+ let _m = crate::PTSNAME_MTX.lock();
+
+ // Open one pty to get attributes for the second one
+ let mut termios = {
+ let pty = openpty(None, None).expect("openpty failed");
+ assert!(pty.master > 0);
+ assert!(pty.slave > 0);
+ let termios = tcgetattr(pty.slave).expect("tcgetattr failed");
+ close(pty.master).unwrap();
+ close(pty.slave).unwrap();
+ termios
+ };
+
+ // Make sure postprocessing '\r' isn't specified by default or this test is useless.
+ assert!(!termios
+ .output_flags
+ .contains(OutputFlags::OPOST | OutputFlags::OCRNL));
+
+ // Specify that '\r' characters should be transformed to '\n'
+ // OPOST is specified to enable post-processing
+ termios
+ .output_flags
+ .insert(OutputFlags::OPOST | OutputFlags::OCRNL);
+
+ // Open a pty
+ let pty = openpty(None, &termios).unwrap();
+ assert!(pty.master > 0);
+ assert!(pty.slave > 0);
+
+ // Write into the master
+ let string = "foofoofoo\r";
+ write_all(pty.master, string.as_bytes());
+
+ // Read from the slave verifying that the output has been properly transformed
+ let mut buf = [0u8; 10];
+ crate::read_exact(pty.slave, &mut buf);
+ let transformed_string = "foofoofoo\n";
+ close(pty.master).unwrap();
+ close(pty.slave).unwrap();
+ assert_eq!(&buf, transformed_string.as_bytes());
+}
+
+// Test modifying local flags
+#[test]
+fn test_local_flags() {
+ // openpty uses ptname(3) internally
+ let _m = crate::PTSNAME_MTX.lock();
+
+ // Open one pty to get attributes for the second one
+ let mut termios = {
+ let pty = openpty(None, None).unwrap();
+ assert!(pty.master > 0);
+ assert!(pty.slave > 0);
+ let termios = tcgetattr(pty.slave).unwrap();
+ close(pty.master).unwrap();
+ close(pty.slave).unwrap();
+ termios
+ };
+
+ // Make sure echo is specified by default or this test is useless.
+ assert!(termios.local_flags.contains(LocalFlags::ECHO));
+
+ // Disable local echo
+ termios.local_flags.remove(LocalFlags::ECHO);
+
+ // Open a new pty with our modified termios settings
+ let pty = openpty(None, &termios).unwrap();
+ assert!(pty.master > 0);
+ assert!(pty.slave > 0);
+
+ // Set the master is in nonblocking mode or reading will never return.
+ let flags = fcntl::fcntl(pty.master, fcntl::F_GETFL).unwrap();
+ let new_flags =
+ fcntl::OFlag::from_bits_truncate(flags) | fcntl::OFlag::O_NONBLOCK;
+ fcntl::fcntl(pty.master, fcntl::F_SETFL(new_flags)).unwrap();
+
+ // Write into the master
+ let string = "foofoofoo\r";
+ write_all(pty.master, string.as_bytes());
+
+ // Try to read from the master, which should not have anything as echoing was disabled.
+ let mut buf = [0u8; 10];
+ let read = read(pty.master, &mut buf).unwrap_err();
+ close(pty.master).unwrap();
+ close(pty.slave).unwrap();
+ assert_eq!(read, Errno::EAGAIN);
+}
diff --git a/third_party/rust/nix/test/sys/test_timerfd.rs b/third_party/rust/nix/test/sys/test_timerfd.rs
new file mode 100644
index 0000000000..08e292106c
--- /dev/null
+++ b/third_party/rust/nix/test/sys/test_timerfd.rs
@@ -0,0 +1,69 @@
+use nix::sys::time::{TimeSpec, TimeValLike};
+use nix::sys::timerfd::{
+ ClockId, Expiration, TimerFd, TimerFlags, TimerSetTimeFlags,
+};
+use std::time::Instant;
+
+#[test]
+pub fn test_timerfd_oneshot() {
+ let timer =
+ TimerFd::new(ClockId::CLOCK_MONOTONIC, TimerFlags::empty()).unwrap();
+
+ let before = Instant::now();
+
+ timer
+ .set(
+ Expiration::OneShot(TimeSpec::seconds(1)),
+ TimerSetTimeFlags::empty(),
+ )
+ .unwrap();
+
+ timer.wait().unwrap();
+
+ let millis = before.elapsed().as_millis();
+ assert!(millis > 900);
+}
+
+#[test]
+pub fn test_timerfd_interval() {
+ let timer =
+ TimerFd::new(ClockId::CLOCK_MONOTONIC, TimerFlags::empty()).unwrap();
+
+ let before = Instant::now();
+ timer
+ .set(
+ Expiration::IntervalDelayed(
+ TimeSpec::seconds(1),
+ TimeSpec::seconds(2),
+ ),
+ TimerSetTimeFlags::empty(),
+ )
+ .unwrap();
+
+ timer.wait().unwrap();
+
+ let start_delay = before.elapsed().as_millis();
+ assert!(start_delay > 900);
+
+ timer.wait().unwrap();
+
+ let interval_delay = before.elapsed().as_millis();
+ assert!(interval_delay > 2900);
+}
+
+#[test]
+pub fn test_timerfd_unset() {
+ let timer =
+ TimerFd::new(ClockId::CLOCK_MONOTONIC, TimerFlags::empty()).unwrap();
+
+ timer
+ .set(
+ Expiration::OneShot(TimeSpec::seconds(1)),
+ TimerSetTimeFlags::empty(),
+ )
+ .unwrap();
+
+ timer.unset().unwrap();
+
+ assert!(timer.get().unwrap().is_none());
+}
diff --git a/third_party/rust/nix/test/sys/test_uio.rs b/third_party/rust/nix/test/sys/test_uio.rs
new file mode 100644
index 0000000000..0f4b8a6568
--- /dev/null
+++ b/third_party/rust/nix/test/sys/test_uio.rs
@@ -0,0 +1,270 @@
+use nix::sys::uio::*;
+use nix::unistd::*;
+use rand::distributions::Alphanumeric;
+use rand::{thread_rng, Rng};
+use std::fs::OpenOptions;
+use std::io::IoSlice;
+use std::os::unix::io::AsRawFd;
+use std::{cmp, iter};
+
+#[cfg(not(target_os = "redox"))]
+use std::io::IoSliceMut;
+
+use tempfile::tempdir;
+#[cfg(not(target_os = "redox"))]
+use tempfile::tempfile;
+
+#[test]
+fn test_writev() {
+ let mut to_write = Vec::with_capacity(16 * 128);
+ for _ in 0..16 {
+ let s: String = thread_rng()
+ .sample_iter(&Alphanumeric)
+ .map(char::from)
+ .take(128)
+ .collect();
+ let b = s.as_bytes();
+ to_write.extend(b.iter().cloned());
+ }
+ // Allocate and fill iovecs
+ let mut iovecs = Vec::new();
+ let mut consumed = 0;
+ while consumed < to_write.len() {
+ let left = to_write.len() - consumed;
+ let slice_len = if left <= 64 {
+ left
+ } else {
+ thread_rng().gen_range(64..cmp::min(256, left))
+ };
+ let b = &to_write[consumed..consumed + slice_len];
+ iovecs.push(IoSlice::new(b));
+ consumed += slice_len;
+ }
+ let pipe_res = pipe();
+ let (reader, writer) = pipe_res.expect("Couldn't create pipe");
+ // FileDesc will close its filedesc (reader).
+ let mut read_buf: Vec<u8> = iter::repeat(0u8).take(128 * 16).collect();
+ // Blocking io, should write all data.
+ let write_res = writev(writer, &iovecs);
+ let written = write_res.expect("couldn't write");
+ // Check whether we written all data
+ assert_eq!(to_write.len(), written);
+ let read_res = read(reader, &mut read_buf[..]);
+ let read = read_res.expect("couldn't read");
+ // Check we have read as much as we written
+ assert_eq!(read, written);
+ // Check equality of written and read data
+ assert_eq!(&to_write, &read_buf);
+ close(writer).expect("closed writer");
+ close(reader).expect("closed reader");
+}
+
+#[test]
+#[cfg(not(target_os = "redox"))]
+fn test_readv() {
+ let s: String = thread_rng()
+ .sample_iter(&Alphanumeric)
+ .map(char::from)
+ .take(128)
+ .collect();
+ let to_write = s.as_bytes().to_vec();
+ let mut storage = Vec::new();
+ let mut allocated = 0;
+ while allocated < to_write.len() {
+ let left = to_write.len() - allocated;
+ let vec_len = if left <= 64 {
+ left
+ } else {
+ thread_rng().gen_range(64..cmp::min(256, left))
+ };
+ let v: Vec<u8> = iter::repeat(0u8).take(vec_len).collect();
+ storage.push(v);
+ allocated += vec_len;
+ }
+ let mut iovecs = Vec::with_capacity(storage.len());
+ for v in &mut storage {
+ iovecs.push(IoSliceMut::new(&mut v[..]));
+ }
+ let (reader, writer) = pipe().expect("couldn't create pipe");
+ // Blocking io, should write all data.
+ write(writer, &to_write).expect("write failed");
+ let read = readv(reader, &mut iovecs[..]).expect("read failed");
+ // Check whether we've read all data
+ assert_eq!(to_write.len(), read);
+ // Cccumulate data from iovecs
+ let mut read_buf = Vec::with_capacity(to_write.len());
+ for iovec in &iovecs {
+ read_buf.extend(iovec.iter().cloned());
+ }
+ // Check whether iovecs contain all written data
+ assert_eq!(read_buf.len(), to_write.len());
+ // Check equality of written and read data
+ assert_eq!(&read_buf, &to_write);
+ close(reader).expect("couldn't close reader");
+ close(writer).expect("couldn't close writer");
+}
+
+#[test]
+#[cfg(not(target_os = "redox"))]
+fn test_pwrite() {
+ use std::io::Read;
+
+ let mut file = tempfile().unwrap();
+ let buf = [1u8; 8];
+ assert_eq!(Ok(8), pwrite(file.as_raw_fd(), &buf, 8));
+ let mut file_content = Vec::new();
+ file.read_to_end(&mut file_content).unwrap();
+ let mut expected = vec![0u8; 8];
+ expected.extend(vec![1; 8]);
+ assert_eq!(file_content, expected);
+}
+
+#[test]
+fn test_pread() {
+ use std::io::Write;
+
+ let tempdir = tempdir().unwrap();
+
+ let path = tempdir.path().join("pread_test_file");
+ let mut file = OpenOptions::new()
+ .write(true)
+ .read(true)
+ .create(true)
+ .truncate(true)
+ .open(path)
+ .unwrap();
+ let file_content: Vec<u8> = (0..64).collect();
+ file.write_all(&file_content).unwrap();
+
+ let mut buf = [0u8; 16];
+ assert_eq!(Ok(16), pread(file.as_raw_fd(), &mut buf, 16));
+ let expected: Vec<_> = (16..32).collect();
+ assert_eq!(&buf[..], &expected[..]);
+}
+
+#[test]
+#[cfg(not(any(target_os = "redox", target_os = "haiku")))]
+fn test_pwritev() {
+ use std::io::Read;
+
+ let to_write: Vec<u8> = (0..128).collect();
+ let expected: Vec<u8> = [vec![0; 100], to_write.clone()].concat();
+
+ let iovecs = [
+ IoSlice::new(&to_write[0..17]),
+ IoSlice::new(&to_write[17..64]),
+ IoSlice::new(&to_write[64..128]),
+ ];
+
+ let tempdir = tempdir().unwrap();
+
+ // pwritev them into a temporary file
+ let path = tempdir.path().join("pwritev_test_file");
+ let mut file = OpenOptions::new()
+ .write(true)
+ .read(true)
+ .create(true)
+ .truncate(true)
+ .open(path)
+ .unwrap();
+
+ let written = pwritev(file.as_raw_fd(), &iovecs, 100).ok().unwrap();
+ assert_eq!(written, to_write.len());
+
+ // Read the data back and make sure it matches
+ let mut contents = Vec::new();
+ file.read_to_end(&mut contents).unwrap();
+ assert_eq!(contents, expected);
+}
+
+#[test]
+#[cfg(not(any(target_os = "redox", target_os = "haiku")))]
+fn test_preadv() {
+ use std::io::Write;
+
+ let to_write: Vec<u8> = (0..200).collect();
+ let expected: Vec<u8> = (100..200).collect();
+
+ let tempdir = tempdir().unwrap();
+
+ let path = tempdir.path().join("preadv_test_file");
+
+ let mut file = OpenOptions::new()
+ .read(true)
+ .write(true)
+ .create(true)
+ .truncate(true)
+ .open(path)
+ .unwrap();
+ file.write_all(&to_write).unwrap();
+
+ let mut buffers: Vec<Vec<u8>> = vec![vec![0; 24], vec![0; 1], vec![0; 75]];
+
+ {
+ // Borrow the buffers into IoVecs and preadv into them
+ let mut iovecs: Vec<_> = buffers
+ .iter_mut()
+ .map(|buf| IoSliceMut::new(&mut buf[..]))
+ .collect();
+ assert_eq!(Ok(100), preadv(file.as_raw_fd(), &mut iovecs, 100));
+ }
+
+ let all = buffers.concat();
+ assert_eq!(all, expected);
+}
+
+#[test]
+#[cfg(all(target_os = "linux", not(target_env = "uclibc")))]
+// uclibc doesn't implement process_vm_readv
+// qemu-user doesn't implement process_vm_readv/writev on most arches
+#[cfg_attr(qemu, ignore)]
+fn test_process_vm_readv() {
+ use crate::*;
+ use nix::sys::signal::*;
+ use nix::sys::wait::*;
+ use nix::unistd::ForkResult::*;
+
+ require_capability!("test_process_vm_readv", CAP_SYS_PTRACE);
+ let _m = crate::FORK_MTX.lock();
+
+ // Pre-allocate memory in the child, since allocation isn't safe
+ // post-fork (~= async-signal-safe)
+ let mut vector = vec![1u8, 2, 3, 4, 5];
+
+ let (r, w) = pipe().unwrap();
+ match unsafe { fork() }.expect("Error: Fork Failed") {
+ Parent { child } => {
+ close(w).unwrap();
+ // wait for child
+ read(r, &mut [0u8]).unwrap();
+ close(r).unwrap();
+
+ let ptr = vector.as_ptr() as usize;
+ let remote_iov = RemoteIoVec { base: ptr, len: 5 };
+ let mut buf = vec![0u8; 5];
+
+ let ret = process_vm_readv(
+ child,
+ &mut [IoSliceMut::new(&mut buf)],
+ &[remote_iov],
+ );
+
+ kill(child, SIGTERM).unwrap();
+ waitpid(child, None).unwrap();
+
+ assert_eq!(Ok(5), ret);
+ assert_eq!(20u8, buf.iter().sum());
+ }
+ Child => {
+ let _ = close(r);
+ for i in &mut vector {
+ *i += 1;
+ }
+ let _ = write(w, b"\0");
+ let _ = close(w);
+ loop {
+ pause();
+ }
+ }
+ }
+}
diff --git a/third_party/rust/nix/test/sys/test_wait.rs b/third_party/rust/nix/test/sys/test_wait.rs
new file mode 100644
index 0000000000..d472f1ec19
--- /dev/null
+++ b/third_party/rust/nix/test/sys/test_wait.rs
@@ -0,0 +1,257 @@
+use libc::_exit;
+use nix::errno::Errno;
+use nix::sys::signal::*;
+use nix::sys::wait::*;
+use nix::unistd::ForkResult::*;
+use nix::unistd::*;
+
+#[test]
+#[cfg(not(any(target_os = "redox", target_os = "haiku")))]
+fn test_wait_signal() {
+ let _m = crate::FORK_MTX.lock();
+
+ // Safe: The child only calls `pause` and/or `_exit`, which are async-signal-safe.
+ match unsafe { fork() }.expect("Error: Fork Failed") {
+ Child => {
+ pause();
+ unsafe { _exit(123) }
+ }
+ Parent { child } => {
+ kill(child, Some(SIGKILL)).expect("Error: Kill Failed");
+ assert_eq!(
+ waitpid(child, None),
+ Ok(WaitStatus::Signaled(child, SIGKILL, false))
+ );
+ }
+ }
+}
+
+#[test]
+#[cfg(any(
+ target_os = "android",
+ target_os = "freebsd",
+ //target_os = "haiku",
+ all(target_os = "linux", not(target_env = "uclibc")),
+))]
+#[cfg(not(any(target_arch = "mips", target_arch = "mips64")))]
+fn test_waitid_signal() {
+ let _m = crate::FORK_MTX.lock();
+
+ // Safe: The child only calls `pause` and/or `_exit`, which are async-signal-safe.
+ match unsafe { fork() }.expect("Error: Fork Failed") {
+ Child => {
+ pause();
+ unsafe { _exit(123) }
+ }
+ Parent { child } => {
+ kill(child, Some(SIGKILL)).expect("Error: Kill Failed");
+ assert_eq!(
+ waitid(Id::Pid(child), WaitPidFlag::WEXITED),
+ Ok(WaitStatus::Signaled(child, SIGKILL, false)),
+ );
+ }
+ }
+}
+
+#[test]
+fn test_wait_exit() {
+ let _m = crate::FORK_MTX.lock();
+
+ // Safe: Child only calls `_exit`, which is async-signal-safe.
+ match unsafe { fork() }.expect("Error: Fork Failed") {
+ Child => unsafe {
+ _exit(12);
+ },
+ Parent { child } => {
+ assert_eq!(waitpid(child, None), Ok(WaitStatus::Exited(child, 12)));
+ }
+ }
+}
+
+#[cfg(not(target_os = "haiku"))]
+#[test]
+#[cfg(any(
+ target_os = "android",
+ target_os = "freebsd",
+ target_os = "haiku",
+ all(target_os = "linux", not(target_env = "uclibc")),
+))]
+#[cfg(not(any(target_arch = "mips", target_arch = "mips64")))]
+fn test_waitid_exit() {
+ let _m = crate::FORK_MTX.lock();
+
+ // Safe: Child only calls `_exit`, which is async-signal-safe.
+ match unsafe { fork() }.expect("Error: Fork Failed") {
+ Child => unsafe {
+ _exit(12);
+ },
+ Parent { child } => {
+ assert_eq!(
+ waitid(Id::Pid(child), WaitPidFlag::WEXITED),
+ Ok(WaitStatus::Exited(child, 12)),
+ );
+ }
+ }
+}
+
+#[test]
+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(Errno::EINVAL));
+}
+
+#[test]
+fn test_waitstatus_pid() {
+ let _m = crate::FORK_MTX.lock();
+
+ match unsafe { fork() }.unwrap() {
+ Child => unsafe { _exit(0) },
+ Parent { child } => {
+ let status = waitpid(child, None).unwrap();
+ assert_eq!(status.pid(), Some(child));
+ }
+ }
+}
+
+#[test]
+#[cfg(any(
+ target_os = "android",
+ target_os = "freebsd",
+ target_os = "haiku",
+ all(target_os = "linux", not(target_env = "uclibc")),
+))]
+fn test_waitid_pid() {
+ let _m = crate::FORK_MTX.lock();
+
+ match unsafe { fork() }.unwrap() {
+ Child => unsafe { _exit(0) },
+ Parent { child } => {
+ let status = waitid(Id::Pid(child), WaitPidFlag::WEXITED).unwrap();
+ assert_eq!(status.pid(), Some(child));
+ }
+ }
+}
+
+#[cfg(any(target_os = "linux", target_os = "android"))]
+// FIXME: qemu-user doesn't implement ptrace on most arches
+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+mod ptrace {
+ use crate::*;
+ use libc::_exit;
+ use nix::sys::ptrace::{self, Event, Options};
+ use nix::sys::signal::*;
+ use nix::sys::wait::*;
+ use nix::unistd::ForkResult::*;
+ use nix::unistd::*;
+
+ fn ptrace_child() -> ! {
+ ptrace::traceme().unwrap();
+ // As recommended by ptrace(2), raise SIGTRAP to pause the child
+ // until the parent is ready to continue
+ raise(SIGTRAP).unwrap();
+ unsafe { _exit(0) }
+ }
+
+ fn ptrace_wait_parent(child: Pid) {
+ // Wait for the raised SIGTRAP
+ assert_eq!(
+ waitpid(child, None),
+ Ok(WaitStatus::Stopped(child, SIGTRAP))
+ );
+ // We want to test a syscall stop and a PTRACE_EVENT stop
+ ptrace::setoptions(
+ child,
+ Options::PTRACE_O_TRACESYSGOOD | Options::PTRACE_O_TRACEEXIT,
+ )
+ .expect("setoptions failed");
+
+ // First, stop on the next system call, which will be exit()
+ ptrace::syscall(child, None).expect("syscall failed");
+ assert_eq!(waitpid(child, None), Ok(WaitStatus::PtraceSyscall(child)));
+ // Then get the ptrace event for the process exiting
+ ptrace::cont(child, None).expect("cont failed");
+ assert_eq!(
+ waitpid(child, None),
+ Ok(WaitStatus::PtraceEvent(
+ child,
+ SIGTRAP,
+ Event::PTRACE_EVENT_EXIT as i32
+ ))
+ );
+ // Finally get the normal wait() result, now that the process has exited
+ ptrace::cont(child, None).expect("cont failed");
+ assert_eq!(waitpid(child, None), Ok(WaitStatus::Exited(child, 0)));
+ }
+
+ #[cfg(not(target_env = "uclibc"))]
+ fn ptrace_waitid_parent(child: Pid) {
+ // Wait for the raised SIGTRAP
+ //
+ // Unlike waitpid(), waitid() can distinguish trap events from regular
+ // stop events, so unlike ptrace_wait_parent(), we get a PtraceEvent here
+ assert_eq!(
+ waitid(Id::Pid(child), WaitPidFlag::WEXITED),
+ Ok(WaitStatus::PtraceEvent(child, SIGTRAP, 0)),
+ );
+ // We want to test a syscall stop and a PTRACE_EVENT stop
+ ptrace::setoptions(
+ child,
+ Options::PTRACE_O_TRACESYSGOOD | Options::PTRACE_O_TRACEEXIT,
+ )
+ .expect("setopts failed");
+
+ // First, stop on the next system call, which will be exit()
+ ptrace::syscall(child, None).expect("syscall failed");
+ assert_eq!(
+ waitid(Id::Pid(child), WaitPidFlag::WEXITED),
+ Ok(WaitStatus::PtraceSyscall(child)),
+ );
+ // Then get the ptrace event for the process exiting
+ ptrace::cont(child, None).expect("cont failed");
+ assert_eq!(
+ waitid(Id::Pid(child), WaitPidFlag::WEXITED),
+ Ok(WaitStatus::PtraceEvent(
+ child,
+ SIGTRAP,
+ Event::PTRACE_EVENT_EXIT as i32
+ )),
+ );
+ // Finally get the normal wait() result, now that the process has exited
+ ptrace::cont(child, None).expect("cont failed");
+ assert_eq!(
+ waitid(Id::Pid(child), WaitPidFlag::WEXITED),
+ Ok(WaitStatus::Exited(child, 0)),
+ );
+ }
+
+ #[test]
+ fn test_wait_ptrace() {
+ require_capability!("test_wait_ptrace", CAP_SYS_PTRACE);
+ let _m = crate::FORK_MTX.lock();
+
+ match unsafe { fork() }.expect("Error: Fork Failed") {
+ Child => ptrace_child(),
+ Parent { child } => ptrace_wait_parent(child),
+ }
+ }
+
+ #[test]
+ #[cfg(not(target_env = "uclibc"))]
+ fn test_waitid_ptrace() {
+ require_capability!("test_waitid_ptrace", CAP_SYS_PTRACE);
+ let _m = crate::FORK_MTX.lock();
+
+ match unsafe { fork() }.expect("Error: Fork Failed") {
+ Child => ptrace_child(),
+ Parent { child } => ptrace_waitid_parent(child),
+ }
+ }
+}
diff --git a/third_party/rust/nix/test/test.rs b/third_party/rust/nix/test/test.rs
new file mode 100644
index 0000000000..6b42aad950
--- /dev/null
+++ b/third_party/rust/nix/test/test.rs
@@ -0,0 +1,124 @@
+#[macro_use]
+extern crate cfg_if;
+#[cfg_attr(not(any(target_os = "redox", target_os = "haiku")), macro_use)]
+extern crate nix;
+#[macro_use]
+extern crate lazy_static;
+
+mod common;
+mod sys;
+#[cfg(not(target_os = "redox"))]
+mod test_dir;
+mod test_fcntl;
+#[cfg(any(target_os = "android", target_os = "linux"))]
+mod test_kmod;
+#[cfg(any(
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "fushsia",
+ target_os = "linux",
+ target_os = "netbsd"
+))]
+mod test_mq;
+#[cfg(not(target_os = "redox"))]
+mod test_net;
+mod test_nix_path;
+#[cfg(target_os = "freebsd")]
+mod test_nmount;
+mod test_poll;
+#[cfg(not(any(
+ target_os = "redox",
+ target_os = "fuchsia",
+ target_os = "haiku"
+)))]
+mod test_pty;
+mod test_resource;
+#[cfg(any(
+ target_os = "android",
+ target_os = "dragonfly",
+ all(target_os = "freebsd", fbsd14),
+ target_os = "linux"
+))]
+mod test_sched;
+#[cfg(any(
+ target_os = "android",
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "ios",
+ target_os = "linux",
+ target_os = "macos"
+))]
+mod test_sendfile;
+mod test_stat;
+mod test_time;
+#[cfg(all(
+ any(
+ target_os = "freebsd",
+ target_os = "illumos",
+ target_os = "linux",
+ target_os = "netbsd"
+ ),
+ feature = "time",
+ feature = "signal"
+))]
+mod test_timer;
+mod test_unistd;
+
+use nix::unistd::{chdir, getcwd, read};
+use parking_lot::{Mutex, RwLock, RwLockWriteGuard};
+use std::os::unix::io::RawFd;
+use std::path::PathBuf;
+
+/// Helper function analogous to `std::io::Read::read_exact`, but for `RawFD`s
+fn read_exact(f: RawFd, buf: &mut [u8]) {
+ let mut len = 0;
+ while len < buf.len() {
+ // get_mut would be better than split_at_mut, but it requires nightly
+ let (_, remaining) = buf.split_at_mut(len);
+ len += read(f, remaining).unwrap();
+ }
+}
+
+lazy_static! {
+ /// Any test that changes the process's current working directory must grab
+ /// the RwLock exclusively. Any process that cares about the current
+ /// working directory must grab it shared.
+ pub static ref CWD_LOCK: RwLock<()> = RwLock::new(());
+ /// Any test that creates child processes must grab this mutex, regardless
+ /// of what it does with those children.
+ pub static ref FORK_MTX: Mutex<()> = Mutex::new(());
+ /// Any test that changes the process's supplementary groups must grab this
+ /// mutex
+ pub static ref GROUPS_MTX: Mutex<()> = Mutex::new(());
+ /// Any tests that loads or unloads kernel modules must grab this mutex
+ pub static ref KMOD_MTX: Mutex<()> = Mutex::new(());
+ /// Any test that calls ptsname(3) must grab this mutex.
+ pub static ref PTSNAME_MTX: Mutex<()> = Mutex::new(());
+ /// Any test that alters signal handling must grab this mutex.
+ pub static ref SIGNAL_MTX: Mutex<()> = Mutex::new(());
+}
+
+/// RAII object that restores a test's original directory on drop
+struct DirRestore<'a> {
+ d: PathBuf,
+ _g: RwLockWriteGuard<'a, ()>,
+}
+
+impl<'a> DirRestore<'a> {
+ fn new() -> Self {
+ let guard = crate::CWD_LOCK.write();
+ DirRestore {
+ _g: guard,
+ d: getcwd().unwrap(),
+ }
+ }
+}
+
+impl<'a> Drop for DirRestore<'a> {
+ fn drop(&mut self) {
+ let r = chdir(&self.d);
+ if std::thread::panicking() {
+ r.unwrap();
+ }
+ }
+}
diff --git a/third_party/rust/nix/test/test_clearenv.rs b/third_party/rust/nix/test/test_clearenv.rs
new file mode 100644
index 0000000000..28a7768049
--- /dev/null
+++ b/third_party/rust/nix/test/test_clearenv.rs
@@ -0,0 +1,9 @@
+use std::env;
+
+#[test]
+fn clearenv() {
+ env::set_var("FOO", "BAR");
+ unsafe { nix::env::clearenv() }.unwrap();
+ assert_eq!(env::var("FOO").unwrap_err(), env::VarError::NotPresent);
+ assert_eq!(env::vars().count(), 0);
+}
diff --git a/third_party/rust/nix/test/test_dir.rs b/third_party/rust/nix/test/test_dir.rs
new file mode 100644
index 0000000000..2af4aa5c0a
--- /dev/null
+++ b/third_party/rust/nix/test/test_dir.rs
@@ -0,0 +1,65 @@
+use nix::dir::{Dir, Type};
+use nix::fcntl::OFlag;
+use nix::sys::stat::Mode;
+use std::fs::File;
+use tempfile::tempdir;
+
+#[cfg(test)]
+fn flags() -> OFlag {
+ #[cfg(target_os = "illumos")]
+ let f = OFlag::O_RDONLY | OFlag::O_CLOEXEC;
+
+ #[cfg(not(target_os = "illumos"))]
+ let f = OFlag::O_RDONLY | OFlag::O_CLOEXEC | OFlag::O_DIRECTORY;
+
+ f
+}
+
+#[test]
+#[allow(clippy::unnecessary_sort_by)] // False positive
+fn read() {
+ let tmp = tempdir().unwrap();
+ File::create(tmp.path().join("foo")).unwrap();
+ std::os::unix::fs::symlink("foo", tmp.path().join("bar")).unwrap();
+ let mut dir = Dir::open(tmp.path(), flags(), Mode::empty()).unwrap();
+ let mut entries: Vec<_> = dir.iter().map(|e| e.unwrap()).collect();
+ entries.sort_by(|a, b| a.file_name().cmp(b.file_name()));
+ let entry_names: Vec<_> = entries
+ .iter()
+ .map(|e| e.file_name().to_str().unwrap().to_owned())
+ .collect();
+ assert_eq!(&entry_names[..], &[".", "..", "bar", "foo"]);
+
+ // Check file types. The system is allowed to return DT_UNKNOWN (aka None here) but if it does
+ // return a type, ensure it's correct.
+ assert!(&[Some(Type::Directory), None].contains(&entries[0].file_type())); // .: dir
+ assert!(&[Some(Type::Directory), None].contains(&entries[1].file_type())); // ..: dir
+ assert!(&[Some(Type::Symlink), None].contains(&entries[2].file_type())); // bar: symlink
+ assert!(&[Some(Type::File), None].contains(&entries[3].file_type())); // foo: regular file
+}
+
+#[test]
+fn rewind() {
+ let tmp = tempdir().unwrap();
+ let mut dir = Dir::open(tmp.path(), flags(), Mode::empty()).unwrap();
+ let entries1: Vec<_> = dir
+ .iter()
+ .map(|e| e.unwrap().file_name().to_owned())
+ .collect();
+ let entries2: Vec<_> = dir
+ .iter()
+ .map(|e| e.unwrap().file_name().to_owned())
+ .collect();
+ let entries3: Vec<_> = dir
+ .into_iter()
+ .map(|e| e.unwrap().file_name().to_owned())
+ .collect();
+ assert_eq!(entries1, entries2);
+ assert_eq!(entries2, entries3);
+}
+
+#[cfg(not(target_os = "haiku"))]
+#[test]
+fn ebadf() {
+ assert_eq!(Dir::from_fd(-1).unwrap_err(), nix::Error::EBADF);
+}
diff --git a/third_party/rust/nix/test/test_fcntl.rs b/third_party/rust/nix/test/test_fcntl.rs
new file mode 100644
index 0000000000..e51044a069
--- /dev/null
+++ b/third_party/rust/nix/test/test_fcntl.rs
@@ -0,0 +1,565 @@
+#[cfg(not(target_os = "redox"))]
+use nix::errno::*;
+#[cfg(not(target_os = "redox"))]
+use nix::fcntl::{open, readlink, OFlag};
+#[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::{renameat2, RenameFlags};
+#[cfg(not(target_os = "redox"))]
+use nix::sys::stat::Mode;
+#[cfg(not(target_os = "redox"))]
+use nix::unistd::{close, read};
+#[cfg(not(target_os = "redox"))]
+use std::fs::File;
+#[cfg(not(target_os = "redox"))]
+use std::io::prelude::*;
+#[cfg(not(target_os = "redox"))]
+use std::os::unix::fs;
+#[cfg(not(target_os = "redox"))]
+use tempfile::{self, NamedTempFile};
+
+#[test]
+#[cfg(not(target_os = "redox"))]
+// QEMU does not handle openat well enough to satisfy this test
+// https://gitlab.com/qemu-project/qemu/-/issues/829
+#[cfg_attr(qemu, ignore)]
+fn test_openat() {
+ const CONTENTS: &[u8] = b"abcd";
+ let mut tmp = NamedTempFile::new().unwrap();
+ tmp.write_all(CONTENTS).unwrap();
+
+ let dirfd =
+ open(tmp.path().parent().unwrap(), OFlag::empty(), Mode::empty())
+ .unwrap();
+ let fd = openat(
+ dirfd,
+ tmp.path().file_name().unwrap(),
+ OFlag::O_RDONLY,
+ Mode::empty(),
+ )
+ .unwrap();
+
+ let mut buf = [0u8; 1024];
+ assert_eq!(4, read(fd, &mut buf).unwrap());
+ assert_eq!(CONTENTS, &buf[0..4]);
+
+ close(fd).unwrap();
+ close(dirfd).unwrap();
+}
+
+#[test]
+#[cfg(not(target_os = "redox"))]
+fn test_renameat() {
+ 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();
+ renameat(Some(old_dirfd), "old", Some(new_dirfd), "new").unwrap();
+ assert_eq!(
+ renameat(Some(old_dirfd), "old", Some(new_dirfd), "new").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_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_all(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_all(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() {
+ let tempdir = tempfile::tempdir().unwrap();
+ let src = tempdir.path().join("a");
+ let dst = tempdir.path().join("b");
+ println!("a: {:?}, b: {:?}", &src, &dst);
+ fs::symlink(src.as_path(), dst.as_path()).unwrap();
+ let dirfd = open(tempdir.path(), OFlag::empty(), Mode::empty()).unwrap();
+ let expected_dir = src.to_str().unwrap();
+
+ assert_eq!(readlink(&dst).unwrap().to_str().unwrap(), expected_dir);
+ assert_eq!(
+ readlinkat(dirfd, "b").unwrap().to_str().unwrap(),
+ expected_dir
+ );
+}
+
+#[cfg(any(target_os = "linux", target_os = "android"))]
+mod linux_android {
+ use libc::loff_t;
+ use std::io::prelude::*;
+ use std::io::IoSlice;
+ use std::os::unix::prelude::*;
+
+ use nix::fcntl::*;
+ use nix::unistd::{close, pipe, read, write};
+
+ use tempfile::tempfile;
+ #[cfg(any(target_os = "linux"))]
+ use tempfile::NamedTempFile;
+
+ use crate::*;
+
+ /// This test creates a temporary file containing the contents
+ /// 'foobarbaz' and uses the `copy_file_range` call to transfer
+ /// 3 bytes at offset 3 (`bar`) to another empty file at offset 0. The
+ /// 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).
+ #[test]
+ // QEMU does not support copy_file_range. Skip under qemu
+ #[cfg_attr(qemu, ignore)]
+ fn test_copy_file_range() {
+ const CONTENTS: &[u8] = b"foobarbaz";
+
+ let mut tmp1 = tempfile().unwrap();
+ let mut tmp2 = tempfile().unwrap();
+
+ tmp1.write_all(CONTENTS).unwrap();
+ tmp1.flush().unwrap();
+
+ let mut from_offset: i64 = 3;
+ copy_file_range(
+ tmp1.as_raw_fd(),
+ Some(&mut from_offset),
+ tmp2.as_raw_fd(),
+ None,
+ 3,
+ )
+ .unwrap();
+
+ let mut res: String = String::new();
+ tmp2.rewind().unwrap();
+ tmp2.read_to_string(&mut res).unwrap();
+
+ assert_eq!(res, String::from("bar"));
+ assert_eq!(from_offset, 6);
+ }
+
+ #[test]
+ fn test_splice() {
+ const CONTENTS: &[u8] = b"abcdef123456";
+ let mut tmp = tempfile().unwrap();
+ tmp.write_all(CONTENTS).unwrap();
+
+ let (rd, wr) = pipe().unwrap();
+ let mut offset: loff_t = 5;
+ let res = splice(
+ tmp.as_raw_fd(),
+ Some(&mut offset),
+ wr,
+ None,
+ 2,
+ SpliceFFlags::empty(),
+ )
+ .unwrap();
+
+ assert_eq!(2, res);
+
+ let mut buf = [0u8; 1024];
+ assert_eq!(2, read(rd, &mut buf).unwrap());
+ assert_eq!(b"f1", &buf[0..2]);
+ assert_eq!(7, offset);
+
+ close(rd).unwrap();
+ close(wr).unwrap();
+ }
+
+ #[test]
+ fn test_tee() {
+ let (rd1, wr1) = pipe().unwrap();
+ let (rd2, wr2) = pipe().unwrap();
+
+ write(wr1, b"abc").unwrap();
+ let res = tee(rd1, wr2, 2, SpliceFFlags::empty()).unwrap();
+
+ assert_eq!(2, res);
+
+ let mut buf = [0u8; 1024];
+
+ // Check the tee'd bytes are at rd2.
+ assert_eq!(2, read(rd2, &mut buf).unwrap());
+ assert_eq!(b"ab", &buf[0..2]);
+
+ // Check all the bytes are still at rd1.
+ assert_eq!(3, read(rd1, &mut buf).unwrap());
+ assert_eq!(b"abc", &buf[0..3]);
+
+ close(rd1).unwrap();
+ close(wr1).unwrap();
+ close(rd2).unwrap();
+ close(wr2).unwrap();
+ }
+
+ #[test]
+ fn test_vmsplice() {
+ let (rd, wr) = pipe().unwrap();
+
+ let buf1 = b"abcdef";
+ let buf2 = b"defghi";
+ let iovecs = vec![IoSlice::new(&buf1[0..3]), IoSlice::new(&buf2[0..3])];
+
+ let res = vmsplice(wr, &iovecs[..], SpliceFFlags::empty()).unwrap();
+
+ assert_eq!(6, res);
+
+ // Check the bytes can be read at rd.
+ let mut buf = [0u8; 32];
+ assert_eq!(6, read(rd, &mut buf).unwrap());
+ assert_eq!(b"abcdef", &buf[0..6]);
+
+ close(rd).unwrap();
+ close(wr).unwrap();
+ }
+
+ #[cfg(any(target_os = "linux"))]
+ #[test]
+ fn test_fallocate() {
+ let tmp = NamedTempFile::new().unwrap();
+
+ let fd = tmp.as_raw_fd();
+ fallocate(fd, FallocateFlags::empty(), 0, 100).unwrap();
+
+ // Check if we read exactly 100 bytes
+ let mut buf = [0u8; 200];
+ assert_eq!(100, read(fd, &mut buf).unwrap());
+ }
+
+ // The tests below are disabled for the listed targets
+ // due to OFD locks not being available in the kernel/libc
+ // versions used in the CI environment, probably because
+ // they run under QEMU.
+
+ #[test]
+ #[cfg(all(target_os = "linux", not(target_env = "musl")))]
+ #[cfg_attr(target_env = "uclibc", ignore)] // uclibc doesn't support OFD locks, but the test should still compile
+ fn test_ofd_write_lock() {
+ use nix::sys::stat::fstat;
+ use std::mem;
+
+ let tmp = NamedTempFile::new().unwrap();
+
+ let fd = tmp.as_raw_fd();
+ let statfs = nix::sys::statfs::fstatfs(&tmp).unwrap();
+ if statfs.filesystem_type() == nix::sys::statfs::OVERLAYFS_SUPER_MAGIC {
+ // OverlayFS is a union file system. It returns one inode value in
+ // stat(2), but a different one shows up in /proc/locks. So we must
+ // skip the test.
+ skip!("/proc/locks does not work on overlayfs");
+ }
+ let inode = fstat(fd).expect("fstat failed").st_ino as usize;
+
+ let mut flock: libc::flock = unsafe {
+ mem::zeroed() // required for Linux/mips
+ };
+ flock.l_type = libc::F_WRLCK as libc::c_short;
+ flock.l_whence = libc::SEEK_SET as libc::c_short;
+ flock.l_start = 0;
+ flock.l_len = 0;
+ flock.l_pid = 0;
+ fcntl(fd, FcntlArg::F_OFD_SETLKW(&flock)).expect("write lock failed");
+ assert_eq!(
+ Some(("OFDLCK".to_string(), "WRITE".to_string())),
+ lock_info(inode)
+ );
+
+ flock.l_type = libc::F_UNLCK as libc::c_short;
+ fcntl(fd, FcntlArg::F_OFD_SETLKW(&flock)).expect("write unlock failed");
+ assert_eq!(None, lock_info(inode));
+ }
+
+ #[test]
+ #[cfg(all(target_os = "linux", not(target_env = "musl")))]
+ #[cfg_attr(target_env = "uclibc", ignore)] // uclibc doesn't support OFD locks, but the test should still compile
+ fn test_ofd_read_lock() {
+ use nix::sys::stat::fstat;
+ use std::mem;
+
+ let tmp = NamedTempFile::new().unwrap();
+
+ let fd = tmp.as_raw_fd();
+ let statfs = nix::sys::statfs::fstatfs(&tmp).unwrap();
+ if statfs.filesystem_type() == nix::sys::statfs::OVERLAYFS_SUPER_MAGIC {
+ // OverlayFS is a union file system. It returns one inode value in
+ // stat(2), but a different one shows up in /proc/locks. So we must
+ // skip the test.
+ skip!("/proc/locks does not work on overlayfs");
+ }
+ let inode = fstat(fd).expect("fstat failed").st_ino as usize;
+
+ let mut flock: libc::flock = unsafe {
+ mem::zeroed() // required for Linux/mips
+ };
+ flock.l_type = libc::F_RDLCK as libc::c_short;
+ flock.l_whence = libc::SEEK_SET as libc::c_short;
+ flock.l_start = 0;
+ flock.l_len = 0;
+ flock.l_pid = 0;
+ fcntl(fd, FcntlArg::F_OFD_SETLKW(&flock)).expect("read lock failed");
+ assert_eq!(
+ Some(("OFDLCK".to_string(), "READ".to_string())),
+ lock_info(inode)
+ );
+
+ flock.l_type = libc::F_UNLCK as libc::c_short;
+ fcntl(fd, FcntlArg::F_OFD_SETLKW(&flock)).expect("read unlock failed");
+ assert_eq!(None, lock_info(inode));
+ }
+
+ #[cfg(all(target_os = "linux", not(target_env = "musl")))]
+ fn lock_info(inode: usize) -> Option<(String, String)> {
+ use std::{fs::File, io::BufReader};
+
+ let file = File::open("/proc/locks").expect("open /proc/locks failed");
+ let buf = BufReader::new(file);
+
+ for line in buf.lines() {
+ let line = line.unwrap();
+ let parts: Vec<_> = line.split_whitespace().collect();
+ let lock_type = parts[1];
+ let lock_access = parts[3];
+ let ino_parts: Vec<_> = parts[5].split(':').collect();
+ let ino: usize = ino_parts[2].parse().unwrap();
+ if ino == inode {
+ return Some((lock_type.to_string(), lock_access.to_string()));
+ }
+ }
+ None
+ }
+}
+
+#[cfg(any(
+ target_os = "linux",
+ target_os = "android",
+ target_os = "emscripten",
+ target_os = "fuchsia",
+ target_os = "wasi",
+ target_env = "uclibc",
+ target_os = "freebsd"
+))]
+mod test_posix_fadvise {
+
+ use nix::errno::Errno;
+ use nix::fcntl::*;
+ use nix::unistd::pipe;
+ use std::os::unix::io::{AsRawFd, RawFd};
+ use tempfile::NamedTempFile;
+
+ #[test]
+ fn test_success() {
+ let tmp = NamedTempFile::new().unwrap();
+ let fd = tmp.as_raw_fd();
+ posix_fadvise(fd, 0, 100, PosixFadviseAdvice::POSIX_FADV_WILLNEED)
+ .expect("posix_fadvise failed");
+ }
+
+ #[test]
+ fn test_errno() {
+ let (rd, _wr) = pipe().unwrap();
+ let res = posix_fadvise(
+ rd as RawFd,
+ 0,
+ 100,
+ PosixFadviseAdvice::POSIX_FADV_WILLNEED,
+ );
+ assert_eq!(res, Err(Errno::ESPIPE));
+ }
+}
+
+#[cfg(any(
+ target_os = "linux",
+ target_os = "android",
+ target_os = "dragonfly",
+ target_os = "emscripten",
+ target_os = "fuchsia",
+ target_os = "wasi",
+ target_os = "freebsd"
+))]
+mod test_posix_fallocate {
+
+ use nix::errno::Errno;
+ use nix::fcntl::*;
+ use nix::unistd::pipe;
+ use std::{
+ io::Read,
+ os::unix::io::{AsRawFd, RawFd},
+ };
+ use tempfile::NamedTempFile;
+
+ #[test]
+ fn success() {
+ const LEN: usize = 100;
+ let mut tmp = NamedTempFile::new().unwrap();
+ let fd = tmp.as_raw_fd();
+ let res = posix_fallocate(fd, 0, LEN as libc::off_t);
+ match res {
+ Ok(_) => {
+ let mut data = [1u8; LEN];
+ assert_eq!(tmp.read(&mut data).expect("read failure"), LEN);
+ assert_eq!(&data[..], &[0u8; LEN][..]);
+ }
+ 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.
+ // There's no way to tell for sure whether the file system
+ // supports posix_fallocate, so we must pass the test if it
+ // returns EINVAL.
+ }
+ _ => res.unwrap(),
+ }
+ }
+
+ #[test]
+ fn errno() {
+ let (rd, _wr) = pipe().unwrap();
+ let err = posix_fallocate(rd as RawFd, 0, 100).unwrap_err();
+ match err {
+ Errno::EINVAL | Errno::ENODEV | Errno::ESPIPE | Errno::EBADF => (),
+ errno => panic!("unexpected errno {}", errno,),
+ }
+ }
+}
diff --git a/third_party/rust/nix/test/test_kmod/hello_mod/Makefile b/third_party/rust/nix/test/test_kmod/hello_mod/Makefile
new file mode 100644
index 0000000000..74c99b77e9
--- /dev/null
+++ b/third_party/rust/nix/test/test_kmod/hello_mod/Makefile
@@ -0,0 +1,7 @@
+obj-m += hello.o
+
+all:
+ make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) modules
+
+clean:
+ make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) clean
diff --git a/third_party/rust/nix/test/test_kmod/hello_mod/hello.c b/third_party/rust/nix/test/test_kmod/hello_mod/hello.c
new file mode 100644
index 0000000000..1c34987d2a
--- /dev/null
+++ b/third_party/rust/nix/test/test_kmod/hello_mod/hello.c
@@ -0,0 +1,26 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0+ or MIT
+ */
+#include <linux/module.h>
+#include <linux/kernel.h>
+
+static int number= 1;
+static char *who = "World";
+
+module_param(number, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+MODULE_PARM_DESC(myint, "Just some number");
+module_param(who, charp, 0000);
+MODULE_PARM_DESC(who, "Whot to greet");
+
+int init_module(void)
+{
+ printk(KERN_INFO "Hello %s (%d)!\n", who, number);
+ return 0;
+}
+
+void cleanup_module(void)
+{
+ printk(KERN_INFO "Goodbye %s (%d)!\n", who, number);
+}
+
+MODULE_LICENSE("Dual MIT/GPL");
diff --git a/third_party/rust/nix/test/test_kmod/mod.rs b/third_party/rust/nix/test/test_kmod/mod.rs
new file mode 100644
index 0000000000..6f9aaa897f
--- /dev/null
+++ b/third_party/rust/nix/test/test_kmod/mod.rs
@@ -0,0 +1,188 @@
+use crate::*;
+use std::fs::copy;
+use std::path::PathBuf;
+use std::process::Command;
+use tempfile::{tempdir, TempDir};
+
+fn compile_kernel_module() -> (PathBuf, String, TempDir) {
+ let _m = crate::FORK_MTX.lock();
+
+ let tmp_dir =
+ tempdir().expect("unable to create temporary build directory");
+
+ copy(
+ "test/test_kmod/hello_mod/hello.c",
+ tmp_dir.path().join("hello.c"),
+ )
+ .expect("unable to copy hello.c to temporary build directory");
+ copy(
+ "test/test_kmod/hello_mod/Makefile",
+ tmp_dir.path().join("Makefile"),
+ )
+ .expect("unable to copy Makefile to temporary build directory");
+
+ let status = Command::new("make")
+ .current_dir(tmp_dir.path())
+ .status()
+ .expect("failed to run make");
+
+ assert!(status.success());
+
+ // Return the relative path of the build kernel module
+ (tmp_dir.path().join("hello.ko"), "hello".to_owned(), tmp_dir)
+}
+
+use nix::errno::Errno;
+use nix::kmod::{delete_module, DeleteModuleFlags};
+use nix::kmod::{finit_module, init_module, ModuleInitFlags};
+use std::ffi::CString;
+use std::fs::File;
+use std::io::Read;
+
+#[test]
+fn test_finit_and_delete_module() {
+ require_capability!("test_finit_and_delete_module", CAP_SYS_MODULE);
+ let _m0 = crate::KMOD_MTX.lock();
+ let _m1 = crate::CWD_LOCK.read();
+
+ let (kmod_path, kmod_name, _kmod_dir) = compile_kernel_module();
+
+ let f = File::open(kmod_path).expect("unable to open kernel module");
+ finit_module(&f, &CString::new("").unwrap(), ModuleInitFlags::empty())
+ .expect("unable to load kernel module");
+
+ delete_module(
+ &CString::new(kmod_name).unwrap(),
+ DeleteModuleFlags::empty(),
+ )
+ .expect("unable to unload kernel module");
+}
+
+#[test]
+fn test_finit_and_delete_module_with_params() {
+ require_capability!(
+ "test_finit_and_delete_module_with_params",
+ CAP_SYS_MODULE
+ );
+ let _m0 = crate::KMOD_MTX.lock();
+ let _m1 = crate::CWD_LOCK.read();
+
+ let (kmod_path, kmod_name, _kmod_dir) = compile_kernel_module();
+
+ let f = File::open(kmod_path).expect("unable to open kernel module");
+ finit_module(
+ &f,
+ &CString::new("who=Rust number=2018").unwrap(),
+ ModuleInitFlags::empty(),
+ )
+ .expect("unable to load kernel module");
+
+ delete_module(
+ &CString::new(kmod_name).unwrap(),
+ DeleteModuleFlags::empty(),
+ )
+ .expect("unable to unload kernel module");
+}
+
+#[test]
+fn test_init_and_delete_module() {
+ require_capability!("test_init_and_delete_module", CAP_SYS_MODULE);
+ let _m0 = crate::KMOD_MTX.lock();
+ let _m1 = crate::CWD_LOCK.read();
+
+ let (kmod_path, kmod_name, _kmod_dir) = compile_kernel_module();
+
+ let mut f = File::open(kmod_path).expect("unable to open kernel module");
+ let mut contents: Vec<u8> = Vec::new();
+ f.read_to_end(&mut contents)
+ .expect("unable to read kernel module content to buffer");
+ init_module(&contents, &CString::new("").unwrap())
+ .expect("unable to load kernel module");
+
+ delete_module(
+ &CString::new(kmod_name).unwrap(),
+ DeleteModuleFlags::empty(),
+ )
+ .expect("unable to unload kernel module");
+}
+
+#[test]
+fn test_init_and_delete_module_with_params() {
+ require_capability!(
+ "test_init_and_delete_module_with_params",
+ CAP_SYS_MODULE
+ );
+ let _m0 = crate::KMOD_MTX.lock();
+ let _m1 = crate::CWD_LOCK.read();
+
+ let (kmod_path, kmod_name, _kmod_dir) = compile_kernel_module();
+
+ let mut f = File::open(kmod_path).expect("unable to open kernel module");
+ let mut contents: Vec<u8> = Vec::new();
+ f.read_to_end(&mut contents)
+ .expect("unable to read kernel module content to buffer");
+ init_module(&contents, &CString::new("who=Nix number=2015").unwrap())
+ .expect("unable to load kernel module");
+
+ delete_module(
+ &CString::new(kmod_name).unwrap(),
+ DeleteModuleFlags::empty(),
+ )
+ .expect("unable to unload kernel module");
+}
+
+#[test]
+fn test_finit_module_invalid() {
+ require_capability!("test_finit_module_invalid", CAP_SYS_MODULE);
+ let _m0 = crate::KMOD_MTX.lock();
+ let _m1 = crate::CWD_LOCK.read();
+
+ let kmod_path = "/dev/zero";
+
+ 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(), Errno::EINVAL);
+}
+
+#[test]
+fn test_finit_module_twice_and_delete_module() {
+ require_capability!(
+ "test_finit_module_twice_and_delete_module",
+ CAP_SYS_MODULE
+ );
+ let _m0 = crate::KMOD_MTX.lock();
+ let _m1 = crate::CWD_LOCK.read();
+
+ let (kmod_path, kmod_name, _kmod_dir) = compile_kernel_module();
+
+ let f = File::open(kmod_path).expect("unable to open kernel module");
+ finit_module(&f, &CString::new("").unwrap(), ModuleInitFlags::empty())
+ .expect("unable to load kernel module");
+
+ let result =
+ finit_module(&f, &CString::new("").unwrap(), ModuleInitFlags::empty());
+
+ assert_eq!(result.unwrap_err(), Errno::EEXIST);
+
+ delete_module(
+ &CString::new(kmod_name).unwrap(),
+ DeleteModuleFlags::empty(),
+ )
+ .expect("unable to unload kernel module");
+}
+
+#[test]
+fn test_delete_module_not_loaded() {
+ require_capability!("test_delete_module_not_loaded", CAP_SYS_MODULE);
+ let _m0 = crate::KMOD_MTX.lock();
+ let _m1 = crate::CWD_LOCK.read();
+
+ let result = delete_module(
+ &CString::new("hello").unwrap(),
+ DeleteModuleFlags::empty(),
+ );
+
+ assert_eq!(result.unwrap_err(), Errno::ENOENT);
+}
diff --git a/third_party/rust/nix/test/test_mount.rs b/third_party/rust/nix/test/test_mount.rs
new file mode 100644
index 0000000000..2fd612e358
--- /dev/null
+++ b/third_party/rust/nix/test/test_mount.rs
@@ -0,0 +1,271 @@
+mod common;
+
+// Implementation note: to allow unprivileged users to run it, this test makes
+// use of user and mount namespaces. On systems that allow unprivileged user
+// namespaces (Linux >= 3.8 compiled with CONFIG_USER_NS), the test should run
+// without root.
+
+#[cfg(target_os = "linux")]
+mod test_mount {
+ use std::fs::{self, File};
+ use std::io::{self, Read, Write};
+ use std::os::unix::fs::OpenOptionsExt;
+ use std::os::unix::fs::PermissionsExt;
+ use std::process::{self, Command};
+
+ use libc::{EACCES, EROFS};
+
+ use nix::errno::Errno;
+ use nix::mount::{mount, umount, MsFlags};
+ use nix::sched::{unshare, CloneFlags};
+ use nix::sys::stat::{self, Mode};
+ use nix::unistd::getuid;
+
+ static SCRIPT_CONTENTS: &[u8] = b"#!/bin/sh
+exit 23";
+
+ const EXPECTED_STATUS: i32 = 23;
+
+ const NONE: Option<&'static [u8]> = None;
+ #[allow(clippy::bind_instead_of_map)] // False positive
+ pub fn test_mount_tmpfs_without_flags_allows_rwx() {
+ let tempdir = tempfile::tempdir().unwrap();
+
+ mount(
+ NONE,
+ tempdir.path(),
+ Some(b"tmpfs".as_ref()),
+ MsFlags::empty(),
+ NONE,
+ )
+ .unwrap_or_else(|e| panic!("mount failed: {}", e));
+
+ let test_path = tempdir.path().join("test");
+
+ // Verify write.
+ fs::OpenOptions::new()
+ .create(true)
+ .write(true)
+ .mode((Mode::S_IRWXU | Mode::S_IRWXG | Mode::S_IRWXO).bits())
+ .open(&test_path)
+ .or_else(|e| {
+ if Errno::from_i32(e.raw_os_error().unwrap())
+ == Errno::EOVERFLOW
+ {
+ // Skip tests on certain Linux kernels which have a bug
+ // regarding tmpfs in namespaces.
+ // Ubuntu 14.04 and 16.04 are known to be affected; 16.10 is
+ // not. There is no legitimate reason for open(2) to return
+ // EOVERFLOW here.
+ // https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1659087
+ let stderr = io::stderr();
+ let mut handle = stderr.lock();
+ writeln!(
+ handle,
+ "Buggy Linux kernel detected. Skipping test."
+ )
+ .unwrap();
+ process::exit(0);
+ } else {
+ panic!("open failed: {}", e);
+ }
+ })
+ .and_then(|mut f| f.write(SCRIPT_CONTENTS))
+ .unwrap_or_else(|e| panic!("write failed: {}", e));
+
+ // Verify read.
+ let mut buf = Vec::new();
+ File::open(&test_path)
+ .and_then(|mut f| f.read_to_end(&mut buf))
+ .unwrap_or_else(|e| panic!("read failed: {}", e));
+ assert_eq!(buf, SCRIPT_CONTENTS);
+
+ // Verify execute.
+ assert_eq!(
+ EXPECTED_STATUS,
+ Command::new(&test_path)
+ .status()
+ .unwrap_or_else(|e| panic!("exec failed: {}", e))
+ .code()
+ .unwrap_or_else(|| panic!("child killed by signal"))
+ );
+
+ umount(tempdir.path())
+ .unwrap_or_else(|e| panic!("umount failed: {}", e));
+ }
+
+ pub fn test_mount_rdonly_disallows_write() {
+ let tempdir = tempfile::tempdir().unwrap();
+
+ mount(
+ NONE,
+ tempdir.path(),
+ Some(b"tmpfs".as_ref()),
+ MsFlags::MS_RDONLY,
+ NONE,
+ )
+ .unwrap_or_else(|e| panic!("mount failed: {}", e));
+
+ // EROFS: Read-only file system
+ assert_eq!(
+ EROFS,
+ File::create(tempdir.path().join("test"))
+ .unwrap_err()
+ .raw_os_error()
+ .unwrap()
+ );
+
+ umount(tempdir.path())
+ .unwrap_or_else(|e| panic!("umount failed: {}", e));
+ }
+
+ pub fn test_mount_noexec_disallows_exec() {
+ let tempdir = tempfile::tempdir().unwrap();
+
+ mount(
+ NONE,
+ tempdir.path(),
+ Some(b"tmpfs".as_ref()),
+ MsFlags::MS_NOEXEC,
+ NONE,
+ )
+ .unwrap_or_else(|e| panic!("mount failed: {}", e));
+
+ let test_path = tempdir.path().join("test");
+
+ fs::OpenOptions::new()
+ .create(true)
+ .write(true)
+ .mode((Mode::S_IRWXU | Mode::S_IRWXG | Mode::S_IRWXO).bits())
+ .open(&test_path)
+ .and_then(|mut f| f.write(SCRIPT_CONTENTS))
+ .unwrap_or_else(|e| panic!("write failed: {}", e));
+
+ // Verify that we cannot execute despite a+x permissions being set.
+ let mode = stat::Mode::from_bits_truncate(
+ fs::metadata(&test_path)
+ .map(|md| md.permissions().mode())
+ .unwrap_or_else(|e| panic!("metadata failed: {}", e)),
+ );
+
+ assert!(
+ mode.contains(Mode::S_IXUSR | Mode::S_IXGRP | Mode::S_IXOTH),
+ "{:?} did not have execute permissions",
+ &test_path
+ );
+
+ // EACCES: Permission denied
+ assert_eq!(
+ EACCES,
+ Command::new(&test_path)
+ .status()
+ .unwrap_err()
+ .raw_os_error()
+ .unwrap()
+ );
+
+ umount(tempdir.path())
+ .unwrap_or_else(|e| panic!("umount failed: {}", e));
+ }
+
+ pub fn test_mount_bind() {
+ let tempdir = tempfile::tempdir().unwrap();
+ let file_name = "test";
+
+ {
+ let mount_point = tempfile::tempdir().unwrap();
+
+ mount(
+ Some(tempdir.path()),
+ mount_point.path(),
+ NONE,
+ MsFlags::MS_BIND,
+ NONE,
+ )
+ .unwrap_or_else(|e| panic!("mount failed: {}", e));
+
+ fs::OpenOptions::new()
+ .create(true)
+ .write(true)
+ .mode((Mode::S_IRWXU | Mode::S_IRWXG | Mode::S_IRWXO).bits())
+ .open(mount_point.path().join(file_name))
+ .and_then(|mut f| f.write(SCRIPT_CONTENTS))
+ .unwrap_or_else(|e| panic!("write failed: {}", e));
+
+ umount(mount_point.path())
+ .unwrap_or_else(|e| panic!("umount failed: {}", e));
+ }
+
+ // Verify the file written in the mount shows up in source directory, even
+ // after unmounting.
+
+ let mut buf = Vec::new();
+ File::open(tempdir.path().join(file_name))
+ .and_then(|mut f| f.read_to_end(&mut buf))
+ .unwrap_or_else(|e| panic!("read failed: {}", e));
+ assert_eq!(buf, SCRIPT_CONTENTS);
+ }
+
+ pub fn setup_namespaces() {
+ // Hold on to the uid in the parent namespace.
+ let uid = getuid();
+
+ unshare(CloneFlags::CLONE_NEWNS | CloneFlags::CLONE_NEWUSER).unwrap_or_else(|e| {
+ let stderr = io::stderr();
+ let mut handle = stderr.lock();
+ writeln!(handle,
+ "unshare failed: {}. Are unprivileged user namespaces available?",
+ e).unwrap();
+ writeln!(handle, "mount is not being tested").unwrap();
+ // Exit with success because not all systems support unprivileged user namespaces, and
+ // that's not what we're testing for.
+ process::exit(0);
+ });
+
+ // Map user as uid 1000.
+ fs::OpenOptions::new()
+ .write(true)
+ .open("/proc/self/uid_map")
+ .and_then(|mut f| f.write(format!("1000 {} 1\n", uid).as_bytes()))
+ .unwrap_or_else(|e| panic!("could not write uid map: {}", e));
+ }
+}
+
+// Test runner
+
+/// Mimic normal test output (hackishly).
+#[cfg(target_os = "linux")]
+macro_rules! run_tests {
+ ( $($test_fn:ident),* ) => {{
+ println!();
+
+ $(
+ print!("test test_mount::{} ... ", stringify!($test_fn));
+ $test_fn();
+ println!("ok");
+ )*
+
+ println!();
+ }}
+}
+
+#[cfg(target_os = "linux")]
+fn main() {
+ use test_mount::{
+ setup_namespaces, test_mount_bind, test_mount_noexec_disallows_exec,
+ test_mount_rdonly_disallows_write,
+ test_mount_tmpfs_without_flags_allows_rwx,
+ };
+ skip_if_cirrus!("Fails for an unknown reason Cirrus CI. Bug #1351");
+ setup_namespaces();
+
+ run_tests!(
+ test_mount_tmpfs_without_flags_allows_rwx,
+ test_mount_rdonly_disallows_write,
+ test_mount_noexec_disallows_exec,
+ test_mount_bind
+ );
+}
+
+#[cfg(not(target_os = "linux"))]
+fn main() {}
diff --git a/third_party/rust/nix/test/test_mq.rs b/third_party/rust/nix/test/test_mq.rs
new file mode 100644
index 0000000000..7b48e7ac78
--- /dev/null
+++ b/third_party/rust/nix/test/test_mq.rs
@@ -0,0 +1,190 @@
+use cfg_if::cfg_if;
+use std::ffi::CString;
+use std::str;
+
+use nix::errno::Errno;
+use nix::mqueue::{mq_attr_member_t, mq_close, mq_open, mq_receive, mq_send};
+use nix::mqueue::{MQ_OFlag, MqAttr};
+use nix::sys::stat::Mode;
+
+// Defined as a macro such that the error source is reported as the caller's location.
+macro_rules! assert_attr_eq {
+ ($read_attr:ident, $initial_attr:ident) => {
+ cfg_if! {
+ if #[cfg(any(target_os = "dragonfly", target_os = "netbsd"))] {
+ // NetBSD (and others which inherit its implementation) include other flags
+ // in read_attr, such as those specified by oflag. Just make sure at least
+ // the correct bits are set.
+ assert_eq!($read_attr.flags() & $initial_attr.flags(), $initial_attr.flags());
+ assert_eq!($read_attr.maxmsg(), $initial_attr.maxmsg());
+ assert_eq!($read_attr.msgsize(), $initial_attr.msgsize());
+ assert_eq!($read_attr.curmsgs(), $initial_attr.curmsgs());
+ } else {
+ assert_eq!($read_attr, $initial_attr);
+ }
+ }
+ }
+}
+
+#[test]
+fn test_mq_send_and_receive() {
+ const MSG_SIZE: mq_attr_member_t = 32;
+ let attr = MqAttr::new(0, 10, MSG_SIZE, 0);
+ let mq_name = &CString::new(b"/a_nix_test_queue".as_ref()).unwrap();
+
+ 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(Errno::ENOSYS) = r0 {
+ println!("message queues not supported or module not loaded?");
+ return;
+ };
+ let mqd0 = r0.unwrap();
+ let msg_to_send = "msg_1";
+ mq_send(&mqd0, msg_to_send.as_bytes(), 1).unwrap();
+
+ let oflag1 = MQ_OFlag::O_CREAT | MQ_OFlag::O_RDONLY;
+ let mqd1 = mq_open(mq_name, oflag1, mode, Some(&attr)).unwrap();
+ let mut buf = [0u8; 32];
+ let mut prio = 0u32;
+ let len = mq_receive(&mqd1, &mut buf, &mut prio).unwrap();
+ assert_eq!(prio, 1);
+
+ mq_close(mqd1).unwrap();
+ mq_close(mqd0).unwrap();
+ assert_eq!(msg_to_send, str::from_utf8(&buf[0..len]).unwrap());
+}
+
+#[test]
+fn test_mq_getattr() {
+ use nix::mqueue::mq_getattr;
+ const MSG_SIZE: mq_attr_member_t = 32;
+ let initial_attr = MqAttr::new(0, 10, MSG_SIZE, 0);
+ let mq_name = &CString::new(b"/attr_test_get_attr".as_ref()).unwrap();
+ 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(Errno::ENOSYS) = r {
+ println!("message queues not supported or module not loaded?");
+ return;
+ };
+ let mqd = r.unwrap();
+
+ let read_attr = mq_getattr(&mqd).unwrap();
+ assert_attr_eq!(read_attr, initial_attr);
+ mq_close(mqd).unwrap();
+}
+
+// FIXME: Fix failures for mips in QEMU
+#[test]
+#[cfg_attr(
+ all(qemu, any(target_arch = "mips", target_arch = "mips64")),
+ ignore
+)]
+fn test_mq_setattr() {
+ use nix::mqueue::{mq_getattr, mq_setattr};
+ const MSG_SIZE: mq_attr_member_t = 32;
+ let initial_attr = MqAttr::new(0, 10, MSG_SIZE, 0);
+ let mq_name = &CString::new(b"/attr_test_get_attr".as_ref()).unwrap();
+ 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(Errno::ENOSYS) = r {
+ println!("message queues not supported or module not loaded?");
+ return;
+ };
+ let mqd = r.unwrap();
+
+ let new_attr = MqAttr::new(0, 20, MSG_SIZE * 2, 100);
+ let old_attr = mq_setattr(&mqd, &new_attr).unwrap();
+ assert_attr_eq!(old_attr, initial_attr);
+
+ // No changes here because according to the Linux man page only
+ // O_NONBLOCK can be set (see tests below)
+ #[cfg(not(any(target_os = "dragonfly", target_os = "netbsd")))]
+ {
+ let new_attr_get = mq_getattr(&mqd).unwrap();
+ assert_ne!(new_attr_get, new_attr);
+ }
+
+ let new_attr_non_blocking = MqAttr::new(
+ MQ_OFlag::O_NONBLOCK.bits() as mq_attr_member_t,
+ 10,
+ MSG_SIZE,
+ 0,
+ );
+ mq_setattr(&mqd, &new_attr_non_blocking).unwrap();
+ let new_attr_get = mq_getattr(&mqd).unwrap();
+
+ // now the O_NONBLOCK flag has been set
+ #[cfg(not(any(target_os = "dragonfly", target_os = "netbsd")))]
+ {
+ assert_ne!(new_attr_get, initial_attr);
+ }
+ assert_attr_eq!(new_attr_get, new_attr_non_blocking);
+ mq_close(mqd).unwrap();
+}
+
+// FIXME: Fix failures for mips in QEMU
+#[test]
+#[cfg_attr(
+ all(qemu, any(target_arch = "mips", target_arch = "mips64")),
+ ignore
+)]
+fn test_mq_set_nonblocking() {
+ use nix::mqueue::{mq_getattr, mq_remove_nonblock, mq_set_nonblock};
+ const MSG_SIZE: mq_attr_member_t = 32;
+ let initial_attr = MqAttr::new(0, 10, MSG_SIZE, 0);
+ let mq_name = &CString::new(b"/attr_test_get_attr".as_ref()).unwrap();
+ 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(Errno::ENOSYS) = r {
+ println!("message queues not supported or module not loaded?");
+ return;
+ };
+ let mqd = r.unwrap();
+ mq_set_nonblock(&mqd).unwrap();
+ let new_attr = mq_getattr(&mqd);
+ let o_nonblock_bits = MQ_OFlag::O_NONBLOCK.bits() as mq_attr_member_t;
+ assert_eq!(new_attr.unwrap().flags() & o_nonblock_bits, o_nonblock_bits);
+ mq_remove_nonblock(&mqd).unwrap();
+ let new_attr = mq_getattr(&mqd);
+ assert_eq!(new_attr.unwrap().flags() & o_nonblock_bits, 0);
+ mq_close(mqd).unwrap();
+}
+
+#[test]
+fn test_mq_unlink() {
+ use nix::mqueue::mq_unlink;
+ const MSG_SIZE: mq_attr_member_t = 32;
+ let initial_attr = MqAttr::new(0, 10, MSG_SIZE, 0);
+ let mq_name_opened = &CString::new(b"/mq_unlink_test".as_ref()).unwrap();
+ #[cfg(not(any(target_os = "dragonfly", target_os = "netbsd")))]
+ let mq_name_not_opened =
+ &CString::new(b"/mq_unlink_test".as_ref()).unwrap();
+ 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(Errno::ENOSYS) = r {
+ println!("message queues not supported or module not loaded?");
+ return;
+ };
+ let mqd = r.unwrap();
+
+ let res_unlink = mq_unlink(mq_name_opened);
+ assert_eq!(res_unlink, Ok(()));
+
+ // NetBSD (and others which inherit its implementation) defer removing the message
+ // queue name until all references are closed, whereas Linux and others remove the
+ // message queue name immediately.
+ #[cfg(not(any(target_os = "dragonfly", target_os = "netbsd")))]
+ {
+ let res_unlink_not_opened = mq_unlink(mq_name_not_opened);
+ 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(Errno::ENOENT));
+}
diff --git a/third_party/rust/nix/test/test_net.rs b/third_party/rust/nix/test/test_net.rs
new file mode 100644
index 0000000000..c44655a4c9
--- /dev/null
+++ b/third_party/rust/nix/test/test_net.rs
@@ -0,0 +1,19 @@
+use nix::net::if_::*;
+
+#[cfg(any(target_os = "android", target_os = "linux"))]
+const LOOPBACK: &[u8] = b"lo";
+
+#[cfg(not(any(
+ target_os = "android",
+ target_os = "linux",
+ target_os = "haiku"
+)))]
+const LOOPBACK: &[u8] = b"lo0";
+
+#[cfg(target_os = "haiku")]
+const LOOPBACK: &[u8] = b"loop";
+
+#[test]
+fn test_if_nametoindex() {
+ if_nametoindex(LOOPBACK).expect("assertion failed");
+}
diff --git a/third_party/rust/nix/test/test_nix_path.rs b/third_party/rust/nix/test/test_nix_path.rs
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/third_party/rust/nix/test/test_nix_path.rs
@@ -0,0 +1 @@
+
diff --git a/third_party/rust/nix/test/test_nmount.rs b/third_party/rust/nix/test/test_nmount.rs
new file mode 100644
index 0000000000..dec806a55f
--- /dev/null
+++ b/third_party/rust/nix/test/test_nmount.rs
@@ -0,0 +1,49 @@
+use crate::*;
+use nix::{
+ errno::Errno,
+ mount::{unmount, MntFlags, Nmount},
+};
+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/third_party/rust/nix/test/test_poll.rs b/third_party/rust/nix/test/test_poll.rs
new file mode 100644
index 0000000000..53964e26bb
--- /dev/null
+++ b/third_party/rust/nix/test/test_poll.rs
@@ -0,0 +1,84 @@
+use nix::{
+ errno::Errno,
+ poll::{poll, PollFd, PollFlags},
+ unistd::{pipe, write},
+};
+
+macro_rules! loop_while_eintr {
+ ($poll_expr: expr) => {
+ loop {
+ match $poll_expr {
+ Ok(nfds) => break nfds,
+ Err(Errno::EINTR) => (),
+ Err(e) => panic!("{}", e),
+ }
+ }
+ };
+}
+
+#[test]
+fn test_poll() {
+ let (r, w) = pipe().unwrap();
+ let mut fds = [PollFd::new(r, PollFlags::POLLIN)];
+
+ // Poll an idle pipe. Should timeout
+ let nfds = loop_while_eintr!(poll(&mut fds, 100));
+ assert_eq!(nfds, 0);
+ assert!(!fds[0].revents().unwrap().contains(PollFlags::POLLIN));
+
+ write(w, b".").unwrap();
+
+ // Poll a readable pipe. Should return an event.
+ let nfds = poll(&mut fds, 100).unwrap();
+ assert_eq!(nfds, 1);
+ assert!(fds[0].revents().unwrap().contains(PollFlags::POLLIN));
+}
+
+// ppoll(2) is the same as poll except for how it handles timeouts and signals.
+// Repeating the test for poll(2) should be sufficient to check that our
+// bindings are correct.
+#[cfg(any(
+ target_os = "android",
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "linux"
+))]
+#[test]
+fn test_ppoll() {
+ use nix::poll::ppoll;
+ use nix::sys::signal::SigSet;
+ use nix::sys::time::{TimeSpec, TimeValLike};
+
+ let timeout = TimeSpec::milliseconds(1);
+ let (r, w) = pipe().unwrap();
+ let mut fds = [PollFd::new(r, PollFlags::POLLIN)];
+
+ // Poll an idle pipe. Should timeout
+ let sigset = SigSet::empty();
+ let nfds = loop_while_eintr!(ppoll(&mut fds, Some(timeout), Some(sigset)));
+ assert_eq!(nfds, 0);
+ assert!(!fds[0].revents().unwrap().contains(PollFlags::POLLIN));
+
+ write(w, b".").unwrap();
+
+ // Poll a readable pipe. Should return an event.
+ let nfds = ppoll(&mut fds, Some(timeout), None).unwrap();
+ assert_eq!(nfds, 1);
+ assert!(fds[0].revents().unwrap().contains(PollFlags::POLLIN));
+}
+
+#[test]
+fn test_pollfd_fd() {
+ use std::os::unix::io::AsRawFd;
+
+ let pfd = PollFd::new(0x1234, PollFlags::empty());
+ assert_eq!(pfd.as_raw_fd(), 0x1234);
+}
+
+#[test]
+fn test_pollfd_events() {
+ let mut pfd = PollFd::new(-1, PollFlags::POLLIN);
+ assert_eq!(pfd.events(), PollFlags::POLLIN);
+ pfd.set_events(PollFlags::POLLOUT);
+ assert_eq!(pfd.events(), PollFlags::POLLOUT);
+}
diff --git a/third_party/rust/nix/test/test_pty.rs b/third_party/rust/nix/test/test_pty.rs
new file mode 100644
index 0000000000..5c27e2d632
--- /dev/null
+++ b/third_party/rust/nix/test/test_pty.rs
@@ -0,0 +1,313 @@
+use std::fs::File;
+use std::io::{Read, Write};
+use std::os::unix::prelude::*;
+use std::path::Path;
+use tempfile::tempfile;
+
+use libc::{_exit, STDOUT_FILENO};
+use nix::fcntl::{open, OFlag};
+use nix::pty::*;
+use nix::sys::stat;
+use nix::sys::termios::*;
+use nix::unistd::{close, pause, write};
+
+/// Regression test for Issue #659
+/// This is the correct way to explicitly close a `PtyMaster`
+#[test]
+fn test_explicit_close() {
+ let mut f = {
+ let m = posix_openpt(OFlag::O_RDWR).unwrap();
+ close(m.into_raw_fd()).unwrap();
+ tempfile().unwrap()
+ };
+ // This should work. But if there's been a double close, then it will
+ // return EBADF
+ f.write_all(b"whatever").unwrap();
+}
+
+/// Test equivalence of `ptsname` and `ptsname_r`
+#[test]
+#[cfg(any(target_os = "android", target_os = "linux"))]
+fn test_ptsname_equivalence() {
+ let _m = crate::PTSNAME_MTX.lock();
+
+ // Open a new PTTY master
+ let master_fd = posix_openpt(OFlag::O_RDWR).unwrap();
+ assert!(master_fd.as_raw_fd() > 0);
+
+ // Get the name of the slave
+ let slave_name = unsafe { ptsname(&master_fd) }.unwrap();
+ let slave_name_r = ptsname_r(&master_fd).unwrap();
+ assert_eq!(slave_name, slave_name_r);
+}
+
+/// Test data copying of `ptsname`
+// TODO need to run in a subprocess, since ptsname is non-reentrant
+#[test]
+#[cfg(any(target_os = "android", target_os = "linux"))]
+fn test_ptsname_copy() {
+ let _m = crate::PTSNAME_MTX.lock();
+
+ // Open a new PTTY master
+ let master_fd = posix_openpt(OFlag::O_RDWR).unwrap();
+ assert!(master_fd.as_raw_fd() > 0);
+
+ // Get the name of the slave
+ let slave_name1 = unsafe { ptsname(&master_fd) }.unwrap();
+ let slave_name2 = unsafe { ptsname(&master_fd) }.unwrap();
+ assert_eq!(slave_name1, slave_name2);
+ // Also make sure that the string was actually copied and they point to different parts of
+ // memory.
+ assert_ne!(slave_name1.as_ptr(), slave_name2.as_ptr());
+}
+
+/// Test data copying of `ptsname_r`
+#[test]
+#[cfg(any(target_os = "android", target_os = "linux"))]
+fn test_ptsname_r_copy() {
+ // Open a new PTTY master
+ let master_fd = posix_openpt(OFlag::O_RDWR).unwrap();
+ assert!(master_fd.as_raw_fd() > 0);
+
+ // Get the name of the slave
+ let slave_name1 = ptsname_r(&master_fd).unwrap();
+ let slave_name2 = ptsname_r(&master_fd).unwrap();
+ assert_eq!(slave_name1, slave_name2);
+ assert_ne!(slave_name1.as_ptr(), slave_name2.as_ptr());
+}
+
+/// Test that `ptsname` returns different names for different devices
+#[test]
+#[cfg(any(target_os = "android", target_os = "linux"))]
+fn test_ptsname_unique() {
+ let _m = crate::PTSNAME_MTX.lock();
+
+ // Open a new PTTY master
+ let master1_fd = posix_openpt(OFlag::O_RDWR).unwrap();
+ assert!(master1_fd.as_raw_fd() > 0);
+
+ // Open a second PTTY master
+ let master2_fd = posix_openpt(OFlag::O_RDWR).unwrap();
+ assert!(master2_fd.as_raw_fd() > 0);
+
+ // Get the name of the slave
+ let slave_name1 = unsafe { ptsname(&master1_fd) }.unwrap();
+ let slave_name2 = unsafe { ptsname(&master2_fd) }.unwrap();
+ assert_ne!(slave_name1, slave_name2);
+}
+
+/// Common setup for testing PTTY pairs
+fn open_ptty_pair() -> (PtyMaster, File) {
+ let _m = crate::PTSNAME_MTX.lock();
+
+ // Open a new PTTY master
+ let master = posix_openpt(OFlag::O_RDWR).expect("posix_openpt failed");
+
+ // Allow a slave to be generated for it
+ grantpt(&master).expect("grantpt failed");
+ unlockpt(&master).expect("unlockpt failed");
+
+ // Get the name of the slave
+ let slave_name = unsafe { ptsname(&master) }.expect("ptsname failed");
+
+ // Open the slave device
+ let slave_fd =
+ open(Path::new(&slave_name), OFlag::O_RDWR, stat::Mode::empty())
+ .unwrap();
+
+ #[cfg(target_os = "illumos")]
+ // TODO: rewrite using ioctl!
+ #[allow(clippy::comparison_chain)]
+ {
+ use libc::{ioctl, I_FIND, I_PUSH};
+
+ // On illumos systems, as per pts(7D), one must push STREAMS modules
+ // after opening a device path returned from ptsname().
+ let ptem = b"ptem\0";
+ let ldterm = b"ldterm\0";
+ let r = unsafe { ioctl(slave_fd, I_FIND, ldterm.as_ptr()) };
+ if r < 0 {
+ panic!("I_FIND failure");
+ } else if r == 0 {
+ if unsafe { ioctl(slave_fd, I_PUSH, ptem.as_ptr()) } < 0 {
+ panic!("I_PUSH ptem failure");
+ }
+ if unsafe { ioctl(slave_fd, I_PUSH, ldterm.as_ptr()) } < 0 {
+ panic!("I_PUSH ldterm failure");
+ }
+ }
+ }
+
+ let slave = unsafe { File::from_raw_fd(slave_fd) };
+
+ (master, slave)
+}
+
+/// Test opening a master/slave PTTY pair
+///
+/// This uses a common `open_ptty_pair` because much of these functions aren't useful by
+/// themselves. So for this test we perform the basic act of getting a file handle for a
+/// master/slave PTTY pair, then just sanity-check the raw values.
+#[test]
+fn test_open_ptty_pair() {
+ let (master, slave) = open_ptty_pair();
+ assert!(master.as_raw_fd() > 0);
+ assert!(slave.as_raw_fd() > 0);
+}
+
+/// Put the terminal in raw mode.
+fn make_raw(fd: RawFd) {
+ let mut termios = tcgetattr(fd).unwrap();
+ cfmakeraw(&mut termios);
+ tcsetattr(fd, SetArg::TCSANOW, &termios).unwrap();
+}
+
+/// Test `io::Read` on the PTTY master
+#[test]
+fn test_read_ptty_pair() {
+ let (mut master, mut slave) = open_ptty_pair();
+ make_raw(slave.as_raw_fd());
+
+ let mut buf = [0u8; 5];
+ slave.write_all(b"hello").unwrap();
+ master.read_exact(&mut buf).unwrap();
+ assert_eq!(&buf, b"hello");
+
+ let mut master = &master;
+ slave.write_all(b"hello").unwrap();
+ master.read_exact(&mut buf).unwrap();
+ assert_eq!(&buf, b"hello");
+}
+
+/// Test `io::Write` on the PTTY master
+#[test]
+fn test_write_ptty_pair() {
+ let (mut master, mut slave) = open_ptty_pair();
+ make_raw(slave.as_raw_fd());
+
+ let mut buf = [0u8; 5];
+ master.write_all(b"adios").unwrap();
+ slave.read_exact(&mut buf).unwrap();
+ assert_eq!(&buf, b"adios");
+
+ let mut master = &master;
+ master.write_all(b"adios").unwrap();
+ slave.read_exact(&mut buf).unwrap();
+ assert_eq!(&buf, b"adios");
+}
+
+#[test]
+fn test_openpty() {
+ // openpty uses ptname(3) internally
+ let _m = crate::PTSNAME_MTX.lock();
+
+ let pty = openpty(None, None).unwrap();
+ assert!(pty.master > 0);
+ assert!(pty.slave > 0);
+
+ // Writing to one should be readable on the other one
+ let string = "foofoofoo\n";
+ let mut buf = [0u8; 10];
+ write(pty.master, string.as_bytes()).unwrap();
+ crate::read_exact(pty.slave, &mut buf);
+
+ assert_eq!(&buf, string.as_bytes());
+
+ // Read the echo as well
+ let echoed_string = "foofoofoo\r\n";
+ let mut buf = [0u8; 11];
+ crate::read_exact(pty.master, &mut buf);
+ assert_eq!(&buf, echoed_string.as_bytes());
+
+ let string2 = "barbarbarbar\n";
+ let echoed_string2 = "barbarbarbar\r\n";
+ let mut buf = [0u8; 14];
+ write(pty.slave, string2.as_bytes()).unwrap();
+ crate::read_exact(pty.master, &mut buf);
+
+ assert_eq!(&buf, echoed_string2.as_bytes());
+
+ close(pty.master).unwrap();
+ close(pty.slave).unwrap();
+}
+
+#[test]
+fn test_openpty_with_termios() {
+ // openpty uses ptname(3) internally
+ let _m = crate::PTSNAME_MTX.lock();
+
+ // Open one pty to get attributes for the second one
+ let mut termios = {
+ let pty = openpty(None, None).unwrap();
+ assert!(pty.master > 0);
+ assert!(pty.slave > 0);
+ let termios = tcgetattr(pty.slave).unwrap();
+ close(pty.master).unwrap();
+ close(pty.slave).unwrap();
+ termios
+ };
+ // Make sure newlines are not transformed so the data is preserved when sent.
+ termios.output_flags.remove(OutputFlags::ONLCR);
+
+ let pty = openpty(None, &termios).unwrap();
+ // Must be valid file descriptors
+ assert!(pty.master > 0);
+ assert!(pty.slave > 0);
+
+ // Writing to one should be readable on the other one
+ let string = "foofoofoo\n";
+ let mut buf = [0u8; 10];
+ write(pty.master, string.as_bytes()).unwrap();
+ crate::read_exact(pty.slave, &mut buf);
+
+ assert_eq!(&buf, string.as_bytes());
+
+ // read the echo as well
+ let echoed_string = "foofoofoo\n";
+ crate::read_exact(pty.master, &mut buf);
+ assert_eq!(&buf, echoed_string.as_bytes());
+
+ let string2 = "barbarbarbar\n";
+ let echoed_string2 = "barbarbarbar\n";
+ let mut buf = [0u8; 13];
+ write(pty.slave, string2.as_bytes()).unwrap();
+ crate::read_exact(pty.master, &mut buf);
+
+ assert_eq!(&buf, echoed_string2.as_bytes());
+
+ close(pty.master).unwrap();
+ close(pty.slave).unwrap();
+}
+
+#[test]
+fn test_forkpty() {
+ use nix::sys::signal::*;
+ use nix::sys::wait::wait;
+ use nix::unistd::ForkResult::*;
+ // forkpty calls openpty which uses ptname(3) internally.
+ let _m0 = crate::PTSNAME_MTX.lock();
+ // forkpty spawns a child process
+ let _m1 = crate::FORK_MTX.lock();
+
+ let string = "naninani\n";
+ let echoed_string = "naninani\r\n";
+ let pty = unsafe { forkpty(None, None).unwrap() };
+ match pty.fork_result {
+ Child => {
+ write(STDOUT_FILENO, string.as_bytes()).unwrap();
+ pause(); // we need the child to stay alive until the parent calls read
+ unsafe {
+ _exit(0);
+ }
+ }
+ Parent { child } => {
+ let mut buf = [0u8; 10];
+ assert!(child.as_raw() > 0);
+ crate::read_exact(pty.master, &mut buf);
+ kill(child, SIGTERM).unwrap();
+ wait().unwrap(); // keep other tests using generic wait from getting our child
+ assert_eq!(&buf, echoed_string.as_bytes());
+ close(pty.master).unwrap();
+ }
+ }
+}
diff --git a/third_party/rust/nix/test/test_ptymaster_drop.rs b/third_party/rust/nix/test/test_ptymaster_drop.rs
new file mode 100644
index 0000000000..ffbaa56977
--- /dev/null
+++ b/third_party/rust/nix/test/test_ptymaster_drop.rs
@@ -0,0 +1,20 @@
+#[cfg(not(any(target_os = "redox", target_os = "fuchsia")))]
+mod t {
+ use nix::fcntl::OFlag;
+ use nix::pty::*;
+ use nix::unistd::close;
+ use std::os::unix::io::AsRawFd;
+
+ /// Regression test for Issue #659
+ ///
+ /// `PtyMaster` should panic rather than double close the file descriptor
+ /// This must run in its own test process because it deliberately creates a
+ /// race condition.
+ #[test]
+ #[should_panic(expected = "Closing an invalid file descriptor!")]
+ fn test_double_close() {
+ let m = posix_openpt(OFlag::O_RDWR).unwrap();
+ close(m.as_raw_fd()).unwrap();
+ drop(m); // should panic here
+ }
+}
diff --git a/third_party/rust/nix/test/test_resource.rs b/third_party/rust/nix/test/test_resource.rs
new file mode 100644
index 0000000000..2ab581ba29
--- /dev/null
+++ b/third_party/rust/nix/test/test_resource.rs
@@ -0,0 +1,34 @@
+#[cfg(not(any(
+ target_os = "redox",
+ target_os = "fuchsia",
+ target_os = "illumos",
+ target_os = "haiku"
+)))]
+use nix::sys::resource::{getrlimit, setrlimit, Resource};
+
+/// Tests the RLIMIT_NOFILE functionality of getrlimit(), where the resource RLIMIT_NOFILE refers
+/// to the maximum file descriptor number that can be opened by the process (aka the maximum number
+/// of file descriptors that the process can open, since Linux 4.5).
+///
+/// We first fetch the existing file descriptor maximum values using getrlimit(), then edit the
+/// soft limit to make sure it has a new and distinct value to the hard limit. We then setrlimit()
+/// to put the new soft limit in effect, and then getrlimit() once more to ensure the limits have
+/// been updated.
+#[test]
+#[cfg(not(any(
+ target_os = "redox",
+ target_os = "fuchsia",
+ target_os = "illumos",
+ target_os = "haiku"
+)))]
+pub fn test_resource_limits_nofile() {
+ let (mut soft_limit, hard_limit) =
+ getrlimit(Resource::RLIMIT_NOFILE).unwrap();
+
+ soft_limit -= 1;
+ assert_ne!(soft_limit, hard_limit);
+ setrlimit(Resource::RLIMIT_NOFILE, soft_limit, hard_limit).unwrap();
+
+ let (new_soft_limit, _) = getrlimit(Resource::RLIMIT_NOFILE).unwrap();
+ assert_eq!(new_soft_limit, soft_limit);
+}
diff --git a/third_party/rust/nix/test/test_sched.rs b/third_party/rust/nix/test/test_sched.rs
new file mode 100644
index 0000000000..c52616b8bb
--- /dev/null
+++ b/third_party/rust/nix/test/test_sched.rs
@@ -0,0 +1,39 @@
+use nix::sched::{sched_getaffinity, sched_getcpu, sched_setaffinity, CpuSet};
+use nix::unistd::Pid;
+
+#[test]
+fn test_sched_affinity() {
+ // If pid is zero, then the mask of the calling process is returned.
+ let initial_affinity = sched_getaffinity(Pid::from_raw(0)).unwrap();
+ let mut at_least_one_cpu = false;
+ let mut last_valid_cpu = 0;
+ for field in 0..CpuSet::count() {
+ if initial_affinity.is_set(field).unwrap() {
+ at_least_one_cpu = true;
+ last_valid_cpu = field;
+ }
+ }
+ assert!(at_least_one_cpu);
+
+ // Now restrict the running CPU
+ let mut new_affinity = CpuSet::new();
+ new_affinity.set(last_valid_cpu).unwrap();
+ sched_setaffinity(Pid::from_raw(0), &new_affinity).unwrap();
+
+ // And now re-check the affinity which should be only the one we set.
+ let updated_affinity = sched_getaffinity(Pid::from_raw(0)).unwrap();
+ for field in 0..CpuSet::count() {
+ // Should be set only for the CPU we set previously
+ assert_eq!(
+ updated_affinity.is_set(field).unwrap(),
+ field == last_valid_cpu
+ )
+ }
+
+ // Now check that we're also currently running on the CPU in question.
+ let cur_cpu = sched_getcpu().unwrap();
+ assert_eq!(cur_cpu, last_valid_cpu);
+
+ // Finally, reset the initial CPU set
+ sched_setaffinity(Pid::from_raw(0), &initial_affinity).unwrap();
+}
diff --git a/third_party/rust/nix/test/test_sendfile.rs b/third_party/rust/nix/test/test_sendfile.rs
new file mode 100644
index 0000000000..f73a3b56c3
--- /dev/null
+++ b/third_party/rust/nix/test/test_sendfile.rs
@@ -0,0 +1,208 @@
+use std::io::prelude::*;
+use std::os::unix::prelude::*;
+
+use libc::off_t;
+use nix::sys::sendfile::*;
+use tempfile::tempfile;
+
+cfg_if! {
+ if #[cfg(any(target_os = "android", target_os = "linux"))] {
+ use nix::unistd::{close, pipe, read};
+ } else if #[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "ios", target_os = "macos"))] {
+ use std::net::Shutdown;
+ use std::os::unix::net::UnixStream;
+ }
+}
+
+#[cfg(any(target_os = "android", target_os = "linux"))]
+#[test]
+fn test_sendfile_linux() {
+ const CONTENTS: &[u8] = b"abcdef123456";
+ let mut tmp = tempfile().unwrap();
+ tmp.write_all(CONTENTS).unwrap();
+
+ let (rd, wr) = pipe().unwrap();
+ let mut offset: off_t = 5;
+ let res = sendfile(wr, tmp.as_raw_fd(), Some(&mut offset), 2).unwrap();
+
+ assert_eq!(2, res);
+
+ let mut buf = [0u8; 1024];
+ assert_eq!(2, read(rd, &mut buf).unwrap());
+ assert_eq!(b"f1", &buf[0..2]);
+ assert_eq!(7, offset);
+
+ close(rd).unwrap();
+ close(wr).unwrap();
+}
+
+#[cfg(target_os = "linux")]
+#[test]
+fn test_sendfile64_linux() {
+ const CONTENTS: &[u8] = b"abcdef123456";
+ let mut tmp = tempfile().unwrap();
+ tmp.write_all(CONTENTS).unwrap();
+
+ let (rd, wr) = pipe().unwrap();
+ let mut offset: libc::off64_t = 5;
+ let res = sendfile64(wr, tmp.as_raw_fd(), Some(&mut offset), 2).unwrap();
+
+ assert_eq!(2, res);
+
+ let mut buf = [0u8; 1024];
+ assert_eq!(2, read(rd, &mut buf).unwrap());
+ assert_eq!(b"f1", &buf[0..2]);
+ assert_eq!(7, offset);
+
+ close(rd).unwrap();
+ close(wr).unwrap();
+}
+
+#[cfg(target_os = "freebsd")]
+#[test]
+fn test_sendfile_freebsd() {
+ // Declare the content
+ let header_strings =
+ vec!["HTTP/1.1 200 OK\n", "Content-Type: text/plain\n", "\n"];
+ let body = "Xabcdef123456";
+ let body_offset = 1;
+ let trailer_strings = vec!["\n", "Served by Make Believe\n"];
+
+ // Write the body to a file
+ let mut tmp = tempfile().unwrap();
+ tmp.write_all(body.as_bytes()).unwrap();
+
+ // Prepare headers and trailers for sendfile
+ let headers: Vec<&[u8]> =
+ header_strings.iter().map(|s| s.as_bytes()).collect();
+ let trailers: Vec<&[u8]> =
+ trailer_strings.iter().map(|s| s.as_bytes()).collect();
+
+ // Prepare socket pair
+ let (mut rd, wr) = UnixStream::pair().unwrap();
+
+ // Call the test method
+ let (res, bytes_written) = sendfile(
+ tmp.as_raw_fd(),
+ wr.as_raw_fd(),
+ body_offset as off_t,
+ None,
+ Some(headers.as_slice()),
+ Some(trailers.as_slice()),
+ SfFlags::empty(),
+ 0,
+ );
+ assert!(res.is_ok());
+ wr.shutdown(Shutdown::Both).unwrap();
+
+ // Prepare the expected result
+ let expected_string = header_strings.concat()
+ + &body[body_offset..]
+ + &trailer_strings.concat();
+
+ // Verify the message that was sent
+ assert_eq!(bytes_written as usize, expected_string.as_bytes().len());
+
+ let mut read_string = String::new();
+ let bytes_read = rd.read_to_string(&mut read_string).unwrap();
+ assert_eq!(bytes_written as usize, bytes_read);
+ assert_eq!(expected_string, read_string);
+}
+
+#[cfg(target_os = "dragonfly")]
+#[test]
+fn test_sendfile_dragonfly() {
+ // Declare the content
+ let header_strings =
+ vec!["HTTP/1.1 200 OK\n", "Content-Type: text/plain\n", "\n"];
+ let body = "Xabcdef123456";
+ let body_offset = 1;
+ let trailer_strings = vec!["\n", "Served by Make Believe\n"];
+
+ // Write the body to a file
+ let mut tmp = tempfile().unwrap();
+ tmp.write_all(body.as_bytes()).unwrap();
+
+ // Prepare headers and trailers for sendfile
+ let headers: Vec<&[u8]> =
+ header_strings.iter().map(|s| s.as_bytes()).collect();
+ let trailers: Vec<&[u8]> =
+ trailer_strings.iter().map(|s| s.as_bytes()).collect();
+
+ // Prepare socket pair
+ let (mut rd, wr) = UnixStream::pair().unwrap();
+
+ // Call the test method
+ let (res, bytes_written) = sendfile(
+ tmp.as_raw_fd(),
+ wr.as_raw_fd(),
+ body_offset as off_t,
+ None,
+ Some(headers.as_slice()),
+ Some(trailers.as_slice()),
+ );
+ assert!(res.is_ok());
+ wr.shutdown(Shutdown::Both).unwrap();
+
+ // Prepare the expected result
+ let expected_string = header_strings.concat()
+ + &body[body_offset..]
+ + &trailer_strings.concat();
+
+ // Verify the message that was sent
+ assert_eq!(bytes_written as usize, expected_string.as_bytes().len());
+
+ let mut read_string = String::new();
+ let bytes_read = rd.read_to_string(&mut read_string).unwrap();
+ assert_eq!(bytes_written as usize, bytes_read);
+ assert_eq!(expected_string, read_string);
+}
+
+#[cfg(any(target_os = "ios", target_os = "macos"))]
+#[test]
+fn test_sendfile_darwin() {
+ // Declare the content
+ let header_strings =
+ vec!["HTTP/1.1 200 OK\n", "Content-Type: text/plain\n", "\n"];
+ let body = "Xabcdef123456";
+ let body_offset = 1;
+ let trailer_strings = vec!["\n", "Served by Make Believe\n"];
+
+ // Write the body to a file
+ let mut tmp = tempfile().unwrap();
+ tmp.write_all(body.as_bytes()).unwrap();
+
+ // Prepare headers and trailers for sendfile
+ let headers: Vec<&[u8]> =
+ header_strings.iter().map(|s| s.as_bytes()).collect();
+ let trailers: Vec<&[u8]> =
+ trailer_strings.iter().map(|s| s.as_bytes()).collect();
+
+ // Prepare socket pair
+ let (mut rd, wr) = UnixStream::pair().unwrap();
+
+ // Call the test method
+ let (res, bytes_written) = sendfile(
+ tmp.as_raw_fd(),
+ wr.as_raw_fd(),
+ body_offset as off_t,
+ None,
+ Some(headers.as_slice()),
+ Some(trailers.as_slice()),
+ );
+ assert!(res.is_ok());
+ wr.shutdown(Shutdown::Both).unwrap();
+
+ // Prepare the expected result
+ let expected_string = header_strings.concat()
+ + &body[body_offset..]
+ + &trailer_strings.concat();
+
+ // Verify the message that was sent
+ assert_eq!(bytes_written as usize, expected_string.as_bytes().len());
+
+ let mut read_string = String::new();
+ let bytes_read = rd.read_to_string(&mut read_string).unwrap();
+ assert_eq!(bytes_written as usize, bytes_read);
+ assert_eq!(expected_string, read_string);
+}
diff --git a/third_party/rust/nix/test/test_stat.rs b/third_party/rust/nix/test/test_stat.rs
new file mode 100644
index 0000000000..55f15c0771
--- /dev/null
+++ b/third_party/rust/nix/test/test_stat.rs
@@ -0,0 +1,421 @@
+#[cfg(not(any(target_os = "redox", target_os = "haiku")))]
+use std::fs;
+use std::fs::File;
+#[cfg(not(target_os = "redox"))]
+use std::os::unix::fs::symlink;
+#[cfg(not(any(target_os = "redox", target_os = "haiku")))]
+use std::os::unix::fs::PermissionsExt;
+use std::os::unix::prelude::AsRawFd;
+#[cfg(not(target_os = "redox"))]
+use std::path::Path;
+#[cfg(not(any(target_os = "redox", target_os = "haiku")))]
+use std::time::{Duration, UNIX_EPOCH};
+
+use libc::mode_t;
+#[cfg(not(any(target_os = "netbsd", target_os = "redox")))]
+use libc::{S_IFLNK, S_IFMT};
+
+#[cfg(not(target_os = "redox"))]
+use nix::errno::Errno;
+#[cfg(not(target_os = "redox"))]
+use nix::fcntl;
+#[cfg(any(
+ target_os = "linux",
+ target_os = "ios",
+ target_os = "macos",
+ target_os = "freebsd",
+ target_os = "netbsd"
+))]
+use nix::sys::stat::lutimes;
+#[cfg(not(any(target_os = "redox", target_os = "haiku")))]
+use nix::sys::stat::utimensat;
+#[cfg(not(target_os = "redox"))]
+use nix::sys::stat::FchmodatFlags;
+use nix::sys::stat::Mode;
+#[cfg(not(any(target_os = "redox", target_os = "haiku")))]
+use nix::sys::stat::UtimensatFlags;
+#[cfg(not(target_os = "redox"))]
+use nix::sys::stat::{self};
+use nix::sys::stat::{fchmod, stat};
+#[cfg(not(target_os = "redox"))]
+use nix::sys::stat::{fchmodat, mkdirat};
+#[cfg(not(any(target_os = "redox", target_os = "haiku")))]
+use nix::sys::stat::{futimens, utimes};
+
+#[cfg(not(any(target_os = "netbsd", target_os = "redox")))]
+use nix::sys::stat::FileStat;
+
+#[cfg(not(any(target_os = "redox", target_os = "haiku")))]
+use nix::sys::time::{TimeSpec, TimeVal, TimeValLike};
+#[cfg(not(target_os = "redox"))]
+use nix::unistd::chdir;
+
+#[cfg(not(any(target_os = "netbsd", target_os = "redox")))]
+use nix::Result;
+
+#[cfg(not(any(target_os = "netbsd", target_os = "redox")))]
+fn assert_stat_results(stat_result: Result<FileStat>) {
+ let stats = stat_result.expect("stat call failed");
+ assert!(stats.st_dev > 0); // must be positive integer, exact number machine dependent
+ assert!(stats.st_ino > 0); // inode is positive integer, exact number machine dependent
+ assert!(stats.st_mode > 0); // must be positive integer
+ assert_eq!(stats.st_nlink, 1); // there links created, must be 1
+ assert_eq!(stats.st_size, 0); // size is 0 because we did not write anything to the file
+ assert!(stats.st_blksize > 0); // must be positive integer, exact number machine dependent
+ assert!(stats.st_blocks <= 16); // Up to 16 blocks can be allocated for a blank file
+}
+
+#[cfg(not(any(target_os = "netbsd", target_os = "redox")))]
+// (Android's st_blocks is ulonglong which is always non-negative.)
+#[cfg_attr(target_os = "android", allow(unused_comparisons))]
+#[allow(clippy::absurd_extreme_comparisons)] // Not absurd on all OSes
+fn assert_lstat_results(stat_result: Result<FileStat>) {
+ let stats = stat_result.expect("stat call failed");
+ assert!(stats.st_dev > 0); // must be positive integer, exact number machine dependent
+ assert!(stats.st_ino > 0); // inode is positive integer, exact number machine dependent
+ assert!(stats.st_mode > 0); // must be positive integer
+
+ // st_mode is c_uint (u32 on Android) while S_IFMT is mode_t
+ // (u16 on Android), and that will be a compile error.
+ // On other platforms they are the same (either both are u16 or u32).
+ assert_eq!(
+ (stats.st_mode as usize) & (S_IFMT as usize),
+ S_IFLNK as usize
+ ); // should be a link
+ assert_eq!(stats.st_nlink, 1); // there links created, must be 1
+ assert!(stats.st_size > 0); // size is > 0 because it points to another file
+ assert!(stats.st_blksize > 0); // must be positive integer, exact number machine dependent
+
+ // st_blocks depends on whether the machine's file system uses fast
+ // or slow symlinks, so just make sure it's not negative
+ assert!(stats.st_blocks >= 0);
+}
+
+#[test]
+#[cfg(not(any(target_os = "netbsd", target_os = "redox")))]
+fn test_stat_and_fstat() {
+ use nix::sys::stat::fstat;
+
+ let tempdir = tempfile::tempdir().unwrap();
+ let filename = tempdir.path().join("foo.txt");
+ let file = File::create(&filename).unwrap();
+
+ let stat_result = stat(&filename);
+ assert_stat_results(stat_result);
+
+ let fstat_result = fstat(file.as_raw_fd());
+ assert_stat_results(fstat_result);
+}
+
+#[test]
+#[cfg(not(any(target_os = "netbsd", target_os = "redox")))]
+fn test_fstatat() {
+ let tempdir = tempfile::tempdir().unwrap();
+ let filename = tempdir.path().join("foo.txt");
+ File::create(&filename).unwrap();
+ let dirfd =
+ fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty());
+
+ let result =
+ stat::fstatat(dirfd.unwrap(), &filename, fcntl::AtFlags::empty());
+ assert_stat_results(result);
+}
+
+#[test]
+#[cfg(not(any(target_os = "netbsd", target_os = "redox")))]
+fn test_stat_fstat_lstat() {
+ use nix::sys::stat::{fstat, lstat};
+
+ let tempdir = tempfile::tempdir().unwrap();
+ let filename = tempdir.path().join("bar.txt");
+ let linkname = tempdir.path().join("barlink");
+
+ File::create(&filename).unwrap();
+ symlink("bar.txt", &linkname).unwrap();
+ let link = File::open(&linkname).unwrap();
+
+ // should be the same result as calling stat,
+ // since it's a regular file
+ let stat_result = stat(&filename);
+ assert_stat_results(stat_result);
+
+ let lstat_result = lstat(&linkname);
+ assert_lstat_results(lstat_result);
+
+ let fstat_result = fstat(link.as_raw_fd());
+ assert_stat_results(fstat_result);
+}
+
+#[test]
+fn test_fchmod() {
+ let tempdir = tempfile::tempdir().unwrap();
+ let filename = tempdir.path().join("foo.txt");
+ let file = File::create(&filename).unwrap();
+
+ let mut mode1 = Mode::empty();
+ mode1.insert(Mode::S_IRUSR);
+ mode1.insert(Mode::S_IWUSR);
+ fchmod(file.as_raw_fd(), mode1).unwrap();
+
+ let file_stat1 = stat(&filename).unwrap();
+ assert_eq!(file_stat1.st_mode as mode_t & 0o7777, mode1.bits());
+
+ let mut mode2 = Mode::empty();
+ mode2.insert(Mode::S_IROTH);
+ fchmod(file.as_raw_fd(), mode2).unwrap();
+
+ let file_stat2 = stat(&filename).unwrap();
+ assert_eq!(file_stat2.st_mode as mode_t & 0o7777, mode2.bits());
+}
+
+#[test]
+#[cfg(not(target_os = "redox"))]
+fn test_fchmodat() {
+ let _dr = crate::DirRestore::new();
+ let tempdir = tempfile::tempdir().unwrap();
+ let filename = "foo.txt";
+ let fullpath = tempdir.path().join(filename);
+ File::create(&fullpath).unwrap();
+
+ let dirfd =
+ fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty())
+ .unwrap();
+
+ let mut mode1 = Mode::empty();
+ mode1.insert(Mode::S_IRUSR);
+ mode1.insert(Mode::S_IWUSR);
+ fchmodat(Some(dirfd), filename, mode1, FchmodatFlags::FollowSymlink)
+ .unwrap();
+
+ let file_stat1 = stat(&fullpath).unwrap();
+ assert_eq!(file_stat1.st_mode as mode_t & 0o7777, mode1.bits());
+
+ chdir(tempdir.path()).unwrap();
+
+ let mut mode2 = Mode::empty();
+ mode2.insert(Mode::S_IROTH);
+ fchmodat(None, filename, mode2, FchmodatFlags::FollowSymlink).unwrap();
+
+ let file_stat2 = stat(&fullpath).unwrap();
+ assert_eq!(file_stat2.st_mode as mode_t & 0o7777, mode2.bits());
+}
+
+/// Asserts that the atime and mtime in a file's metadata match expected values.
+///
+/// The atime and mtime are expressed with a resolution of seconds because some file systems
+/// (like macOS's HFS+) do not have higher granularity.
+#[cfg(not(any(target_os = "redox", target_os = "haiku")))]
+fn assert_times_eq(
+ exp_atime_sec: u64,
+ exp_mtime_sec: u64,
+ attr: &fs::Metadata,
+) {
+ assert_eq!(
+ Duration::new(exp_atime_sec, 0),
+ attr.accessed().unwrap().duration_since(UNIX_EPOCH).unwrap()
+ );
+ assert_eq!(
+ Duration::new(exp_mtime_sec, 0),
+ attr.modified().unwrap().duration_since(UNIX_EPOCH).unwrap()
+ );
+}
+
+#[test]
+#[cfg(not(any(target_os = "redox", target_os = "haiku")))]
+fn test_utimes() {
+ let tempdir = tempfile::tempdir().unwrap();
+ let fullpath = tempdir.path().join("file");
+ drop(File::create(&fullpath).unwrap());
+
+ utimes(&fullpath, &TimeVal::seconds(9990), &TimeVal::seconds(5550))
+ .unwrap();
+ assert_times_eq(9990, 5550, &fs::metadata(&fullpath).unwrap());
+}
+
+#[test]
+#[cfg(any(
+ target_os = "linux",
+ target_os = "ios",
+ target_os = "macos",
+ target_os = "freebsd",
+ target_os = "netbsd"
+))]
+fn test_lutimes() {
+ let tempdir = tempfile::tempdir().unwrap();
+ let target = tempdir.path().join("target");
+ let fullpath = tempdir.path().join("symlink");
+ drop(File::create(&target).unwrap());
+ symlink(&target, &fullpath).unwrap();
+
+ let exp_target_metadata = fs::symlink_metadata(&target).unwrap();
+ lutimes(&fullpath, &TimeVal::seconds(4560), &TimeVal::seconds(1230))
+ .unwrap();
+ assert_times_eq(4560, 1230, &fs::symlink_metadata(&fullpath).unwrap());
+
+ let target_metadata = fs::symlink_metadata(&target).unwrap();
+ assert_eq!(
+ exp_target_metadata.accessed().unwrap(),
+ target_metadata.accessed().unwrap(),
+ "atime of symlink target was unexpectedly modified"
+ );
+ assert_eq!(
+ exp_target_metadata.modified().unwrap(),
+ target_metadata.modified().unwrap(),
+ "mtime of symlink target was unexpectedly modified"
+ );
+}
+
+#[test]
+#[cfg(not(any(target_os = "redox", target_os = "haiku")))]
+fn test_futimens() {
+ let tempdir = tempfile::tempdir().unwrap();
+ let fullpath = tempdir.path().join("file");
+ drop(File::create(&fullpath).unwrap());
+
+ let fd = fcntl::open(&fullpath, fcntl::OFlag::empty(), stat::Mode::empty())
+ .unwrap();
+
+ futimens(fd, &TimeSpec::seconds(10), &TimeSpec::seconds(20)).unwrap();
+ assert_times_eq(10, 20, &fs::metadata(&fullpath).unwrap());
+}
+
+#[test]
+#[cfg(not(any(target_os = "redox", target_os = "haiku")))]
+fn test_utimensat() {
+ let _dr = crate::DirRestore::new();
+ let tempdir = tempfile::tempdir().unwrap();
+ let filename = "foo.txt";
+ let fullpath = tempdir.path().join(filename);
+ drop(File::create(&fullpath).unwrap());
+
+ let dirfd =
+ fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty())
+ .unwrap();
+
+ utimensat(
+ Some(dirfd),
+ filename,
+ &TimeSpec::seconds(12345),
+ &TimeSpec::seconds(678),
+ UtimensatFlags::FollowSymlink,
+ )
+ .unwrap();
+ assert_times_eq(12345, 678, &fs::metadata(&fullpath).unwrap());
+
+ chdir(tempdir.path()).unwrap();
+
+ utimensat(
+ None,
+ filename,
+ &TimeSpec::seconds(500),
+ &TimeSpec::seconds(800),
+ UtimensatFlags::FollowSymlink,
+ )
+ .unwrap();
+ assert_times_eq(500, 800, &fs::metadata(&fullpath).unwrap());
+}
+
+#[test]
+#[cfg(not(target_os = "redox"))]
+fn test_mkdirat_success_path() {
+ let tempdir = tempfile::tempdir().unwrap();
+ let filename = "example_subdir";
+ let dirfd =
+ fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty())
+ .unwrap();
+ mkdirat(dirfd, filename, Mode::S_IRWXU).expect("mkdirat failed");
+ assert!(Path::exists(&tempdir.path().join(filename)));
+}
+
+#[test]
+#[cfg(not(any(target_os = "redox", target_os = "haiku")))]
+fn test_mkdirat_success_mode() {
+ let expected_bits =
+ stat::SFlag::S_IFDIR.bits() | stat::Mode::S_IRWXU.bits();
+ let tempdir = tempfile::tempdir().unwrap();
+ let filename = "example_subdir";
+ let dirfd =
+ fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty())
+ .unwrap();
+ mkdirat(dirfd, filename, Mode::S_IRWXU).expect("mkdirat failed");
+ let permissions = fs::metadata(tempdir.path().join(filename))
+ .unwrap()
+ .permissions();
+ let mode = permissions.mode();
+ assert_eq!(mode as mode_t, expected_bits)
+}
+
+#[test]
+#[cfg(not(target_os = "redox"))]
+fn test_mkdirat_fail() {
+ let tempdir = tempfile::tempdir().unwrap();
+ let not_dir_filename = "example_not_dir";
+ let filename = "example_subdir_dir";
+ 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, Errno::ENOTDIR);
+}
+
+#[test]
+#[cfg(not(any(
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "ios",
+ target_os = "macos",
+ target_os = "haiku",
+ target_os = "redox"
+)))]
+fn test_mknod() {
+ use stat::{lstat, mknod, SFlag};
+
+ let file_name = "test_file";
+ let tempdir = tempfile::tempdir().unwrap();
+ let target = tempdir.path().join(file_name);
+ mknod(&target, SFlag::S_IFREG, Mode::S_IRWXU, 0).unwrap();
+ let mode = lstat(&target).unwrap().st_mode as mode_t;
+ assert_eq!(mode & libc::S_IFREG, libc::S_IFREG);
+ assert_eq!(mode & libc::S_IRWXU, libc::S_IRWXU);
+}
+
+#[test]
+#[cfg(not(any(
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "illumos",
+ target_os = "ios",
+ target_os = "macos",
+ target_os = "haiku",
+ target_os = "redox"
+)))]
+fn test_mknodat() {
+ use fcntl::{AtFlags, OFlag};
+ use nix::dir::Dir;
+ use stat::{fstatat, mknodat, SFlag};
+
+ let file_name = "test_file";
+ let tempdir = tempfile::tempdir().unwrap();
+ let target_dir =
+ Dir::open(tempdir.path(), OFlag::O_DIRECTORY, Mode::S_IRWXU).unwrap();
+ mknodat(
+ target_dir.as_raw_fd(),
+ file_name,
+ SFlag::S_IFREG,
+ Mode::S_IRWXU,
+ 0,
+ )
+ .unwrap();
+ let mode = fstatat(
+ target_dir.as_raw_fd(),
+ file_name,
+ AtFlags::AT_SYMLINK_NOFOLLOW,
+ )
+ .unwrap()
+ .st_mode as mode_t;
+ assert_eq!(mode & libc::S_IFREG, libc::S_IFREG);
+ assert_eq!(mode & libc::S_IRWXU, libc::S_IRWXU);
+}
diff --git a/third_party/rust/nix/test/test_time.rs b/third_party/rust/nix/test/test_time.rs
new file mode 100644
index 0000000000..5f76e61a2d
--- /dev/null
+++ b/third_party/rust/nix/test/test_time.rs
@@ -0,0 +1,59 @@
+#[cfg(any(
+ target_os = "freebsd",
+ target_os = "dragonfly",
+ target_os = "linux",
+ target_os = "android",
+ target_os = "emscripten",
+))]
+use nix::time::clock_getcpuclockid;
+use nix::time::{clock_gettime, ClockId};
+
+#[cfg(not(target_os = "redox"))]
+#[test]
+pub fn test_clock_getres() {
+ nix::time::clock_getres(ClockId::CLOCK_REALTIME).expect("assertion failed");
+}
+
+#[test]
+pub fn test_clock_gettime() {
+ clock_gettime(ClockId::CLOCK_REALTIME).expect("assertion failed");
+}
+
+#[cfg(any(
+ target_os = "freebsd",
+ target_os = "dragonfly",
+ target_os = "linux",
+ target_os = "android",
+ target_os = "emscripten",
+))]
+#[test]
+pub fn test_clock_getcpuclockid() {
+ let clock_id = clock_getcpuclockid(nix::unistd::Pid::this()).unwrap();
+ clock_gettime(clock_id).unwrap();
+}
+
+#[cfg(not(target_os = "redox"))]
+#[test]
+pub fn test_clock_id_res() {
+ ClockId::CLOCK_REALTIME.res().unwrap();
+}
+
+#[test]
+pub fn test_clock_id_now() {
+ ClockId::CLOCK_REALTIME.now().unwrap();
+}
+
+#[cfg(any(
+ target_os = "freebsd",
+ target_os = "dragonfly",
+ target_os = "linux",
+ target_os = "android",
+ target_os = "emscripten",
+))]
+#[test]
+pub fn test_clock_id_pid_cpu_clock_id() {
+ ClockId::pid_cpu_clock_id(nix::unistd::Pid::this())
+ .map(ClockId::now)
+ .unwrap()
+ .unwrap();
+}
diff --git a/third_party/rust/nix/test/test_timer.rs b/third_party/rust/nix/test/test_timer.rs
new file mode 100644
index 0000000000..ffd146867b
--- /dev/null
+++ b/third_party/rust/nix/test/test_timer.rs
@@ -0,0 +1,102 @@
+use nix::sys::signal::{
+ sigaction, SaFlags, SigAction, SigEvent, SigHandler, SigSet, SigevNotify,
+ Signal,
+};
+use nix::sys::timer::{Expiration, Timer, TimerSetTimeFlags};
+use nix::time::ClockId;
+use std::convert::TryFrom;
+use std::sync::atomic::{AtomicBool, Ordering};
+use std::thread;
+use std::time::{Duration, Instant};
+
+const SIG: Signal = Signal::SIGALRM;
+static ALARM_CALLED: AtomicBool = AtomicBool::new(false);
+
+pub extern "C" fn handle_sigalarm(raw_signal: libc::c_int) {
+ let signal = Signal::try_from(raw_signal).unwrap();
+ if signal == SIG {
+ ALARM_CALLED.store(true, Ordering::Release);
+ }
+}
+
+#[test]
+fn alarm_fires() {
+ // Avoid interfering with other signal using tests by taking a mutex shared
+ // among other tests in this crate.
+ let _m = crate::SIGNAL_MTX.lock();
+ const TIMER_PERIOD: Duration = Duration::from_millis(100);
+
+ //
+ // Setup
+ //
+
+ // Create a handler for the test signal, `SIG`. The handler is responsible
+ // for flipping `ALARM_CALLED`.
+ let handler = SigHandler::Handler(handle_sigalarm);
+ let signal_action =
+ SigAction::new(handler, SaFlags::SA_RESTART, SigSet::empty());
+ let old_handler = unsafe {
+ sigaction(SIG, &signal_action)
+ .expect("unable to set signal handler for alarm")
+ };
+
+ // Create the timer. We use the monotonic clock here, though any would do
+ // really. The timer is set to fire every 250 milliseconds with no delay for
+ // the initial firing.
+ let clockid = ClockId::CLOCK_MONOTONIC;
+ let sigevent = SigEvent::new(SigevNotify::SigevSignal {
+ signal: SIG,
+ si_value: 0,
+ });
+ let mut timer =
+ Timer::new(clockid, sigevent).expect("failed to create timer");
+ let expiration = Expiration::Interval(TIMER_PERIOD.into());
+ let flags = TimerSetTimeFlags::empty();
+ timer.set(expiration, flags).expect("could not set timer");
+
+ //
+ // Test
+ //
+
+ // Determine that there's still an expiration tracked by the
+ // timer. Depending on when this runs either an `Expiration::Interval` or
+ // `Expiration::IntervalDelayed` will be present. That is, if the timer has
+ // not fired yet we'll get our original `expiration`, else the one that
+ // represents a delay to the next expiration. We're only interested in the
+ // timer still being extant.
+ match timer.get() {
+ Ok(Some(exp)) => assert!(matches!(
+ exp,
+ Expiration::Interval(..) | Expiration::IntervalDelayed(..)
+ )),
+ _ => panic!("timer lost its expiration"),
+ }
+
+ // Wait for 2 firings of the alarm before checking that it has fired and
+ // been handled at least the once. If we wait for 3 seconds and the handler
+ // is never called something has gone sideways and the test fails.
+ let starttime = Instant::now();
+ loop {
+ thread::sleep(2 * TIMER_PERIOD);
+ if ALARM_CALLED.load(Ordering::Acquire) {
+ break;
+ }
+ if starttime.elapsed() > Duration::from_secs(3) {
+ panic!("Timeout waiting for SIGALRM");
+ }
+ }
+
+ // Cleanup:
+ // 1) deregister the OS's timer.
+ // 2) Wait for a full timer period, since POSIX does not require that
+ // disabling the timer will clear pending signals, and on NetBSD at least
+ // it does not.
+ // 2) Replace the old signal handler now that we've completed the test. If
+ // the test fails this process panics, so the fact we might not get here
+ // is okay.
+ drop(timer);
+ thread::sleep(TIMER_PERIOD);
+ unsafe {
+ sigaction(SIG, &old_handler).expect("unable to reset signal handler");
+ }
+}
diff --git a/third_party/rust/nix/test/test_unistd.rs b/third_party/rust/nix/test/test_unistd.rs
new file mode 100644
index 0000000000..9e20f977ec
--- /dev/null
+++ b/third_party/rust/nix/test/test_unistd.rs
@@ -0,0 +1,1407 @@
+use libc::{_exit, mode_t, off_t};
+use nix::errno::Errno;
+#[cfg(not(any(target_os = "redox", target_os = "haiku")))]
+use nix::fcntl::readlink;
+use nix::fcntl::OFlag;
+#[cfg(not(target_os = "redox"))]
+use nix::fcntl::{self, open};
+#[cfg(not(any(
+ target_os = "redox",
+ target_os = "fuchsia",
+ target_os = "haiku"
+)))]
+use nix::pty::{grantpt, posix_openpt, ptsname, unlockpt};
+#[cfg(not(target_os = "redox"))]
+use nix::sys::signal::{
+ sigaction, SaFlags, SigAction, SigHandler, SigSet, Signal,
+};
+use nix::sys::stat::{self, Mode, SFlag};
+use nix::sys::wait::*;
+use nix::unistd::ForkResult::*;
+use nix::unistd::*;
+use std::env;
+#[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(any(
+ target_os = "fuchsia",
+ target_os = "redox",
+ target_os = "haiku"
+)))]
+use std::path::Path;
+use tempfile::{tempdir, tempfile};
+
+use crate::*;
+
+#[test]
+#[cfg(not(any(target_os = "netbsd")))]
+fn test_fork_and_waitpid() {
+ let _m = crate::FORK_MTX.lock();
+
+ // Safe: Child only calls `_exit`, which is signal-safe
+ match unsafe { fork() }.expect("Error: Fork Failed") {
+ Child => unsafe { _exit(0) },
+ Parent { child } => {
+ // assert that child was created and pid > 0
+ let child_raw: ::libc::pid_t = child.into();
+ assert!(child_raw > 0);
+ let wait_status = waitpid(child, None);
+ match wait_status {
+ // assert that waitpid returned correct status and the pid is the one of the child
+ Ok(WaitStatus::Exited(pid_t, _)) => assert_eq!(pid_t, child),
+
+ // panic, must never happen
+ s @ Ok(_) => {
+ panic!("Child exited {:?}, should never happen", s)
+ }
+
+ // panic, waitpid should never fail
+ Err(s) => panic!("Error: waitpid returned Err({:?}", s),
+ }
+ }
+ }
+}
+
+#[test]
+fn test_wait() {
+ // Grab FORK_MTX so wait doesn't reap a different test's child process
+ let _m = crate::FORK_MTX.lock();
+
+ // Safe: Child only calls `_exit`, which is signal-safe
+ match unsafe { fork() }.expect("Error: Fork Failed") {
+ Child => unsafe { _exit(0) },
+ Parent { child } => {
+ let wait_status = wait();
+
+ // just assert that (any) one child returns with WaitStatus::Exited
+ assert_eq!(wait_status, Ok(WaitStatus::Exited(child, 0)));
+ }
+ }
+}
+
+#[test]
+fn test_mkstemp() {
+ let mut path = env::temp_dir();
+ path.push("nix_tempfile.XXXXXX");
+
+ let result = mkstemp(&path);
+ match result {
+ Ok((fd, path)) => {
+ close(fd).unwrap();
+ unlink(path.as_path()).unwrap();
+ }
+ Err(e) => panic!("mkstemp failed: {}", e),
+ }
+}
+
+#[test]
+fn test_mkstemp_directory() {
+ // mkstemp should fail if a directory is given
+ mkstemp(&env::temp_dir()).expect_err("assertion failed");
+}
+
+#[test]
+#[cfg(not(target_os = "redox"))]
+fn test_mkfifo() {
+ let tempdir = tempdir().unwrap();
+ let mkfifo_fifo = tempdir.path().join("mkfifo_fifo");
+
+ mkfifo(&mkfifo_fifo, Mode::S_IRUSR).unwrap();
+
+ let stats = stat::stat(&mkfifo_fifo).unwrap();
+ let typ = stat::SFlag::from_bits_truncate(stats.st_mode as mode_t);
+ assert_eq!(typ, SFlag::S_IFIFO);
+}
+
+#[test]
+#[cfg(not(target_os = "redox"))]
+fn test_mkfifo_directory() {
+ // mkfifo should fail if a directory is given
+ mkfifo(&env::temp_dir(), Mode::S_IRUSR).expect_err("assertion failed");
+}
+
+#[test]
+#[cfg(not(any(
+ target_os = "macos",
+ target_os = "ios",
+ target_os = "android",
+ target_os = "redox",
+ target_os = "haiku"
+)))]
+fn test_mkfifoat_none() {
+ let _m = crate::CWD_LOCK.read();
+
+ let tempdir = tempdir().unwrap();
+ let mkfifoat_fifo = tempdir.path().join("mkfifoat_fifo");
+
+ mkfifoat(None, &mkfifoat_fifo, Mode::S_IRUSR).unwrap();
+
+ let stats = stat::stat(&mkfifoat_fifo).unwrap();
+ let typ = stat::SFlag::from_bits_truncate(stats.st_mode);
+ assert_eq!(typ, SFlag::S_IFIFO);
+}
+
+#[test]
+#[cfg(not(any(
+ target_os = "macos",
+ target_os = "ios",
+ target_os = "android",
+ target_os = "redox",
+ target_os = "haiku"
+)))]
+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";
+
+ mkfifoat(Some(dirfd), mkfifoat_name, Mode::S_IRUSR).unwrap();
+
+ let stats =
+ stat::fstatat(dirfd, mkfifoat_name, fcntl::AtFlags::empty()).unwrap();
+ let typ = stat::SFlag::from_bits_truncate(stats.st_mode);
+ assert_eq!(typ, SFlag::S_IFIFO);
+}
+
+#[test]
+#[cfg(not(any(
+ target_os = "macos",
+ target_os = "ios",
+ target_os = "android",
+ target_os = "redox",
+ target_os = "haiku"
+)))]
+fn test_mkfifoat_directory_none() {
+ let _m = crate::CWD_LOCK.read();
+
+ // mkfifoat should fail if a directory is given
+ mkfifoat(None, &env::temp_dir(), Mode::S_IRUSR)
+ .expect_err("assertion failed");
+}
+
+#[test]
+#[cfg(not(any(
+ target_os = "macos",
+ target_os = "ios",
+ target_os = "android",
+ target_os = "redox",
+ target_os = "haiku"
+)))]
+fn test_mkfifoat_directory() {
+ // mkfifoat should fail if a directory is given
+ let tempdir = tempdir().unwrap();
+ let dirfd = open(tempdir.path(), OFlag::empty(), Mode::empty()).unwrap();
+ let mkfifoat_dir = "mkfifoat_dir";
+ stat::mkdirat(dirfd, mkfifoat_dir, Mode::S_IRUSR).unwrap();
+
+ mkfifoat(Some(dirfd), mkfifoat_dir, Mode::S_IRUSR)
+ .expect_err("assertion failed");
+}
+
+#[test]
+fn test_getpid() {
+ let pid: ::libc::pid_t = getpid().into();
+ let ppid: ::libc::pid_t = getppid().into();
+ assert!(pid > 0);
+ assert!(ppid > 0);
+}
+
+#[test]
+#[cfg(not(target_os = "redox"))]
+fn test_getsid() {
+ let none_sid: ::libc::pid_t = getsid(None).unwrap().into();
+ let pid_sid: ::libc::pid_t = getsid(Some(getpid())).unwrap().into();
+ assert!(none_sid > 0);
+ assert_eq!(none_sid, pid_sid);
+}
+
+#[cfg(any(target_os = "linux", target_os = "android"))]
+mod linux_android {
+ use nix::unistd::gettid;
+
+ #[test]
+ fn test_gettid() {
+ let tid: ::libc::pid_t = gettid().into();
+ assert!(tid > 0);
+ }
+}
+
+#[test]
+// `getgroups()` and `setgroups()` do not behave as expected on Apple platforms
+#[cfg(not(any(
+ target_os = "ios",
+ target_os = "macos",
+ target_os = "redox",
+ target_os = "fuchsia",
+ target_os = "haiku"
+)))]
+fn test_setgroups() {
+ // Skip this test when not run as root as `setgroups()` requires root.
+ skip_if_not_root!("test_setgroups");
+
+ let _m = crate::GROUPS_MTX.lock();
+
+ // Save the existing groups
+ let old_groups = getgroups().unwrap();
+
+ // Set some new made up groups
+ let groups = [Gid::from_raw(123), Gid::from_raw(456)];
+ setgroups(&groups).unwrap();
+
+ let new_groups = getgroups().unwrap();
+ assert_eq!(new_groups, groups);
+
+ // Revert back to the old groups
+ setgroups(&old_groups).unwrap();
+}
+
+#[test]
+// `getgroups()` and `setgroups()` do not behave as expected on Apple platforms
+#[cfg(not(any(
+ target_os = "ios",
+ target_os = "macos",
+ target_os = "redox",
+ target_os = "fuchsia",
+ target_os = "haiku",
+ target_os = "illumos"
+)))]
+fn test_initgroups() {
+ // Skip this test when not run as root as `initgroups()` and `setgroups()`
+ // require root.
+ skip_if_not_root!("test_initgroups");
+
+ let _m = crate::GROUPS_MTX.lock();
+
+ // Save the existing groups
+ let old_groups = getgroups().unwrap();
+
+ // It doesn't matter if the root user is not called "root" or if a user
+ // called "root" doesn't exist. We are just checking that the extra,
+ // made-up group, `123`, is set.
+ // FIXME: Test the other half of initgroups' functionality: whether the
+ // groups that the user belongs to are also set.
+ let user = CString::new("root").unwrap();
+ let group = Gid::from_raw(123);
+ let group_list = getgrouplist(&user, group).unwrap();
+ assert!(group_list.contains(&group));
+
+ initgroups(&user, group).unwrap();
+
+ let new_groups = getgroups().unwrap();
+ assert_eq!(new_groups, group_list);
+
+ // Revert back to the old groups
+ setgroups(&old_groups).unwrap();
+}
+
+#[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)*) => (
+
+ #[cfg(test)]
+ mod $test_name {
+ use std::ffi::CStr;
+ use super::*;
+
+ const EMPTY: &'static [u8] = b"\0";
+ const DASH_C: &'static [u8] = b"-c\0";
+ const BIGARG: &'static [u8] = b"echo nix!!! && echo foo=$foo && echo baz=$baz\0";
+ const FOO: &'static [u8] = b"foo=bar\0";
+ const BAZ: &'static [u8] = b"baz=quux\0";
+
+ fn syscall_cstr_ref() -> Result<std::convert::Infallible, nix::Error> {
+ $syscall(
+ $exe,
+ $(CString::new($pathname).unwrap().as_c_str(), )*
+ &[CStr::from_bytes_with_nul(EMPTY).unwrap(),
+ CStr::from_bytes_with_nul(DASH_C).unwrap(),
+ CStr::from_bytes_with_nul(BIGARG).unwrap()],
+ &[CStr::from_bytes_with_nul(FOO).unwrap(),
+ CStr::from_bytes_with_nul(BAZ).unwrap()]
+ $(, $flags)*)
+ }
+
+ fn syscall_cstring() -> Result<std::convert::Infallible, nix::Error> {
+ $syscall(
+ $exe,
+ $(CString::new($pathname).unwrap().as_c_str(), )*
+ &[CString::from(CStr::from_bytes_with_nul(EMPTY).unwrap()),
+ CString::from(CStr::from_bytes_with_nul(DASH_C).unwrap()),
+ CString::from(CStr::from_bytes_with_nul(BIGARG).unwrap())],
+ &[CString::from(CStr::from_bytes_with_nul(FOO).unwrap()),
+ CString::from(CStr::from_bytes_with_nul(BAZ).unwrap())]
+ $(, $flags)*)
+ }
+
+ fn common_test(syscall: fn() -> Result<std::convert::Infallible, nix::Error>) {
+ if "execveat" == stringify!($syscall) {
+ // Though undocumented, Docker's default seccomp profile seems to
+ // block this syscall. https://github.com/nix-rust/nix/issues/1122
+ skip_if_seccomp!($test_name);
+ }
+
+ let m = crate::FORK_MTX.lock();
+ // The `exec`d process will write to `writer`, and we'll read that
+ // data from `reader`.
+ let (reader, writer) = pipe().unwrap();
+
+ // Safe: Child calls `exit`, `dup`, `close` and the provided `exec*` family function.
+ // NOTE: Technically, this makes the macro unsafe to use because you could pass anything.
+ // The tests make sure not to do that, though.
+ match unsafe{fork()}.unwrap() {
+ Child => {
+ // Make `writer` be the stdout of the new process.
+ dup2(writer, 1).unwrap();
+ let r = syscall();
+ let _ = std::io::stderr()
+ .write_all(format!("{:?}", r).as_bytes());
+ // Should only get here in event of error
+ unsafe{ _exit(1) };
+ },
+ Parent { child } => {
+ // Wait for the child to exit.
+ let ws = waitpid(child, None);
+ drop(m);
+ assert_eq!(ws, Ok(WaitStatus::Exited(child, 0)));
+ // Read 1024 bytes.
+ let mut buf = [0u8; 1024];
+ read(reader, &mut buf).unwrap();
+ // It should contain the things we printed using `/bin/sh`.
+ let string = String::from_utf8_lossy(&buf);
+ assert!(string.contains("nix!!!"));
+ assert!(string.contains("foo=bar"));
+ assert!(string.contains("baz=quux"));
+ }
+ }
+ }
+
+ // These tests frequently fail on musl, probably due to
+ // https://github.com/nix-rust/nix/issues/555
+ #[cfg_attr(target_env = "musl", ignore)]
+ #[test]
+ fn test_cstr_ref() {
+ common_test(syscall_cstr_ref);
+ }
+
+ // These tests frequently fail on musl, probably due to
+ // https://github.com/nix-rust/nix/issues/555
+ #[cfg_attr(target_env = "musl", ignore)]
+ #[test]
+ fn test_cstring() {
+ common_test(syscall_cstring);
+ }
+ }
+
+ )
+);
+
+cfg_if! {
+ if #[cfg(target_os = "android")] {
+ execve_test_factory!(test_execve, execve, CString::new("/system/bin/sh").unwrap().as_c_str());
+ execve_test_factory!(test_fexecve, fexecve, File::open("/system/bin/sh").unwrap().into_raw_fd());
+ } else if #[cfg(any(target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "linux"))] {
+ // These tests frequently fail on musl, probably due to
+ // https://github.com/nix-rust/nix/issues/555
+ execve_test_factory!(test_execve, execve, CString::new("/bin/sh").unwrap().as_c_str());
+ execve_test_factory!(test_fexecve, fexecve, File::open("/bin/sh").unwrap().into_raw_fd());
+ } else if #[cfg(any(target_os = "illumos",
+ target_os = "ios",
+ target_os = "macos",
+ target_os = "netbsd",
+ target_os = "openbsd",
+ target_os = "solaris"))] {
+ execve_test_factory!(test_execve, execve, CString::new("/bin/sh").unwrap().as_c_str());
+ // No fexecve() on ios, macos, NetBSD, OpenBSD.
+ }
+}
+
+#[cfg(any(target_os = "haiku", target_os = "linux", target_os = "openbsd"))]
+execve_test_factory!(test_execvpe, execvpe, &CString::new("sh").unwrap());
+
+cfg_if! {
+ if #[cfg(target_os = "android")] {
+ use nix::fcntl::AtFlags;
+ execve_test_factory!(test_execveat_empty, execveat,
+ File::open("/system/bin/sh").unwrap().into_raw_fd(),
+ "", AtFlags::AT_EMPTY_PATH);
+ execve_test_factory!(test_execveat_relative, execveat,
+ File::open("/system/bin/").unwrap().into_raw_fd(),
+ "./sh", AtFlags::empty());
+ execve_test_factory!(test_execveat_absolute, execveat,
+ File::open("/").unwrap().into_raw_fd(),
+ "/system/bin/sh", AtFlags::empty());
+ } else if #[cfg(all(target_os = "linux", any(target_arch ="x86_64", target_arch ="x86")))] {
+ use nix::fcntl::AtFlags;
+ execve_test_factory!(test_execveat_empty, execveat, File::open("/bin/sh").unwrap().into_raw_fd(),
+ "", AtFlags::AT_EMPTY_PATH);
+ execve_test_factory!(test_execveat_relative, execveat, File::open("/bin/").unwrap().into_raw_fd(),
+ "./sh", AtFlags::empty());
+ execve_test_factory!(test_execveat_absolute, execveat, File::open("/").unwrap().into_raw_fd(),
+ "/bin/sh", AtFlags::empty());
+ }
+}
+
+#[test]
+#[cfg(not(target_os = "fuchsia"))]
+fn test_fchdir() {
+ // fchdir changes the process's cwd
+ let _dr = crate::DirRestore::new();
+
+ let tmpdir = tempdir().unwrap();
+ let tmpdir_path = tmpdir.path().canonicalize().unwrap();
+ let tmpdir_fd = File::open(&tmpdir_path).unwrap().into_raw_fd();
+
+ fchdir(tmpdir_fd).expect("assertion failed");
+ assert_eq!(getcwd().unwrap(), tmpdir_path);
+
+ close(tmpdir_fd).expect("assertion failed");
+}
+
+#[test]
+fn test_getcwd() {
+ // chdir changes the process's cwd
+ let _dr = crate::DirRestore::new();
+
+ let tmpdir = tempdir().unwrap();
+ let tmpdir_path = tmpdir.path().canonicalize().unwrap();
+ chdir(&tmpdir_path).expect("assertion failed");
+ assert_eq!(getcwd().unwrap(), tmpdir_path);
+
+ // make path 500 chars longer so that buffer doubling in getcwd
+ // kicks in. Note: One path cannot be longer than 255 bytes
+ // (NAME_MAX) whole path cannot be longer than PATH_MAX (usually
+ // 4096 on linux, 1024 on macos)
+ let mut inner_tmp_dir = tmpdir_path;
+ for _ in 0..5 {
+ let newdir = "a".repeat(100);
+ inner_tmp_dir.push(newdir);
+ mkdir(inner_tmp_dir.as_path(), Mode::S_IRWXU)
+ .expect("assertion failed");
+ }
+ chdir(inner_tmp_dir.as_path()).expect("assertion failed");
+ assert_eq!(getcwd().unwrap(), inner_tmp_dir.as_path());
+}
+
+#[test]
+fn test_chown() {
+ // Testing for anything other than our own UID/GID is hard.
+ let uid = Some(getuid());
+ let gid = Some(getgid());
+
+ let tempdir = tempdir().unwrap();
+ let path = tempdir.path().join("file");
+ {
+ File::create(&path).unwrap();
+ }
+
+ chown(&path, uid, gid).unwrap();
+ chown(&path, uid, None).unwrap();
+ chown(&path, None, gid).unwrap();
+
+ fs::remove_file(&path).unwrap();
+ chown(&path, uid, gid).unwrap_err();
+}
+
+#[test]
+fn test_fchown() {
+ // Testing for anything other than our own UID/GID is hard.
+ let uid = Some(getuid());
+ let gid = Some(getgid());
+
+ let path = tempfile().unwrap();
+ let fd = path.as_raw_fd();
+
+ fchown(fd, uid, gid).unwrap();
+ fchown(fd, uid, None).unwrap();
+ fchown(fd, None, gid).unwrap();
+ fchown(999999999, uid, gid).unwrap_err();
+}
+
+#[test]
+#[cfg(not(target_os = "redox"))]
+fn test_fchownat() {
+ let _dr = crate::DirRestore::new();
+ // Testing for anything other than our own UID/GID is hard.
+ let uid = Some(getuid());
+ let gid = Some(getgid());
+
+ let tempdir = tempdir().unwrap();
+ let path = tempdir.path().join("file");
+ {
+ File::create(&path).unwrap();
+ }
+
+ let dirfd = open(tempdir.path(), OFlag::empty(), Mode::empty()).unwrap();
+
+ fchownat(Some(dirfd), "file", uid, gid, FchownatFlags::FollowSymlink)
+ .unwrap();
+
+ chdir(tempdir.path()).unwrap();
+ fchownat(None, "file", uid, gid, FchownatFlags::FollowSymlink).unwrap();
+
+ fs::remove_file(&path).unwrap();
+ fchownat(None, "file", uid, gid, FchownatFlags::FollowSymlink).unwrap_err();
+}
+
+#[test]
+fn test_lseek() {
+ const CONTENTS: &[u8] = b"abcdef123456";
+ let mut tmp = tempfile().unwrap();
+ tmp.write_all(CONTENTS).unwrap();
+ let tmpfd = tmp.into_raw_fd();
+
+ let offset: off_t = 5;
+ lseek(tmpfd, offset, Whence::SeekSet).unwrap();
+
+ let mut buf = [0u8; 7];
+ crate::read_exact(tmpfd, &mut buf);
+ assert_eq!(b"f123456", &buf);
+
+ close(tmpfd).unwrap();
+}
+
+#[cfg(any(target_os = "linux", target_os = "android"))]
+#[test]
+fn test_lseek64() {
+ const CONTENTS: &[u8] = b"abcdef123456";
+ let mut tmp = tempfile().unwrap();
+ tmp.write_all(CONTENTS).unwrap();
+ let tmpfd = tmp.into_raw_fd();
+
+ lseek64(tmpfd, 5, Whence::SeekSet).unwrap();
+
+ let mut buf = [0u8; 7];
+ crate::read_exact(tmpfd, &mut buf);
+ assert_eq!(b"f123456", &buf);
+
+ close(tmpfd).unwrap();
+}
+
+cfg_if! {
+ if #[cfg(any(target_os = "android", target_os = "linux"))] {
+ macro_rules! require_acct{
+ () => {
+ require_capability!("test_acct", CAP_SYS_PACCT);
+ }
+ }
+ } else if #[cfg(target_os = "freebsd")] {
+ macro_rules! require_acct{
+ () => {
+ skip_if_not_root!("test_acct");
+ skip_if_jailed!("test_acct");
+ }
+ }
+ } else if #[cfg(not(any(target_os = "redox", target_os = "fuchsia", target_os = "haiku")))] {
+ macro_rules! require_acct{
+ () => {
+ skip_if_not_root!("test_acct");
+ }
+ }
+ }
+}
+
+#[test]
+#[cfg(not(any(
+ target_os = "redox",
+ target_os = "fuchsia",
+ target_os = "haiku"
+)))]
+fn test_acct() {
+ use std::process::Command;
+ use std::{thread, time};
+ use tempfile::NamedTempFile;
+
+ let _m = crate::FORK_MTX.lock();
+ require_acct!();
+
+ let file = NamedTempFile::new().unwrap();
+ let path = file.path().to_str().unwrap();
+
+ acct::enable(path).unwrap();
+
+ loop {
+ Command::new("echo").arg("Hello world").output().unwrap();
+ let len = fs::metadata(path).unwrap().len();
+ if len > 0 {
+ break;
+ }
+ thread::sleep(time::Duration::from_millis(10));
+ }
+ acct::disable().unwrap();
+}
+
+#[test]
+fn test_fpathconf_limited() {
+ let f = tempfile().unwrap();
+ // AFAIK, PATH_MAX is limited on all platforms, so it makes a good test
+ let path_max = fpathconf(f.as_raw_fd(), PathconfVar::PATH_MAX);
+ assert!(
+ path_max
+ .expect("fpathconf failed")
+ .expect("PATH_MAX is unlimited")
+ > 0
+ );
+}
+
+#[test]
+fn test_pathconf_limited() {
+ // AFAIK, PATH_MAX is limited on all platforms, so it makes a good test
+ let path_max = pathconf("/", PathconfVar::PATH_MAX);
+ assert!(
+ path_max
+ .expect("pathconf failed")
+ .expect("PATH_MAX is unlimited")
+ > 0
+ );
+}
+
+#[test]
+fn test_sysconf_limited() {
+ // AFAIK, OPEN_MAX is limited on all platforms, so it makes a good test
+ let open_max = sysconf(SysconfVar::OPEN_MAX);
+ assert!(
+ open_max
+ .expect("sysconf failed")
+ .expect("OPEN_MAX is unlimited")
+ > 0
+ );
+}
+
+#[cfg(target_os = "freebsd")]
+#[test]
+fn test_sysconf_unsupported() {
+ // I know of no sysconf variables that are unsupported everywhere, but
+ // _XOPEN_CRYPT is unsupported on FreeBSD 11.0, which is one of the platforms
+ // we test.
+ let open_max = sysconf(SysconfVar::_XOPEN_CRYPT);
+ assert!(open_max.expect("sysconf failed").is_none())
+}
+
+#[cfg(any(
+ target_os = "android",
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "linux",
+ target_os = "openbsd"
+))]
+#[test]
+fn test_getresuid() {
+ let resuids = getresuid().unwrap();
+ assert_ne!(resuids.real.as_raw(), libc::uid_t::MAX);
+ assert_ne!(resuids.effective.as_raw(), libc::uid_t::MAX);
+ assert_ne!(resuids.saved.as_raw(), libc::uid_t::MAX);
+}
+
+#[cfg(any(
+ target_os = "android",
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "linux",
+ target_os = "openbsd"
+))]
+#[test]
+fn test_getresgid() {
+ let resgids = getresgid().unwrap();
+ assert_ne!(resgids.real.as_raw(), libc::gid_t::MAX);
+ assert_ne!(resgids.effective.as_raw(), libc::gid_t::MAX);
+ assert_ne!(resgids.saved.as_raw(), libc::gid_t::MAX);
+}
+
+// Test that we can create a pair of pipes. No need to verify that they pass
+// data; that's the domain of the OS, not nix.
+#[test]
+fn test_pipe() {
+ let (fd0, fd1) = pipe().unwrap();
+ let m0 = stat::SFlag::from_bits_truncate(
+ stat::fstat(fd0).unwrap().st_mode as mode_t,
+ );
+ // S_IFIFO means it's a pipe
+ assert_eq!(m0, SFlag::S_IFIFO);
+ let m1 = stat::SFlag::from_bits_truncate(
+ stat::fstat(fd1).unwrap().st_mode as mode_t,
+ );
+ assert_eq!(m1, SFlag::S_IFIFO);
+}
+
+// pipe2(2) is the same as pipe(2), except it allows setting some flags. Check
+// that we can set a flag.
+#[cfg(any(
+ target_os = "android",
+ target_os = "dragonfly",
+ target_os = "emscripten",
+ target_os = "freebsd",
+ target_os = "illumos",
+ target_os = "linux",
+ target_os = "netbsd",
+ target_os = "openbsd",
+ target_os = "redox",
+ 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));
+ let f1 = FdFlag::from_bits_truncate(fcntl(fd1, FcntlArg::F_GETFD).unwrap());
+ assert!(f1.contains(FdFlag::FD_CLOEXEC));
+}
+
+#[test]
+#[cfg(not(any(target_os = "redox", target_os = "fuchsia")))]
+fn test_truncate() {
+ let tempdir = tempdir().unwrap();
+ let path = tempdir.path().join("file");
+
+ {
+ let mut tmp = File::create(&path).unwrap();
+ const CONTENTS: &[u8] = b"12345678";
+ tmp.write_all(CONTENTS).unwrap();
+ }
+
+ truncate(&path, 4).unwrap();
+
+ let metadata = fs::metadata(&path).unwrap();
+ assert_eq!(4, metadata.len());
+}
+
+#[test]
+fn test_ftruncate() {
+ let tempdir = tempdir().unwrap();
+ let path = tempdir.path().join("file");
+
+ let tmpfd = {
+ let mut tmp = File::create(&path).unwrap();
+ const CONTENTS: &[u8] = b"12345678";
+ tmp.write_all(CONTENTS).unwrap();
+ tmp.into_raw_fd()
+ };
+
+ ftruncate(tmpfd, 2).unwrap();
+ close(tmpfd).unwrap();
+
+ let metadata = fs::metadata(&path).unwrap();
+ assert_eq!(2, metadata.len());
+}
+
+// Used in `test_alarm`.
+#[cfg(not(target_os = "redox"))]
+static mut ALARM_CALLED: bool = false;
+
+// Used in `test_alarm`.
+#[cfg(not(target_os = "redox"))]
+pub extern "C" fn alarm_signal_handler(raw_signal: libc::c_int) {
+ assert_eq!(
+ raw_signal,
+ libc::SIGALRM,
+ "unexpected signal: {}",
+ raw_signal
+ );
+ unsafe { ALARM_CALLED = true };
+}
+
+#[test]
+#[cfg(not(target_os = "redox"))]
+fn test_alarm() {
+ use std::{
+ thread,
+ time::{Duration, Instant},
+ };
+
+ // Maybe other tests that fork interfere with this one?
+ let _m = crate::SIGNAL_MTX.lock();
+
+ let handler = SigHandler::Handler(alarm_signal_handler);
+ let signal_action =
+ SigAction::new(handler, SaFlags::SA_RESTART, SigSet::empty());
+ let old_handler = unsafe {
+ sigaction(Signal::SIGALRM, &signal_action)
+ .expect("unable to set signal handler for alarm")
+ };
+
+ // Set an alarm.
+ assert_eq!(alarm::set(60), None);
+
+ // Overwriting an alarm should return the old alarm.
+ assert_eq!(alarm::set(1), Some(60));
+
+ // We should be woken up after 1 second by the alarm, so we'll sleep for 3
+ // seconds to be sure.
+ let starttime = Instant::now();
+ loop {
+ thread::sleep(Duration::from_millis(100));
+ if unsafe { ALARM_CALLED } {
+ break;
+ }
+ if starttime.elapsed() > Duration::from_secs(3) {
+ panic!("Timeout waiting for SIGALRM");
+ }
+ }
+
+ // Reset the signal.
+ unsafe {
+ sigaction(Signal::SIGALRM, &old_handler)
+ .expect("unable to set signal handler for alarm");
+ }
+}
+
+#[test]
+#[cfg(not(target_os = "redox"))]
+fn test_canceling_alarm() {
+ let _m = crate::SIGNAL_MTX.lock();
+
+ assert_eq!(alarm::cancel(), None);
+
+ assert_eq!(alarm::set(60), None);
+ assert_eq!(alarm::cancel(), Some(60));
+}
+
+#[test]
+#[cfg(not(any(target_os = "redox", target_os = "haiku")))]
+fn test_symlinkat() {
+ let _m = crate::CWD_LOCK.read();
+
+ let tempdir = tempdir().unwrap();
+
+ let target = tempdir.path().join("a");
+ let linkpath = tempdir.path().join("b");
+ symlinkat(&target, None, &linkpath).unwrap();
+ assert_eq!(
+ readlink(&linkpath).unwrap().to_str().unwrap(),
+ target.to_str().unwrap()
+ );
+
+ let dirfd = open(tempdir.path(), OFlag::empty(), Mode::empty()).unwrap();
+ let target = "c";
+ let linkpath = "d";
+ symlinkat(target, Some(dirfd), linkpath).unwrap();
+ assert_eq!(
+ readlink(&tempdir.path().join(linkpath))
+ .unwrap()
+ .to_str()
+ .unwrap(),
+ target
+ );
+}
+
+#[test]
+#[cfg(not(any(target_os = "redox", target_os = "haiku")))]
+fn test_linkat_file() {
+ let tempdir = tempdir().unwrap();
+ let oldfilename = "foo.txt";
+ let oldfilepath = tempdir.path().join(oldfilename);
+
+ let newfilename = "bar.txt";
+ let newfilepath = tempdir.path().join(newfilename);
+
+ // Create file
+ File::create(oldfilepath).unwrap();
+
+ // Get file descriptor for base directory
+ let dirfd =
+ fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty())
+ .unwrap();
+
+ // Attempt hard link file at relative path
+ linkat(
+ Some(dirfd),
+ oldfilename,
+ Some(dirfd),
+ newfilename,
+ LinkatFlags::SymlinkFollow,
+ )
+ .unwrap();
+ assert!(newfilepath.exists());
+}
+
+#[test]
+#[cfg(not(any(target_os = "redox", target_os = "haiku")))]
+fn test_linkat_olddirfd_none() {
+ let _dr = crate::DirRestore::new();
+
+ let tempdir_oldfile = tempdir().unwrap();
+ let oldfilename = "foo.txt";
+ let oldfilepath = tempdir_oldfile.path().join(oldfilename);
+
+ let tempdir_newfile = tempdir().unwrap();
+ let newfilename = "bar.txt";
+ let newfilepath = tempdir_newfile.path().join(newfilename);
+
+ // Create file
+ File::create(oldfilepath).unwrap();
+
+ // Get file descriptor for base directory of new file
+ let dirfd = fcntl::open(
+ tempdir_newfile.path(),
+ fcntl::OFlag::empty(),
+ stat::Mode::empty(),
+ )
+ .unwrap();
+
+ // Attempt hard link file using curent working directory as relative path for old file path
+ chdir(tempdir_oldfile.path()).unwrap();
+ linkat(
+ None,
+ oldfilename,
+ Some(dirfd),
+ newfilename,
+ LinkatFlags::SymlinkFollow,
+ )
+ .unwrap();
+ assert!(newfilepath.exists());
+}
+
+#[test]
+#[cfg(not(any(target_os = "redox", target_os = "haiku")))]
+fn test_linkat_newdirfd_none() {
+ let _dr = crate::DirRestore::new();
+
+ let tempdir_oldfile = tempdir().unwrap();
+ let oldfilename = "foo.txt";
+ let oldfilepath = tempdir_oldfile.path().join(oldfilename);
+
+ let tempdir_newfile = tempdir().unwrap();
+ let newfilename = "bar.txt";
+ let newfilepath = tempdir_newfile.path().join(newfilename);
+
+ // Create file
+ File::create(oldfilepath).unwrap();
+
+ // Get file descriptor for base directory of old file
+ let dirfd = fcntl::open(
+ tempdir_oldfile.path(),
+ fcntl::OFlag::empty(),
+ stat::Mode::empty(),
+ )
+ .unwrap();
+
+ // Attempt hard link file using current working directory as relative path for new file path
+ chdir(tempdir_newfile.path()).unwrap();
+ linkat(
+ Some(dirfd),
+ oldfilename,
+ None,
+ newfilename,
+ LinkatFlags::SymlinkFollow,
+ )
+ .unwrap();
+ assert!(newfilepath.exists());
+}
+
+#[test]
+#[cfg(not(any(
+ target_os = "ios",
+ target_os = "macos",
+ target_os = "redox",
+ target_os = "haiku"
+)))]
+fn test_linkat_no_follow_symlink() {
+ let _m = crate::CWD_LOCK.read();
+
+ let tempdir = tempdir().unwrap();
+ let oldfilename = "foo.txt";
+ let oldfilepath = tempdir.path().join(oldfilename);
+
+ let symoldfilename = "symfoo.txt";
+ let symoldfilepath = tempdir.path().join(symoldfilename);
+
+ let newfilename = "nofollowsymbar.txt";
+ let newfilepath = tempdir.path().join(newfilename);
+
+ // Create file
+ File::create(&oldfilepath).unwrap();
+
+ // Create symlink to file
+ symlinkat(&oldfilepath, None, &symoldfilepath).unwrap();
+
+ // Get file descriptor for base directory
+ let dirfd =
+ fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty())
+ .unwrap();
+
+ // Attempt link symlink of file at relative path
+ linkat(
+ Some(dirfd),
+ symoldfilename,
+ Some(dirfd),
+ newfilename,
+ LinkatFlags::NoSymlinkFollow,
+ )
+ .unwrap();
+
+ // Assert newfile is actually a symlink to oldfile.
+ assert_eq!(
+ readlink(&newfilepath).unwrap().to_str().unwrap(),
+ oldfilepath.to_str().unwrap()
+ );
+}
+
+#[test]
+#[cfg(not(any(target_os = "redox", target_os = "haiku")))]
+fn test_linkat_follow_symlink() {
+ let _m = crate::CWD_LOCK.read();
+
+ let tempdir = tempdir().unwrap();
+ let oldfilename = "foo.txt";
+ let oldfilepath = tempdir.path().join(oldfilename);
+
+ let symoldfilename = "symfoo.txt";
+ let symoldfilepath = tempdir.path().join(symoldfilename);
+
+ let newfilename = "nofollowsymbar.txt";
+ let newfilepath = tempdir.path().join(newfilename);
+
+ // Create file
+ File::create(&oldfilepath).unwrap();
+
+ // Create symlink to file
+ symlinkat(&oldfilepath, None, &symoldfilepath).unwrap();
+
+ // Get file descriptor for base directory
+ let dirfd =
+ fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty())
+ .unwrap();
+
+ // Attempt link target of symlink of file at relative path
+ linkat(
+ Some(dirfd),
+ symoldfilename,
+ Some(dirfd),
+ newfilename,
+ LinkatFlags::SymlinkFollow,
+ )
+ .unwrap();
+
+ let newfilestat = stat::stat(&newfilepath).unwrap();
+
+ // Check the file type of the new link
+ assert_eq!(
+ (stat::SFlag::from_bits_truncate(newfilestat.st_mode as mode_t)
+ & SFlag::S_IFMT),
+ SFlag::S_IFREG
+ );
+
+ // Check the number of hard links to the original file
+ assert_eq!(newfilestat.st_nlink, 2);
+}
+
+#[test]
+#[cfg(not(target_os = "redox"))]
+fn test_unlinkat_dir_noremovedir() {
+ let tempdir = tempdir().unwrap();
+ let dirname = "foo_dir";
+ let dirpath = tempdir.path().join(dirname);
+
+ // Create dir
+ DirBuilder::new().recursive(true).create(dirpath).unwrap();
+
+ // Get file descriptor for base directory
+ let dirfd =
+ fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty())
+ .unwrap();
+
+ // Attempt unlink dir at relative path without proper flag
+ let err_result =
+ unlinkat(Some(dirfd), dirname, UnlinkatFlags::NoRemoveDir).unwrap_err();
+ assert!(err_result == Errno::EISDIR || err_result == Errno::EPERM);
+}
+
+#[test]
+#[cfg(not(target_os = "redox"))]
+fn test_unlinkat_dir_removedir() {
+ let tempdir = tempdir().unwrap();
+ let dirname = "foo_dir";
+ let dirpath = tempdir.path().join(dirname);
+
+ // Create dir
+ DirBuilder::new().recursive(true).create(&dirpath).unwrap();
+
+ // Get file descriptor for base directory
+ let dirfd =
+ fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty())
+ .unwrap();
+
+ // Attempt unlink dir at relative path with proper flag
+ unlinkat(Some(dirfd), dirname, UnlinkatFlags::RemoveDir).unwrap();
+ assert!(!dirpath.exists());
+}
+
+#[test]
+#[cfg(not(target_os = "redox"))]
+fn test_unlinkat_file() {
+ let tempdir = tempdir().unwrap();
+ let filename = "foo.txt";
+ let filepath = tempdir.path().join(filename);
+
+ // Create file
+ File::create(&filepath).unwrap();
+
+ // Get file descriptor for base directory
+ let dirfd =
+ fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty())
+ .unwrap();
+
+ // Attempt unlink file at relative path
+ unlinkat(Some(dirfd), filename, UnlinkatFlags::NoRemoveDir).unwrap();
+ assert!(!filepath.exists());
+}
+
+#[test]
+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(),
+ Errno::ENOENT
+ );
+}
+
+#[test]
+fn test_access_file_exists() {
+ let tempdir = tempdir().unwrap();
+ let path = tempdir.path().join("does_exist.txt");
+ let _file = File::create(path.clone()).unwrap();
+ access(&path, AccessFlags::R_OK | AccessFlags::W_OK)
+ .expect("assertion failed");
+}
+
+//Clippy false positive https://github.com/rust-lang/rust-clippy/issues/9111
+#[allow(clippy::needless_borrow)]
+#[cfg(not(target_os = "redox"))]
+#[test]
+fn test_user_into_passwd() {
+ // get the UID of the "nobody" user
+ #[cfg(not(target_os = "haiku"))]
+ let test_username = "nobody";
+ // "nobody" unavailable on haiku
+ #[cfg(target_os = "haiku")]
+ let test_username = "user";
+
+ let nobody = User::from_name(test_username).unwrap().unwrap();
+ let pwd: libc::passwd = nobody.into();
+ let _: User = (&pwd).into();
+}
+
+/// Tests setting the filesystem UID with `setfsuid`.
+#[cfg(any(target_os = "linux", target_os = "android"))]
+#[test]
+fn test_setfsuid() {
+ use std::os::unix::fs::PermissionsExt;
+ use std::{fs, io, thread};
+ require_capability!("test_setfsuid", CAP_SETUID);
+
+ // get the UID of the "nobody" user
+ let nobody = User::from_name("nobody").unwrap().unwrap();
+
+ // create a temporary file with permissions '-rw-r-----'
+ let file = tempfile::NamedTempFile::new_in("/var/tmp").unwrap();
+ let temp_path = file.into_temp_path();
+ let temp_path_2 = temp_path.to_path_buf();
+ let mut permissions = fs::metadata(&temp_path).unwrap().permissions();
+ permissions.set_mode(0o640);
+
+ // spawn a new thread where to test setfsuid
+ thread::spawn(move || {
+ // set filesystem UID
+ let fuid = setfsuid(nobody.uid);
+ // trying to open the temporary file should fail with EACCES
+ let res = fs::File::open(&temp_path);
+ let err = res.expect_err("assertion failed");
+ assert_eq!(err.kind(), io::ErrorKind::PermissionDenied);
+
+ // assert fuid actually changes
+ let prev_fuid = setfsuid(Uid::from_raw(-1i32 as u32));
+ assert_ne!(prev_fuid, fuid);
+ })
+ .join()
+ .unwrap();
+
+ // open the temporary file with the current thread filesystem UID
+ fs::File::open(temp_path_2).unwrap();
+}
+
+#[test]
+#[cfg(not(any(
+ target_os = "redox",
+ target_os = "fuchsia",
+ target_os = "haiku"
+)))]
+fn test_ttyname() {
+ let fd = posix_openpt(OFlag::O_RDWR).expect("posix_openpt failed");
+ assert!(fd.as_raw_fd() > 0);
+
+ // on linux, we can just call ttyname on the pty master directly, but
+ // apparently osx requires that ttyname is called on a slave pty (can't
+ // find this documented anywhere, but it seems to empirically be the case)
+ grantpt(&fd).expect("grantpt failed");
+ unlockpt(&fd).expect("unlockpt failed");
+ let sname = unsafe { ptsname(&fd) }.expect("ptsname failed");
+ let fds = open(Path::new(&sname), OFlag::O_RDWR, stat::Mode::empty())
+ .expect("open failed");
+ assert!(fds > 0);
+
+ let name = ttyname(fds).expect("ttyname failed");
+ assert!(name.starts_with("/dev"));
+}
+
+#[test]
+#[cfg(not(any(target_os = "redox", target_os = "fuchsia")))]
+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(Errno::ENOTTY));
+}
+
+#[test]
+#[cfg(not(any(
+ target_os = "redox",
+ target_os = "fuchsia",
+ target_os = "haiku"
+)))]
+fn test_ttyname_invalid_fd() {
+ assert_eq!(ttyname(-1), Err(Errno::EBADF));
+}
+
+#[test]
+#[cfg(any(
+ target_os = "macos",
+ target_os = "ios",
+ target_os = "freebsd",
+ target_os = "openbsd",
+ target_os = "netbsd",
+ target_os = "dragonfly",
+))]
+fn test_getpeereid() {
+ use std::os::unix::net::UnixStream;
+ let (sock_a, sock_b) = UnixStream::pair().unwrap();
+
+ let (uid_a, gid_a) = getpeereid(sock_a.as_raw_fd()).unwrap();
+ let (uid_b, gid_b) = getpeereid(sock_b.as_raw_fd()).unwrap();
+
+ let uid = geteuid();
+ let gid = getegid();
+
+ assert_eq!(uid, uid_a);
+ assert_eq!(gid, gid_a);
+ assert_eq!(uid_a, uid_b);
+ assert_eq!(gid_a, gid_b);
+}
+
+#[test]
+#[cfg(any(
+ target_os = "macos",
+ target_os = "ios",
+ target_os = "freebsd",
+ target_os = "openbsd",
+ target_os = "netbsd",
+ target_os = "dragonfly",
+))]
+fn test_getpeereid_invalid_fd() {
+ // getpeereid is not POSIX, so error codes are inconsistent between different Unices.
+ getpeereid(-1).expect_err("assertion failed");
+}
+
+#[test]
+#[cfg(not(target_os = "redox"))]
+fn test_faccessat_none_not_existing() {
+ use nix::fcntl::AtFlags;
+ let tempdir = tempfile::tempdir().unwrap();
+ let dir = tempdir.path().join("does_not_exist.txt");
+ assert_eq!(
+ faccessat(None, &dir, AccessFlags::F_OK, AtFlags::empty())
+ .err()
+ .unwrap(),
+ Errno::ENOENT
+ );
+}
+
+#[test]
+#[cfg(not(target_os = "redox"))]
+fn test_faccessat_not_existing() {
+ use nix::fcntl::AtFlags;
+ let tempdir = tempfile::tempdir().unwrap();
+ let dirfd = open(tempdir.path(), OFlag::empty(), Mode::empty()).unwrap();
+ let not_exist_file = "does_not_exist.txt";
+ assert_eq!(
+ faccessat(
+ Some(dirfd),
+ not_exist_file,
+ AccessFlags::F_OK,
+ AtFlags::empty(),
+ )
+ .err()
+ .unwrap(),
+ Errno::ENOENT
+ );
+}
+
+#[test]
+#[cfg(not(target_os = "redox"))]
+fn test_faccessat_none_file_exists() {
+ use nix::fcntl::AtFlags;
+ let tempdir = tempfile::tempdir().unwrap();
+ let path = tempdir.path().join("does_exist.txt");
+ let _file = File::create(path.clone()).unwrap();
+ assert!(faccessat(
+ None,
+ &path,
+ AccessFlags::R_OK | AccessFlags::W_OK,
+ AtFlags::empty(),
+ )
+ .is_ok());
+}
+
+#[test]
+#[cfg(not(target_os = "redox"))]
+fn test_faccessat_file_exists() {
+ use nix::fcntl::AtFlags;
+ let tempdir = tempfile::tempdir().unwrap();
+ let dirfd = open(tempdir.path(), OFlag::empty(), Mode::empty()).unwrap();
+ let exist_file = "does_exist.txt";
+ let path = tempdir.path().join(exist_file);
+ let _file = File::create(path.clone()).unwrap();
+ assert!(faccessat(
+ Some(dirfd),
+ &path,
+ AccessFlags::R_OK | AccessFlags::W_OK,
+ AtFlags::empty(),
+ )
+ .is_ok());
+}
+
+#[test]
+#[cfg(any(
+ all(target_os = "linux", not(target_env = "uclibc")),
+ target_os = "freebsd",
+ target_os = "dragonfly"
+))]
+fn test_eaccess_not_existing() {
+ let tempdir = tempdir().unwrap();
+ let dir = tempdir.path().join("does_not_exist.txt");
+ assert_eq!(
+ eaccess(&dir, AccessFlags::F_OK).err().unwrap(),
+ Errno::ENOENT
+ );
+}
+
+#[test]
+#[cfg(any(
+ all(target_os = "linux", not(target_env = "uclibc")),
+ target_os = "freebsd",
+ target_os = "dragonfly"
+))]
+fn test_eaccess_file_exists() {
+ let tempdir = tempdir().unwrap();
+ let path = tempdir.path().join("does_exist.txt");
+ let _file = File::create(path.clone()).unwrap();
+ eaccess(&path, AccessFlags::R_OK | AccessFlags::W_OK)
+ .expect("assertion failed");
+}