blob: 29719072248c0225b954427c3aeb701fa861f65d [file] [log] [blame]
#[cfg(not(target_os = "redox"))]
use nix::Error;
#[cfg(not(target_os = "redox"))]
use nix::errno::*;
#[cfg(not(target_os = "redox"))]
use nix::fcntl::{open, OFlag, readlink};
#[cfg(not(target_os = "redox"))]
use nix::fcntl::{openat, readlinkat, renameat};
#[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 tempfile::{self, NamedTempFile};
#[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;
#[test]
#[cfg(not(target_os = "redox"))]
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(),
Error::Sys(Errno::ENOENT));
close(old_dirfd).unwrap();
close(new_dirfd).unwrap();
assert!(new_dir.path().join("new").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 std::fs::File;
use std::io::prelude::*;
use std::io::{BufRead, BufReader, SeekFrom};
use std::os::unix::prelude::*;
use libc::loff_t;
use nix::fcntl::*;
use nix::sys::stat::fstat;
use nix::sys::uio::IoVec;
use nix::unistd::{close, pipe, read, write};
use tempfile::{tempfile, NamedTempFile};
/// 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).
///
/// FIXME: This test is disabled for linux based builds, because Travis
/// Linux version is too old for `copy_file_range`.
#[test]
#[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.seek(SeekFrom::Start(0)).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 mut iovecs = Vec::with_capacity(2);
iovecs.push(IoVec::from_slice(&buf1[0..3]));
iovecs.push(IoVec::from_slice(&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();
}
#[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(not(any(target_arch = "aarch64",
target_arch = "arm",
target_arch = "armv7",
target_arch = "x86",
target_arch = "mips",
target_arch = "mips64",
target_arch = "mips64el",
target_arch = "powerpc64",
target_arch = "powerpc64le",
target_env = "musl")))]
fn test_ofd_write_lock() {
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 {
l_type: libc::F_WRLCK as libc::c_short,
l_whence: libc::SEEK_SET as libc::c_short,
l_start: 0,
l_len: 0,
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(not(any(target_arch = "aarch64",
target_arch = "arm",
target_arch = "armv7",
target_arch = "x86",
target_arch = "mips",
target_arch = "mips64",
target_arch = "mips64el",
target_arch = "powerpc64",
target_arch = "powerpc64le",
target_env = "musl")))]
fn test_ofd_read_lock() {
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 {
l_type: libc::F_RDLCK as libc::c_short,
l_whence: libc::SEEK_SET as libc::c_short,
l_start: 0,
l_len: 0,
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));
}
fn lock_info(inode: usize) -> Option<(String, String)> {
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",
any(target_os = "wasi", target_env = "wasi"),
target_env = "uclibc",
target_env = "freebsd"))]
mod test_posix_fadvise {
use tempfile::NamedTempFile;
use std::os::unix::io::{RawFd, AsRawFd};
use nix::errno::Errno;
use nix::fcntl::*;
use nix::unistd::pipe;
#[test]
fn test_success() {
let tmp = NamedTempFile::new().unwrap();
let fd = tmp.as_raw_fd();
let res = posix_fadvise(fd, 0, 100, PosixFadviseAdvice::POSIX_FADV_WILLNEED).unwrap();
assert_eq!(res, 0);
}
#[test]
fn test_errno() {
let (rd, _wr) = pipe().unwrap();
let errno = posix_fadvise(rd as RawFd, 0, 100, PosixFadviseAdvice::POSIX_FADV_WILLNEED)
.unwrap();
assert_eq!(errno, Errno::ESPIPE as i32);
}
}
#[cfg(any(target_os = "linux",
target_os = "android",
target_os = "emscripten",
target_os = "fuchsia",
any(target_os = "wasi", target_env = "wasi"),
target_os = "freebsd"))]
mod test_posix_fallocate {
use tempfile::NamedTempFile;
use std::{io::Read, os::unix::io::{RawFd, AsRawFd}};
use nix::errno::Errno;
use nix::fcntl::*;
use nix::unistd::pipe;
#[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(nix::Error::Sys(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();
use nix::Error::Sys;
match err {
Sys(Errno::EINVAL)
| Sys(Errno::ENODEV)
| Sys(Errno::ESPIPE)
| Sys(Errno::EBADF) => (),
errno =>
panic!(
"unexpected errno {}",
errno,
),
}
}
}