#[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::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
// 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]);
}

#[test]
#[cfg(target_os = "linux")]
// 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_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]);
}

#[test]
#[cfg(target_os = "linux")]
// 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_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!(res.unwrap_err(), Errno::EXDEV);
}

#[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(&old_dirfd, "old", &new_dirfd, "new").unwrap();
    assert_eq!(
        renameat(&old_dirfd, "old", &new_dirfd, "new").unwrap_err(),
        Errno::ENOENT
    );
    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(&old_dirfd, "old", &new_dirfd, "new", RenameFlags::empty())
        .unwrap();
    assert_eq!(
        renameat2(&old_dirfd, "old", &new_dirfd, "new", RenameFlags::empty())
            .unwrap_err(),
        Errno::ENOENT
    );
    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(
        &old_dirfd,
        "old",
        &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");
}

#[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(
            &old_dirfd,
            "old",
            &new_dirfd,
            "new",
            RenameFlags::RENAME_NOREPLACE
        )
        .unwrap_err(),
        Errno::EEXIST
    );
    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
    );
}

/// 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;

    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, Some(&mut from_offset), &tmp2, 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 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, &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, &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]);
    }

    #[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, &mut buf).unwrap());
        assert_eq!(b"abcdef", &buf[0..6]);
    }

    #[cfg(target_os = "linux")]
    #[test]
    fn test_fallocate() {
        let tmp = NamedTempFile::new().unwrap();

        fallocate(&tmp, FallocateFlags::empty(), 0, 100).unwrap();

        // Check if we read exactly 100 bytes
        let mut buf = [0u8; 200];
        assert_eq!(100, read(&tmp, &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 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(&tmp).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(&tmp, 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(&tmp, 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 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(&tmp).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(&tmp, 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(&tmp, 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 tempfile::NamedTempFile;

    #[test]
    fn test_success() {
        let tmp = NamedTempFile::new().unwrap();
        posix_fadvise(&tmp, 0, 100, PosixFadviseAdvice::POSIX_FADV_WILLNEED)
            .expect("posix_fadvise failed");
    }

    #[test]
    fn test_errno() {
        let (rd, _wr) = pipe().unwrap();
        let res =
            posix_fadvise(&rd, 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;
    use tempfile::NamedTempFile;

    #[test]
    fn success() {
        const LEN: usize = 100;
        let mut tmp = NamedTempFile::new().unwrap();
        let res = posix_fallocate(&tmp, 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::ENOTSUP) | Err(Errno::EINVAL) => {
                // POSIX 1003.1-2024 Issue 8 specified ENOTSUP for "the file
                // system does not support this operation", so Nix should accept
                // that error code and pass the test.
                // But older POSIX required posix_fallocate to return EINVAL
                // both for invalid arguments (i.e. len < 0) and if the
                // operation is not supported by the file system.  So we must
                // also pass the test if it returns EINVAL.
            }
            _ => res.unwrap(),
        }
    }

    #[test]
    fn errno() {
        let (rd, _wr) = pipe().unwrap();
        let err = posix_fallocate(&rd, 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::path::PathBuf;

    let tmp = NamedTempFile::new().unwrap();
    let mut path = PathBuf::new();
    let res =
        fcntl(&tmp, 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_preallocate() {
    use nix::fcntl::*;

    let tmp = NamedTempFile::new().unwrap();
    let mut st: libc::fstore_t = unsafe { std::mem::zeroed() };

    st.fst_flags = libc::F_ALLOCATECONTIG as libc::c_uint;
    st.fst_posmode = libc::F_PEOFPOSMODE;
    st.fst_length = 1024;
    let res = fcntl(tmp, FcntlArg::F_PREALLOCATE(&mut st))
        .expect("preallocation failed");

    assert_eq!(res, 0);
    assert!(st.fst_bytesalloc > 0);
}

#[cfg(apple_targets)]
#[test]
fn test_f_get_path_nofirmlink() {
    use nix::fcntl::*;
    use std::path::PathBuf;

    let tmp = NamedTempFile::new().unwrap();
    let mut path = PathBuf::new();
    let res = fcntl(&tmp, 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::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 mut path = PathBuf::new();
    let res =
        fcntl(&tmp2, 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");
    }
}

#[cfg(apple_targets)]
#[test]
fn test_f_rdadvise() {
    use nix::fcntl::*;

    let contents = vec![1; 1024];
    let mut buf = [0; 1024];
    let mut tmp = NamedTempFile::new().unwrap();
    tmp.write_all(&contents).unwrap();
    let fd = open(tmp.path(), OFlag::empty(), Mode::empty()).unwrap();
    let rad = libc::radvisory {
        ra_offset: 0,
        ra_count: contents.len() as _,
    };
    let res = fcntl(&tmp, FcntlArg::F_RDADVISE(rad)).expect("rdadivse failed");
    assert_ne!(res, -1);
    assert_eq!(contents.len(), read(&fd, &mut buf).unwrap());
    assert_eq!(contents, &buf[0..contents.len()]);
}

#[cfg(apple_targets)]
#[test]
fn test_f_log2phys() {
    use nix::fcntl::*;

    const CONTENTS: &[u8] = b"abcd";
    let mut tmp = NamedTempFile::new().unwrap();
    tmp.write_all(CONTENTS).unwrap();
    let mut offset: libc::off_t = 0;
    let mut res = fcntl(&tmp, FcntlArg::F_LOG2PHYS(&mut offset))
        .expect("log2phys failed");
    assert_ne!(res, -1);
    assert_ne!(offset, 0);
    let mut info: libc::log2phys = unsafe { std::mem::zeroed() };
    info.l2p_contigbytes = CONTENTS.len() as _;
    info.l2p_devoffset = 3;
    res = fcntl(&tmp, FcntlArg::F_LOG2PHYS_EXT(&mut info))
        .expect("log2phys failed");
    assert_ne!(res, -1);
    assert_ne!({ info.l2p_devoffset }, 3);
}

#[cfg(apple_targets)]
#[test]
fn test_f_transferextents() {
    use nix::fcntl::*;
    use std::os::fd::AsRawFd;

    let tmp1 = NamedTempFile::new().unwrap();
    let tmp2 = NamedTempFile::new().unwrap();
    let res = fcntl(&tmp1, FcntlArg::F_TRANSFEREXTENTS(tmp2.as_raw_fd()))
        .expect("transferextents failed");
    assert_ne!(res, -1);
}

#[cfg(target_os = "freebsd")]
#[test]
fn test_f_readahead() {
    use nix::fcntl::*;

    let tmp = NamedTempFile::new().unwrap();
    let mut res = fcntl(&tmp, FcntlArg::F_READAHEAD(1_000_000))
        .expect("read ahead failed");
    assert_ne!(res, -1);
    res = fcntl(&tmp, FcntlArg::F_READAHEAD(-1024)).expect("read ahead failed");
    assert_ne!(res, -1);
}
