| #[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::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( |
| 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(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(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.as_raw_fd(), |
| Some(&mut offset), |
| wr.as_raw_fd(), |
| 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.as_raw_fd(), wr2.as_raw_fd(), 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.as_raw_fd(), &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 verify_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."); |
| } |
| } |
| |
| /// Verify that `Flock::unlock()` correctly obtains unlocks. |
| #[test] |
| fn verify_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."); |
| } |
| } |
| } |