third_party_rust_nix/test/test_unistd.rs
Alan Somers c8a9cf3351 Fix the test_acct test
It has never actually executed its command, so the only reason that it
ever worked is that on most systems there are usually processes starting
and exiting all the time.
2022-06-07 20:10:46 -06:00

1167 lines
37 KiB
Rust

#[cfg(not(target_os = "redox"))]
use nix::fcntl::{self, open};
#[cfg(not(any(target_os = "redox", target_os = "haiku")))]
use nix::fcntl::{readlink};
use nix::fcntl::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", target_os = "haiku")))]
use nix::pty::{posix_openpt, grantpt, unlockpt, ptsname};
use nix::errno::Errno;
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 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();
// 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
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", 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
assert!(mkfifoat(None, &env::temp_dir(), Mode::S_IRUSR).is_err());
}
#[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();
assert!(mkfifoat(Some(dirfd), mkfifoat_dir, Mode::S_IRUSR).is_err());
}
#[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();
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;
for _ in 0..5 {
let newdir = "a".repeat(100);
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!("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 tempfile::NamedTempFile;
use std::process::Command;
use std::{thread, time};
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!(resuids.real.as_raw() != libc::uid_t::max_value());
assert!(resuids.effective.as_raw() != libc::uid_t::max_value());
assert!(resuids.saved.as_raw() != libc::uid_t::max_value());
}
#[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!(resgids.real.as_raw() != libc::gid_t::max_value());
assert!(resgids.effective.as_raw() != libc::gid_t::max_value());
assert!(resgids.saved.as_raw() != libc::gid_t::max_value());
}
// 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 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();
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();
assert!(access(&path, AccessFlags::R_OK | AccessFlags::W_OK).is_ok());
}
#[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();
dbg!(&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);
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", 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.
assert!(getpeereid(-1).is_err());
}