1 #[cfg(not(target_os = "redox"))]
2 use nix::errno::*;
3 #[cfg(not(target_os = "redox"))]
4 use nix::fcntl::{open, readlink, OFlag};
5 #[cfg(not(target_os = "redox"))]
6 use nix::fcntl::{openat, readlinkat, renameat};
7
8 #[cfg(target_os = "linux")]
9 use nix::fcntl::{openat2, OpenHow, ResolveFlag};
10
11 #[cfg(all(
12 target_os = "linux",
13 target_env = "gnu",
14 any(
15 target_arch = "x86_64",
16 target_arch = "powerpc",
17 target_arch = "s390x"
18 )
19 ))]
20 use nix::fcntl::{renameat2, RenameFlags};
21 #[cfg(not(target_os = "redox"))]
22 use nix::sys::stat::Mode;
23 #[cfg(not(target_os = "redox"))]
24 use nix::unistd::{close, read};
25 #[cfg(not(target_os = "redox"))]
26 use std::fs::File;
27 #[cfg(not(target_os = "redox"))]
28 use std::io::prelude::*;
29 #[cfg(not(target_os = "redox"))]
30 use std::os::unix::fs;
31 #[cfg(not(target_os = "redox"))]
32 use tempfile::NamedTempFile;
33
34 #[test]
35 #[cfg(not(target_os = "redox"))]
36 // QEMU does not handle openat well enough to satisfy this test
37 // https://gitlab.com/qemu-project/qemu/-/issues/829
38 #[cfg_attr(qemu, ignore)]
test_openat()39 fn test_openat() {
40 const CONTENTS: &[u8] = b"abcd";
41 let mut tmp = NamedTempFile::new().unwrap();
42 tmp.write_all(CONTENTS).unwrap();
43
44 let dirfd =
45 open(tmp.path().parent().unwrap(), OFlag::empty(), Mode::empty())
46 .unwrap();
47 let fd = openat(
48 Some(dirfd),
49 tmp.path().file_name().unwrap(),
50 OFlag::O_RDONLY,
51 Mode::empty(),
52 )
53 .unwrap();
54
55 let mut buf = [0u8; 1024];
56 assert_eq!(4, read(fd, &mut buf).unwrap());
57 assert_eq!(CONTENTS, &buf[0..4]);
58
59 close(fd).unwrap();
60 close(dirfd).unwrap();
61 }
62
63 #[test]
64 #[cfg(target_os = "linux")]
65 // QEMU does not handle openat well enough to satisfy this test
66 // https://gitlab.com/qemu-project/qemu/-/issues/829
67 #[cfg_attr(qemu, ignore)]
test_openat2()68 fn test_openat2() {
69 const CONTENTS: &[u8] = b"abcd";
70 let mut tmp = NamedTempFile::new().unwrap();
71 tmp.write_all(CONTENTS).unwrap();
72
73 let dirfd =
74 open(tmp.path().parent().unwrap(), OFlag::empty(), Mode::empty())
75 .unwrap();
76
77 let fd = openat2(
78 dirfd,
79 tmp.path().file_name().unwrap(),
80 OpenHow::new()
81 .flags(OFlag::O_RDONLY)
82 .mode(Mode::empty())
83 .resolve(ResolveFlag::RESOLVE_BENEATH),
84 )
85 .unwrap();
86
87 let mut buf = [0u8; 1024];
88 assert_eq!(4, read(fd, &mut buf).unwrap());
89 assert_eq!(CONTENTS, &buf[0..4]);
90
91 close(fd).unwrap();
92 close(dirfd).unwrap();
93 }
94
95 #[test]
96 #[cfg(target_os = "linux")]
97 // QEMU does not handle openat well enough to satisfy this test
98 // https://gitlab.com/qemu-project/qemu/-/issues/829
99 #[cfg_attr(qemu, ignore)]
test_openat2_forbidden()100 fn test_openat2_forbidden() {
101 let mut tmp = NamedTempFile::new().unwrap();
102 tmp.write_all(b"let me out").unwrap();
103
104 let dirfd =
105 open(tmp.path().parent().unwrap(), OFlag::empty(), Mode::empty())
106 .unwrap();
107
108 let escape_attempt =
109 tmp.path().parent().unwrap().join("../../../hello.txt");
110
111 let res = openat2(
112 dirfd,
113 &escape_attempt,
114 OpenHow::new()
115 .flags(OFlag::O_RDONLY)
116 .resolve(ResolveFlag::RESOLVE_BENEATH),
117 );
118 assert_eq!(Err(Errno::EXDEV), res);
119 }
120
121 #[test]
122 #[cfg(not(target_os = "redox"))]
test_renameat()123 fn test_renameat() {
124 let old_dir = tempfile::tempdir().unwrap();
125 let old_dirfd =
126 open(old_dir.path(), OFlag::empty(), Mode::empty()).unwrap();
127 let old_path = old_dir.path().join("old");
128 File::create(old_path).unwrap();
129 let new_dir = tempfile::tempdir().unwrap();
130 let new_dirfd =
131 open(new_dir.path(), OFlag::empty(), Mode::empty()).unwrap();
132 renameat(Some(old_dirfd), "old", Some(new_dirfd), "new").unwrap();
133 assert_eq!(
134 renameat(Some(old_dirfd), "old", Some(new_dirfd), "new").unwrap_err(),
135 Errno::ENOENT
136 );
137 close(old_dirfd).unwrap();
138 close(new_dirfd).unwrap();
139 assert!(new_dir.path().join("new").exists());
140 }
141
142 #[test]
143 #[cfg(all(
144 target_os = "linux",
145 target_env = "gnu",
146 any(
147 target_arch = "x86_64",
148 target_arch = "powerpc",
149 target_arch = "s390x"
150 )
151 ))]
test_renameat2_behaves_like_renameat_with_no_flags()152 fn test_renameat2_behaves_like_renameat_with_no_flags() {
153 let old_dir = tempfile::tempdir().unwrap();
154 let old_dirfd =
155 open(old_dir.path(), OFlag::empty(), Mode::empty()).unwrap();
156 let old_path = old_dir.path().join("old");
157 File::create(old_path).unwrap();
158 let new_dir = tempfile::tempdir().unwrap();
159 let new_dirfd =
160 open(new_dir.path(), OFlag::empty(), Mode::empty()).unwrap();
161 renameat2(
162 Some(old_dirfd),
163 "old",
164 Some(new_dirfd),
165 "new",
166 RenameFlags::empty(),
167 )
168 .unwrap();
169 assert_eq!(
170 renameat2(
171 Some(old_dirfd),
172 "old",
173 Some(new_dirfd),
174 "new",
175 RenameFlags::empty()
176 )
177 .unwrap_err(),
178 Errno::ENOENT
179 );
180 close(old_dirfd).unwrap();
181 close(new_dirfd).unwrap();
182 assert!(new_dir.path().join("new").exists());
183 }
184
185 #[test]
186 #[cfg(all(
187 target_os = "linux",
188 target_env = "gnu",
189 any(
190 target_arch = "x86_64",
191 target_arch = "powerpc",
192 target_arch = "s390x"
193 )
194 ))]
test_renameat2_exchange()195 fn test_renameat2_exchange() {
196 let old_dir = tempfile::tempdir().unwrap();
197 let old_dirfd =
198 open(old_dir.path(), OFlag::empty(), Mode::empty()).unwrap();
199 let old_path = old_dir.path().join("old");
200 {
201 let mut old_f = File::create(&old_path).unwrap();
202 old_f.write_all(b"old").unwrap();
203 }
204 let new_dir = tempfile::tempdir().unwrap();
205 let new_dirfd =
206 open(new_dir.path(), OFlag::empty(), Mode::empty()).unwrap();
207 let new_path = new_dir.path().join("new");
208 {
209 let mut new_f = File::create(&new_path).unwrap();
210 new_f.write_all(b"new").unwrap();
211 }
212 renameat2(
213 Some(old_dirfd),
214 "old",
215 Some(new_dirfd),
216 "new",
217 RenameFlags::RENAME_EXCHANGE,
218 )
219 .unwrap();
220 let mut buf = String::new();
221 let mut new_f = File::open(&new_path).unwrap();
222 new_f.read_to_string(&mut buf).unwrap();
223 assert_eq!(buf, "old");
224 buf = "".to_string();
225 let mut old_f = File::open(&old_path).unwrap();
226 old_f.read_to_string(&mut buf).unwrap();
227 assert_eq!(buf, "new");
228 close(old_dirfd).unwrap();
229 close(new_dirfd).unwrap();
230 }
231
232 #[test]
233 #[cfg(all(
234 target_os = "linux",
235 target_env = "gnu",
236 any(
237 target_arch = "x86_64",
238 target_arch = "powerpc",
239 target_arch = "s390x"
240 )
241 ))]
test_renameat2_noreplace()242 fn test_renameat2_noreplace() {
243 let old_dir = tempfile::tempdir().unwrap();
244 let old_dirfd =
245 open(old_dir.path(), OFlag::empty(), Mode::empty()).unwrap();
246 let old_path = old_dir.path().join("old");
247 File::create(old_path).unwrap();
248 let new_dir = tempfile::tempdir().unwrap();
249 let new_dirfd =
250 open(new_dir.path(), OFlag::empty(), Mode::empty()).unwrap();
251 let new_path = new_dir.path().join("new");
252 File::create(new_path).unwrap();
253 assert_eq!(
254 renameat2(
255 Some(old_dirfd),
256 "old",
257 Some(new_dirfd),
258 "new",
259 RenameFlags::RENAME_NOREPLACE
260 )
261 .unwrap_err(),
262 Errno::EEXIST
263 );
264 close(old_dirfd).unwrap();
265 close(new_dirfd).unwrap();
266 assert!(new_dir.path().join("new").exists());
267 assert!(old_dir.path().join("old").exists());
268 }
269
270 #[test]
271 #[cfg(not(target_os = "redox"))]
test_readlink()272 fn test_readlink() {
273 let tempdir = tempfile::tempdir().unwrap();
274 let src = tempdir.path().join("a");
275 let dst = tempdir.path().join("b");
276 println!("a: {:?}, b: {:?}", &src, &dst);
277 fs::symlink(src.as_path(), dst.as_path()).unwrap();
278 let dirfd = open(tempdir.path(), OFlag::empty(), Mode::empty()).unwrap();
279 let expected_dir = src.to_str().unwrap();
280
281 assert_eq!(readlink(&dst).unwrap().to_str().unwrap(), expected_dir);
282 assert_eq!(
283 readlinkat(Some(dirfd), "b").unwrap().to_str().unwrap(),
284 expected_dir
285 );
286 }
287
288 /// This test creates a temporary file containing the contents
289 /// 'foobarbaz' and uses the `copy_file_range` call to transfer
290 /// 3 bytes at offset 3 (`bar`) to another empty file at offset 0. The
291 /// resulting file is read and should contain the contents `bar`.
292 /// The from_offset should be updated by the call to reflect
293 /// the 3 bytes read (6).
294 #[cfg(any(
295 linux_android,
296 // Not available until FreeBSD 13.0
297 all(target_os = "freebsd", fbsd14),
298 ))]
299 #[test]
300 // QEMU does not support copy_file_range. Skip under qemu
301 #[cfg_attr(qemu, ignore)]
test_copy_file_range()302 fn test_copy_file_range() {
303 use nix::fcntl::copy_file_range;
304 use std::os::unix::io::AsFd;
305
306 const CONTENTS: &[u8] = b"foobarbaz";
307
308 let mut tmp1 = tempfile::tempfile().unwrap();
309 let mut tmp2 = tempfile::tempfile().unwrap();
310
311 tmp1.write_all(CONTENTS).unwrap();
312 tmp1.flush().unwrap();
313
314 let mut from_offset: i64 = 3;
315 copy_file_range(
316 tmp1.as_fd(),
317 Some(&mut from_offset),
318 tmp2.as_fd(),
319 None,
320 3,
321 )
322 .unwrap();
323
324 let mut res: String = String::new();
325 tmp2.rewind().unwrap();
326 tmp2.read_to_string(&mut res).unwrap();
327
328 assert_eq!(res, String::from("bar"));
329 assert_eq!(from_offset, 6);
330 }
331
332 #[cfg(linux_android)]
333 mod linux_android {
334 use libc::loff_t;
335 use std::io::prelude::*;
336 use std::io::IoSlice;
337 use std::os::unix::prelude::*;
338
339 use nix::fcntl::*;
340 use nix::unistd::{pipe, read, write};
341
342 use tempfile::tempfile;
343 #[cfg(target_os = "linux")]
344 use tempfile::NamedTempFile;
345
346 use crate::*;
347
348 #[test]
test_splice()349 fn test_splice() {
350 const CONTENTS: &[u8] = b"abcdef123456";
351 let mut tmp = tempfile().unwrap();
352 tmp.write_all(CONTENTS).unwrap();
353
354 let (rd, wr) = pipe().unwrap();
355 let mut offset: loff_t = 5;
356 let res =
357 splice(tmp, Some(&mut offset), wr, None, 2, SpliceFFlags::empty())
358 .unwrap();
359
360 assert_eq!(2, res);
361
362 let mut buf = [0u8; 1024];
363 assert_eq!(2, read(rd.as_raw_fd(), &mut buf).unwrap());
364 assert_eq!(b"f1", &buf[0..2]);
365 assert_eq!(7, offset);
366 }
367
368 #[test]
test_tee()369 fn test_tee() {
370 let (rd1, wr1) = pipe().unwrap();
371 let (rd2, wr2) = pipe().unwrap();
372
373 write(wr1, b"abc").unwrap();
374 let res = tee(rd1.try_clone().unwrap(), wr2, 2, SpliceFFlags::empty())
375 .unwrap();
376
377 assert_eq!(2, res);
378
379 let mut buf = [0u8; 1024];
380
381 // Check the tee'd bytes are at rd2.
382 assert_eq!(2, read(rd2.as_raw_fd(), &mut buf).unwrap());
383 assert_eq!(b"ab", &buf[0..2]);
384
385 // Check all the bytes are still at rd1.
386 assert_eq!(3, read(rd1.as_raw_fd(), &mut buf).unwrap());
387 assert_eq!(b"abc", &buf[0..3]);
388 }
389
390 #[test]
test_vmsplice()391 fn test_vmsplice() {
392 let (rd, wr) = pipe().unwrap();
393
394 let buf1 = b"abcdef";
395 let buf2 = b"defghi";
396 let iovecs = [IoSlice::new(&buf1[0..3]), IoSlice::new(&buf2[0..3])];
397
398 let res = vmsplice(wr, &iovecs[..], SpliceFFlags::empty()).unwrap();
399
400 assert_eq!(6, res);
401
402 // Check the bytes can be read at rd.
403 let mut buf = [0u8; 32];
404 assert_eq!(6, read(rd.as_raw_fd(), &mut buf).unwrap());
405 assert_eq!(b"abcdef", &buf[0..6]);
406 }
407
408 #[cfg(target_os = "linux")]
409 #[test]
test_fallocate()410 fn test_fallocate() {
411 let tmp = NamedTempFile::new().unwrap();
412
413 let fd = tmp.as_raw_fd();
414 fallocate(fd, FallocateFlags::empty(), 0, 100).unwrap();
415
416 // Check if we read exactly 100 bytes
417 let mut buf = [0u8; 200];
418 assert_eq!(100, read(fd, &mut buf).unwrap());
419 }
420
421 // The tests below are disabled for the listed targets
422 // due to OFD locks not being available in the kernel/libc
423 // versions used in the CI environment, probably because
424 // they run under QEMU.
425
426 #[test]
427 #[cfg(all(target_os = "linux", not(target_env = "musl")))]
428 #[cfg_attr(target_env = "uclibc", ignore)] // uclibc doesn't support OFD locks, but the test should still compile
test_ofd_write_lock()429 fn test_ofd_write_lock() {
430 use nix::sys::stat::fstat;
431 use std::mem;
432
433 let tmp = NamedTempFile::new().unwrap();
434
435 let fd = tmp.as_raw_fd();
436 let statfs = nix::sys::statfs::fstatfs(tmp.as_file()).unwrap();
437 if statfs.filesystem_type() == nix::sys::statfs::OVERLAYFS_SUPER_MAGIC {
438 // OverlayFS is a union file system. It returns one inode value in
439 // stat(2), but a different one shows up in /proc/locks. So we must
440 // skip the test.
441 skip!("/proc/locks does not work on overlayfs");
442 }
443 let inode = fstat(fd).expect("fstat failed").st_ino as usize;
444
445 let mut flock: libc::flock = unsafe {
446 mem::zeroed() // required for Linux/mips
447 };
448 flock.l_type = libc::F_WRLCK as libc::c_short;
449 flock.l_whence = libc::SEEK_SET as libc::c_short;
450 flock.l_start = 0;
451 flock.l_len = 0;
452 flock.l_pid = 0;
453 fcntl(fd, FcntlArg::F_OFD_SETLKW(&flock)).expect("write lock failed");
454 assert_eq!(
455 Some(("OFDLCK".to_string(), "WRITE".to_string())),
456 lock_info(inode)
457 );
458
459 flock.l_type = libc::F_UNLCK as libc::c_short;
460 fcntl(fd, FcntlArg::F_OFD_SETLKW(&flock)).expect("write unlock failed");
461 assert_eq!(None, lock_info(inode));
462 }
463
464 #[test]
465 #[cfg(all(target_os = "linux", not(target_env = "musl")))]
466 #[cfg_attr(target_env = "uclibc", ignore)] // uclibc doesn't support OFD locks, but the test should still compile
test_ofd_read_lock()467 fn test_ofd_read_lock() {
468 use nix::sys::stat::fstat;
469 use std::mem;
470
471 let tmp = NamedTempFile::new().unwrap();
472
473 let fd = tmp.as_raw_fd();
474 let statfs = nix::sys::statfs::fstatfs(tmp.as_file()).unwrap();
475 if statfs.filesystem_type() == nix::sys::statfs::OVERLAYFS_SUPER_MAGIC {
476 // OverlayFS is a union file system. It returns one inode value in
477 // stat(2), but a different one shows up in /proc/locks. So we must
478 // skip the test.
479 skip!("/proc/locks does not work on overlayfs");
480 }
481 let inode = fstat(fd).expect("fstat failed").st_ino as usize;
482
483 let mut flock: libc::flock = unsafe {
484 mem::zeroed() // required for Linux/mips
485 };
486 flock.l_type = libc::F_RDLCK as libc::c_short;
487 flock.l_whence = libc::SEEK_SET as libc::c_short;
488 flock.l_start = 0;
489 flock.l_len = 0;
490 flock.l_pid = 0;
491 fcntl(fd, FcntlArg::F_OFD_SETLKW(&flock)).expect("read lock failed");
492 assert_eq!(
493 Some(("OFDLCK".to_string(), "READ".to_string())),
494 lock_info(inode)
495 );
496
497 flock.l_type = libc::F_UNLCK as libc::c_short;
498 fcntl(fd, FcntlArg::F_OFD_SETLKW(&flock)).expect("read unlock failed");
499 assert_eq!(None, lock_info(inode));
500 }
501
502 #[cfg(all(target_os = "linux", not(target_env = "musl")))]
lock_info(inode: usize) -> Option<(String, String)>503 fn lock_info(inode: usize) -> Option<(String, String)> {
504 use std::{fs::File, io::BufReader};
505
506 let file = File::open("/proc/locks").expect("open /proc/locks failed");
507 let buf = BufReader::new(file);
508
509 for line in buf.lines() {
510 let line = line.unwrap();
511 let parts: Vec<_> = line.split_whitespace().collect();
512 let lock_type = parts[1];
513 let lock_access = parts[3];
514 let ino_parts: Vec<_> = parts[5].split(':').collect();
515 let ino: usize = ino_parts[2].parse().unwrap();
516 if ino == inode {
517 return Some((lock_type.to_string(), lock_access.to_string()));
518 }
519 }
520 None
521 }
522 }
523
524 #[cfg(any(
525 linux_android,
526 target_os = "emscripten",
527 target_os = "fuchsia",
528 target_os = "wasi",
529 target_env = "uclibc",
530 target_os = "freebsd"
531 ))]
532 mod test_posix_fadvise {
533
534 use nix::errno::Errno;
535 use nix::fcntl::*;
536 use nix::unistd::pipe;
537 use std::os::unix::io::AsRawFd;
538 use tempfile::NamedTempFile;
539
540 #[test]
test_success()541 fn test_success() {
542 let tmp = NamedTempFile::new().unwrap();
543 let fd = tmp.as_raw_fd();
544 posix_fadvise(fd, 0, 100, PosixFadviseAdvice::POSIX_FADV_WILLNEED)
545 .expect("posix_fadvise failed");
546 }
547
548 #[test]
test_errno()549 fn test_errno() {
550 let (rd, _wr) = pipe().unwrap();
551 let res = posix_fadvise(
552 rd.as_raw_fd(),
553 0,
554 100,
555 PosixFadviseAdvice::POSIX_FADV_WILLNEED,
556 );
557 assert_eq!(res, Err(Errno::ESPIPE));
558 }
559 }
560
561 #[cfg(any(
562 linux_android,
563 freebsdlike,
564 target_os = "emscripten",
565 target_os = "fuchsia",
566 target_os = "wasi",
567 ))]
568 mod test_posix_fallocate {
569
570 use nix::errno::Errno;
571 use nix::fcntl::*;
572 use nix::unistd::pipe;
573 use std::{io::Read, os::unix::io::AsRawFd};
574 use tempfile::NamedTempFile;
575
576 #[test]
success()577 fn success() {
578 const LEN: usize = 100;
579 let mut tmp = NamedTempFile::new().unwrap();
580 let fd = tmp.as_raw_fd();
581 let res = posix_fallocate(fd, 0, LEN as libc::off_t);
582 match res {
583 Ok(_) => {
584 let mut data = [1u8; LEN];
585 assert_eq!(tmp.read(&mut data).expect("read failure"), LEN);
586 assert_eq!(&data[..], &[0u8; LEN][..]);
587 }
588 Err(Errno::EINVAL) => {
589 // POSIX requires posix_fallocate to return EINVAL both for
590 // invalid arguments (i.e. len < 0) and if the operation is not
591 // supported by the file system.
592 // There's no way to tell for sure whether the file system
593 // supports posix_fallocate, so we must pass the test if it
594 // returns EINVAL.
595 }
596 _ => res.unwrap(),
597 }
598 }
599
600 #[test]
errno()601 fn errno() {
602 let (rd, _wr) = pipe().unwrap();
603 let err = posix_fallocate(rd.as_raw_fd(), 0, 100).unwrap_err();
604 match err {
605 Errno::EINVAL | Errno::ENODEV | Errno::ESPIPE | Errno::EBADF => (),
606 errno => panic!("unexpected errno {errno}",),
607 }
608 }
609 }
610
611 #[cfg(any(target_os = "dragonfly", target_os = "netbsd", apple_targets))]
612 #[test]
test_f_get_path()613 fn test_f_get_path() {
614 use nix::fcntl::*;
615 use std::{os::unix::io::AsRawFd, path::PathBuf};
616
617 let tmp = NamedTempFile::new().unwrap();
618 let fd = tmp.as_raw_fd();
619 let mut path = PathBuf::new();
620 let res =
621 fcntl(fd, FcntlArg::F_GETPATH(&mut path)).expect("get path failed");
622 assert_ne!(res, -1);
623 assert_eq!(
624 path.as_path().canonicalize().unwrap(),
625 tmp.path().canonicalize().unwrap()
626 );
627 }
628
629 #[cfg(apple_targets)]
630 #[test]
test_f_get_path_nofirmlink()631 fn test_f_get_path_nofirmlink() {
632 use nix::fcntl::*;
633 use std::{os::unix::io::AsRawFd, path::PathBuf};
634
635 let tmp = NamedTempFile::new().unwrap();
636 let fd = tmp.as_raw_fd();
637 let mut path = PathBuf::new();
638 let res = fcntl(fd, FcntlArg::F_GETPATH_NOFIRMLINK(&mut path))
639 .expect("get path failed");
640 let mut tmpstr = String::from("/System/Volumes/Data");
641 tmpstr.push_str(
642 &tmp.path()
643 .canonicalize()
644 .unwrap()
645 .into_os_string()
646 .into_string()
647 .unwrap(),
648 );
649 assert_ne!(res, -1);
650 assert_eq!(
651 path.as_path()
652 .canonicalize()
653 .unwrap()
654 .into_os_string()
655 .into_string()
656 .unwrap(),
657 tmpstr
658 );
659 }
660
661 #[cfg(all(target_os = "freebsd", target_arch = "x86_64"))]
662 #[test]
test_f_kinfo()663 fn test_f_kinfo() {
664 use nix::fcntl::*;
665 use std::{os::unix::io::AsRawFd, path::PathBuf};
666
667 let tmp = NamedTempFile::new().unwrap();
668 // With TMPDIR set with UFS, the vnode name is not entered
669 // into the name cache thus path is always empty.
670 // Therefore, we reopen the tempfile a second time for the test
671 // to pass.
672 let tmp2 = File::open(tmp.path()).unwrap();
673 let fd = tmp2.as_raw_fd();
674 let mut path = PathBuf::new();
675 let res = fcntl(fd, FcntlArg::F_KINFO(&mut path)).expect("get path failed");
676 assert_ne!(res, -1);
677 assert_eq!(path, tmp.path());
678 }
679
680 /// Test `Flock` and associated functions.
681 ///
682 #[cfg(not(any(target_os = "redox", target_os = "solaris")))]
683 mod test_flock {
684 use nix::fcntl::*;
685 use tempfile::NamedTempFile;
686
687 /// Verify that `Flock::lock()` correctly obtains a lock, and subsequently unlocks upon drop.
688 #[test]
lock_and_drop()689 fn lock_and_drop() {
690 // Get 2 `File` handles to same underlying file.
691 let file1 = NamedTempFile::new().unwrap();
692 let file2 = file1.reopen().unwrap();
693 let file1 = file1.into_file();
694
695 // Lock first handle
696 let lock1 = Flock::lock(file1, FlockArg::LockExclusive).unwrap();
697
698 // Attempt to lock second handle
699 let file2 = match Flock::lock(file2, FlockArg::LockExclusiveNonblock) {
700 Ok(_) => panic!("Expected second exclusive lock to fail."),
701 Err((f, _)) => f,
702 };
703
704 // Drop first lock
705 std::mem::drop(lock1);
706
707 // Attempt to lock second handle again (but successfully)
708 if Flock::lock(file2, FlockArg::LockExclusiveNonblock).is_err() {
709 panic!("Expected locking to be successful.");
710 }
711 }
712
713 /// An exclusive lock can be downgraded
714 #[test]
downgrade()715 fn downgrade() {
716 let file1 = NamedTempFile::new().unwrap();
717 let file2 = file1.reopen().unwrap();
718 let file1 = file1.into_file();
719
720 // Lock first handle
721 let lock1 = Flock::lock(file1, FlockArg::LockExclusive).unwrap();
722
723 // Attempt to lock second handle
724 let file2 = Flock::lock(file2, FlockArg::LockSharedNonblock)
725 .unwrap_err()
726 .0;
727
728 // Downgrade the lock
729 lock1.relock(FlockArg::LockShared).unwrap();
730
731 // Attempt to lock second handle again (but successfully)
732 Flock::lock(file2, FlockArg::LockSharedNonblock)
733 .expect("Expected locking to be successful.");
734 }
735
736 /// Verify that `Flock::unlock()` correctly obtains unlocks.
737 #[test]
unlock()738 fn unlock() {
739 // Get 2 `File` handles to same underlying file.
740 let file1 = NamedTempFile::new().unwrap();
741 let file2 = file1.reopen().unwrap();
742 let file1 = file1.into_file();
743
744 // Lock first handle
745 let lock1 = Flock::lock(file1, FlockArg::LockExclusive).unwrap();
746
747 // Unlock and retain file so any erroneous flocks also remain present.
748 let _file1 = lock1.unlock().unwrap();
749
750 // Attempt to lock second handle.
751 if Flock::lock(file2, FlockArg::LockExclusiveNonblock).is_err() {
752 panic!("Expected locking to be successful.");
753 }
754 }
755
756 /// A shared lock can be upgraded
757 #[test]
upgrade()758 fn upgrade() {
759 let file1 = NamedTempFile::new().unwrap();
760 let file2 = file1.reopen().unwrap();
761 let file3 = file1.reopen().unwrap();
762 let file1 = file1.into_file();
763
764 // Lock first handle
765 let lock1 = Flock::lock(file1, FlockArg::LockShared).unwrap();
766
767 // Attempt to lock second handle
768 {
769 Flock::lock(file2, FlockArg::LockSharedNonblock)
770 .expect("Locking should've succeeded");
771 }
772
773 // Upgrade the lock
774 lock1.relock(FlockArg::LockExclusive).unwrap();
775
776 // Acquiring an additional shared lock should fail
777 Flock::lock(file3, FlockArg::LockSharedNonblock)
778 .expect_err("Should not have been able to lock the file");
779 }
780 }
781