Source code
Revision control
Copy as Markdown
Other Tools
#[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(target_os = "linux")]
use nix::fcntl::{openat2, OpenHow, ResolveFlag};
#[cfg(all(
target_os = "linux",
target_env = "gnu",
any(
target_arch = "x86_64",
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::NamedTempFile;
#[test]
#[cfg(not(target_os = "redox"))]
// QEMU does not handle openat well enough to satisfy this test
#[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(
Some(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(target_os = "linux")]
// QEMU does not handle openat well enough to satisfy this test
#[cfg_attr(qemu, ignore)]
fn test_openat2() {
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 = openat2(
dirfd,
tmp.path().file_name().unwrap(),
OpenHow::new()
.flags(OFlag::O_RDONLY)
.mode(Mode::empty())
.resolve(ResolveFlag::RESOLVE_BENEATH),
)
.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(target_os = "linux")]
// QEMU does not handle openat well enough to satisfy this test
#[cfg_attr(qemu, ignore)]
fn test_openat2_forbidden() {
let mut tmp = NamedTempFile::new().unwrap();
tmp.write_all(b"let me out").unwrap();
let dirfd =
open(tmp.path().parent().unwrap(), OFlag::empty(), Mode::empty())
.unwrap();
let escape_attempt =
tmp.path().parent().unwrap().join("../../../hello.txt");
let res = openat2(
dirfd,
&escape_attempt,
OpenHow::new()
.flags(OFlag::O_RDONLY)
.resolve(ResolveFlag::RESOLVE_BENEATH),
);
assert_eq!(Err(Errno::EXDEV), res);
}
#[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 = "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 = "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 = "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(Some(dirfd), "b").unwrap().to_str().unwrap(),
expected_dir
);
}
/// 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).
#[cfg(any(
linux_android,
// Not available until FreeBSD 13.0
all(target_os = "freebsd", fbsd14),
))]
#[test]
// QEMU does not support copy_file_range. Skip under qemu
#[cfg_attr(qemu, ignore)]
fn test_copy_file_range() {
use nix::fcntl::copy_file_range;
use std::os::unix::io::AsFd;
const CONTENTS: &[u8] = b"foobarbaz";
let mut tmp1 = tempfile::tempfile().unwrap();
let mut tmp2 = tempfile::tempfile().unwrap();
tmp1.write_all(CONTENTS).unwrap();
tmp1.flush().unwrap();
let mut from_offset: i64 = 3;
copy_file_range(
tmp1.as_fd(),
Some(&mut from_offset),
tmp2.as_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);
}
#[cfg(linux_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::{pipe, read, write};
use tempfile::tempfile;
#[cfg(target_os = "linux")]
use tempfile::NamedTempFile;
use crate::*;
#[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, Some(&mut offset), wr, None, 2, SpliceFFlags::empty())
.unwrap();
assert_eq!(2, res);
let mut buf = [0u8; 1024];
assert_eq!(2, read(rd.as_raw_fd(), &mut buf).unwrap());
assert_eq!(b"f1", &buf[0..2]);
assert_eq!(7, offset);
}
#[test]
fn test_tee() {
let (rd1, wr1) = pipe().unwrap();
let (rd2, wr2) = pipe().unwrap();
write(wr1, b"abc").unwrap();
let res = tee(rd1.try_clone().unwrap(), 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.as_raw_fd(), &mut buf).unwrap());
assert_eq!(b"ab", &buf[0..2]);
// Check all the bytes are still at rd1.
assert_eq!(3, read(rd1.as_raw_fd(), &mut buf).unwrap());
assert_eq!(b"abc", &buf[0..3]);
}
#[test]
fn test_vmsplice() {
let (rd, wr) = pipe().unwrap();
let buf1 = b"abcdef";
let buf2 = b"defghi";
let iovecs = [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.as_raw_fd(), &mut buf).unwrap());
assert_eq!(b"abcdef", &buf[0..6]);
}
#[cfg(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.as_file()).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.as_file()).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(
linux_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;
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_raw_fd(),
0,
100,
PosixFadviseAdvice::POSIX_FADV_WILLNEED,
);
assert_eq!(res, Err(Errno::ESPIPE));
}
}
#[cfg(any(
linux_android,
freebsdlike,
target_os = "emscripten",
target_os = "fuchsia",
target_os = "wasi",
))]
mod test_posix_fallocate {
use nix::errno::Errno;
use nix::fcntl::*;
use nix::unistd::pipe;
use std::{io::Read, os::unix::io::AsRawFd};
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_raw_fd(), 0, 100).unwrap_err();
match err {
Errno::EINVAL | Errno::ENODEV | Errno::ESPIPE | Errno::EBADF => (),
errno => panic!("unexpected errno {errno}",),
}
}
}
#[cfg(any(target_os = "dragonfly", target_os = "netbsd", apple_targets))]
#[test]
fn test_f_get_path() {
use nix::fcntl::*;
use std::{os::unix::io::AsRawFd, path::PathBuf};
let tmp = NamedTempFile::new().unwrap();
let fd = tmp.as_raw_fd();
let mut path = PathBuf::new();
let res =
fcntl(fd, FcntlArg::F_GETPATH(&mut path)).expect("get path failed");
assert_ne!(res, -1);
assert_eq!(
path.as_path().canonicalize().unwrap(),
tmp.path().canonicalize().unwrap()
);
}
#[cfg(apple_targets)]
#[test]
fn test_f_get_path_nofirmlink() {
use nix::fcntl::*;
use std::{os::unix::io::AsRawFd, path::PathBuf};
let tmp = NamedTempFile::new().unwrap();
let fd = tmp.as_raw_fd();
let mut path = PathBuf::new();
let res = fcntl(fd, FcntlArg::F_GETPATH_NOFIRMLINK(&mut path))
.expect("get path failed");
let mut tmpstr = String::from("/System/Volumes/Data");
tmpstr.push_str(
&tmp.path()
.canonicalize()
.unwrap()
.into_os_string()
.into_string()
.unwrap(),
);
assert_ne!(res, -1);
assert_eq!(
path.as_path()
.canonicalize()
.unwrap()
.into_os_string()
.into_string()
.unwrap(),
tmpstr
);
}
#[cfg(all(target_os = "freebsd", target_arch = "x86_64"))]
#[test]
fn test_f_kinfo() {
use nix::fcntl::*;
use std::{os::unix::io::AsRawFd, path::PathBuf};
let tmp = NamedTempFile::new().unwrap();
// With TMPDIR set with UFS, the vnode name is not entered
// into the name cache thus path is always empty.
// Therefore, we reopen the tempfile a second time for the test
// to pass.
let tmp2 = File::open(tmp.path()).unwrap();
let fd = tmp2.as_raw_fd();
let mut path = PathBuf::new();
let res = fcntl(fd, FcntlArg::F_KINFO(&mut path)).expect("get path failed");
assert_ne!(res, -1);
assert_eq!(path, tmp.path());
}
/// Test `Flock` and associated functions.
///
#[cfg(not(any(target_os = "redox", target_os = "solaris")))]
mod test_flock {
use nix::fcntl::*;
use tempfile::NamedTempFile;
/// Verify that `Flock::lock()` correctly obtains a lock, and subsequently unlocks upon drop.
#[test]
fn lock_and_drop() {
// Get 2 `File` handles to same underlying file.
let file1 = NamedTempFile::new().unwrap();
let file2 = file1.reopen().unwrap();
let file1 = file1.into_file();
// Lock first handle
let lock1 = Flock::lock(file1, FlockArg::LockExclusive).unwrap();
// Attempt to lock second handle
let file2 = match Flock::lock(file2, FlockArg::LockExclusiveNonblock) {
Ok(_) => panic!("Expected second exclusive lock to fail."),
Err((f, _)) => f,
};
// Drop first lock
std::mem::drop(lock1);
// Attempt to lock second handle again (but successfully)
if Flock::lock(file2, FlockArg::LockExclusiveNonblock).is_err() {
panic!("Expected locking to be successful.");
}
}
/// An exclusive lock can be downgraded
#[test]
fn downgrade() {
let file1 = NamedTempFile::new().unwrap();
let file2 = file1.reopen().unwrap();
let file1 = file1.into_file();
// Lock first handle
let lock1 = Flock::lock(file1, FlockArg::LockExclusive).unwrap();
// Attempt to lock second handle
let file2 = Flock::lock(file2, FlockArg::LockSharedNonblock)
.unwrap_err()
.0;
// Downgrade the lock
lock1.relock(FlockArg::LockShared).unwrap();
// Attempt to lock second handle again (but successfully)
Flock::lock(file2, FlockArg::LockSharedNonblock)
.expect("Expected locking to be successful.");
}
/// Verify that `Flock::unlock()` correctly obtains unlocks.
#[test]
fn unlock() {
// Get 2 `File` handles to same underlying file.
let file1 = NamedTempFile::new().unwrap();
let file2 = file1.reopen().unwrap();
let file1 = file1.into_file();
// Lock first handle
let lock1 = Flock::lock(file1, FlockArg::LockExclusive).unwrap();
// Unlock and retain file so any erroneous flocks also remain present.
let _file1 = lock1.unlock().unwrap();
// Attempt to lock second handle.
if Flock::lock(file2, FlockArg::LockExclusiveNonblock).is_err() {
panic!("Expected locking to be successful.");
}
}
/// A shared lock can be upgraded
#[test]
fn upgrade() {
let file1 = NamedTempFile::new().unwrap();
let file2 = file1.reopen().unwrap();
let file3 = file1.reopen().unwrap();
let file1 = file1.into_file();
// Lock first handle
let lock1 = Flock::lock(file1, FlockArg::LockShared).unwrap();
// Attempt to lock second handle
{
Flock::lock(file2, FlockArg::LockSharedNonblock)
.expect("Locking should've succeeded");
}
// Upgrade the lock
lock1.relock(FlockArg::LockExclusive).unwrap();
// Acquiring an additional shared lock should fail
Flock::lock(file3, FlockArg::LockSharedNonblock)
.expect_err("Should not have been able to lock the file");
}
}