#[cfg(not(target_os = "redox"))] use nix::fcntl::{self, open, readlink}; use nix::fcntl::{fcntl, FcntlArg, FdFlag, OFlag}; use nix::unistd::*; use nix::unistd::ForkResult::*; #[cfg(not(target_os = "redox"))] use nix::sys::signal::{SaFlags, SigAction, SigHandler, SigSet, Signal, sigaction}; use nix::sys::wait::*; use nix::sys::stat::{self, Mode, SFlag}; #[cfg(not(any(target_os = "redox", target_os = "fuchsia")))] use nix::pty::{posix_openpt, grantpt, unlockpt, ptsname}; use nix::errno::Errno; #[cfg(not(target_os = "redox"))] use nix::Error; use std::{env, iter}; #[cfg(not(target_os = "redox"))] use std::ffi::CString; #[cfg(not(target_os = "redox"))] use std::fs::DirBuilder; use std::fs::{self, File}; use std::io::Write; use std::os::unix::prelude::*; #[cfg(not(target_os = "redox"))] use std::path::Path; use tempfile::{tempdir, tempfile}; use libc::{_exit, mode_t, off_t}; use crate::*; #[test] #[cfg(not(any(target_os = "netbsd")))] fn test_fork_and_waitpid() { let _m = crate::FORK_MTX.lock().expect("Mutex got poisoned by another test"); // 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().expect("Mutex got poisoned by another test"); // 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 assert!(mkstemp(&env::temp_dir()).is_err()); } #[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!(typ == SFlag::S_IFIFO); } #[test] #[cfg(not(target_os = "redox"))] fn test_mkfifo_directory() { // mkfifo should fail if a directory is given assert!(mkfifo(&env::temp_dir(), Mode::S_IRUSR).is_err()); } #[test] #[cfg(not(any( target_os = "macos", target_os = "ios", target_os = "android", target_os = "redox")))] fn test_mkfifoat_none() { let _m = crate::CWD_LOCK.read().expect("Mutex got poisoned by another test"); 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")))] fn test_mkfifoat() { 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")))] fn test_mkfifoat_directory_none() { let _m = crate::CWD_LOCK.read().expect("Mutex got poisoned by another test"); // mkfifoat should fail if a directory is given assert!(!mkfifoat(None, &env::temp_dir(), Mode::S_IRUSR).is_ok()); } #[test] #[cfg(not(any( target_os = "macos", target_os = "ios", target_os = "android", target_os = "redox")))] 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(); assert!(!mkfifoat(Some(dirfd), mkfifoat_dir, Mode::S_IRUSR).is_ok()); } #[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")))] 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().expect("Mutex got poisoned by another test"); // 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 = "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().expect("Mutex got poisoned by another test"); // 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(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 { $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 { $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) { 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().expect("Mutex got poisoned by another test"); // 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 = "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 = "dragonfly", 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 DragonFly, ios, macos, NetBSD, OpenBSD. // // Note for NetBSD and OpenBSD: although rust-lang/libc includes it // (under unix/bsd/netbsdlike/) fexecve is not currently implemented on // NetBSD nor on 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(); assert!(fchdir(tmpdir_fd).is_ok()); assert_eq!(getcwd().unwrap(), tmpdir_path); assert!(close(tmpdir_fd).is_ok()); } #[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(); assert!(chdir(&tmpdir_path).is_ok()); 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.to_path_buf(); for _ in 0..5 { let newdir = iter::repeat("a").take(100).collect::(); inner_tmp_dir.push(newdir); assert!(mkdir(inner_tmp_dir.as_path(), Mode::S_IRWXU).is_ok()); } assert!(chdir(inner_tmp_dir.as_path()).is_ok()); 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!(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")))] { macro_rules! require_acct{ () => { skip_if_not_root!("test_acct"); } } } } #[test] #[cfg(not(any(target_os = "redox", target_os = "fuchsia")))] fn test_acct() { use tempfile::NamedTempFile; use std::process::Command; use std::{thread, time}; let _m = crate::FORK_MTX.lock().expect("Mutex got poisoned by another test"); 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"); 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()) } // 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() { 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 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::{ time::{Duration, Instant,}, thread }; // Maybe other tests that fork interfere with this one? let _m = crate::SIGNAL_MTX.lock().expect("Mutex got poisoned by another test"); 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 2 // 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().expect("Mutex got poisoned by another test"); assert_eq!(alarm::cancel(), None); assert_eq!(alarm::set(60), None); assert_eq!(alarm::cancel(), Some(60)); } #[test] #[cfg(not(target_os = "redox"))] fn test_symlinkat() { let _m = crate::CWD_LOCK.read().expect("Mutex got poisoned by another test"); 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(target_os = "redox"))] 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(target_os = "redox"))] 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(target_os = "redox"))] 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")))] fn test_linkat_no_follow_symlink() { let _m = crate::CWD_LOCK.read().expect("Mutex got poisoned by another test"); 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(target_os = "redox"))] fn test_linkat_follow_symlink() { let _m = crate::CWD_LOCK.read().expect("Mutex got poisoned by another test"); 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 == Error::Sys(Errno::EISDIR) || err_result == Error::Sys(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().as_errno().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(); assert!(access(&path, AccessFlags::R_OK | AccessFlags::W_OK).is_ok()); } /// 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!(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(); dbg!(&temp_path); let temp_path_2 = (&temp_path).to_path_buf(); let mut permissions = fs::metadata(&temp_path).unwrap().permissions(); permissions.set_mode(640); // 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); assert!(res.is_err()); assert_eq!(res.err().unwrap().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")))] 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(Error::Sys(Errno::ENOTTY))); } #[test] #[cfg(not(any(target_os = "redox", target_os = "fuchsia")))] fn test_ttyname_invalid_fd() { assert_eq!(ttyname(-1), Err(Error::Sys(Errno::EBADF))); } #[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. assert!(getpeereid(-1).is_err()); }