diff options
Diffstat (limited to 'third_party/rust/nix/test')
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"); +} |