summaryrefslogtreecommitdiffstats
path: root/third_party/rust/nix/test/test_fcntl.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/nix/test/test_fcntl.rs')
-rw-r--r--third_party/rust/nix/test/test_fcntl.rs565
1 files changed, 565 insertions, 0 deletions
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,),
+ }
+ }
+}