1#[cfg(not(target_os = "redox"))]
2use nix::errno::*;
3#[cfg(not(target_os = "redox"))]
4use nix::fcntl::{open, readlink, OFlag};
5#[cfg(not(target_os = "redox"))]
6use nix::fcntl::{openat, readlinkat, renameat};
7#[cfg(all(
8 target_os = "linux",
9 target_env = "gnu",
10 any(
11 target_arch = "x86_64",
12 target_arch = "x32",
13 target_arch = "powerpc",
14 target_arch = "s390x"
15 )
16))]
17use nix::fcntl::{renameat2, RenameFlags};
18#[cfg(not(target_os = "redox"))]
19use nix::sys::stat::Mode;
20#[cfg(not(target_os = "redox"))]
21use nix::unistd::{close, read};
22#[cfg(not(target_os = "redox"))]
23use std::fs::File;
24#[cfg(not(target_os = "redox"))]
25use std::io::prelude::*;
26#[cfg(not(target_os = "redox"))]
27use std::os::unix::fs;
28#[cfg(not(target_os = "redox"))]
29use tempfile::NamedTempFile;
30
31#[test]
32#[cfg(not(target_os = "redox"))]
33// QEMU does not handle openat well enough to satisfy this test
34// https://gitlab.com/qemu-project/qemu/-/issues/829
35#[cfg_attr(qemu, ignore)]
36fn test_openat() {
37 const CONTENTS: &[u8] = b"abcd";
38 let mut tmp = NamedTempFile::new().unwrap();
39 tmp.write_all(CONTENTS).unwrap();
40
41 let dirfd =
42 open(tmp.path().parent().unwrap(), OFlag::empty(), Mode::empty())
43 .unwrap();
44 let fd = openat(
45 dirfd,
46 tmp.path().file_name().unwrap(),
47 OFlag::O_RDONLY,
48 Mode::empty(),
49 )
50 .unwrap();
51
52 let mut buf = [0u8; 1024];
53 assert_eq!(4, read(fd, &mut buf).unwrap());
54 assert_eq!(CONTENTS, &buf[0..4]);
55
56 close(fd).unwrap();
57 close(dirfd).unwrap();
58}
59
60#[test]
61#[cfg(not(target_os = "redox"))]
62fn test_renameat() {
63 let old_dir = tempfile::tempdir().unwrap();
64 let old_dirfd =
65 open(old_dir.path(), OFlag::empty(), Mode::empty()).unwrap();
66 let old_path = old_dir.path().join("old");
67 File::create(old_path).unwrap();
68 let new_dir = tempfile::tempdir().unwrap();
69 let new_dirfd =
70 open(new_dir.path(), OFlag::empty(), Mode::empty()).unwrap();
71 renameat(Some(old_dirfd), "old", Some(new_dirfd), "new").unwrap();
72 assert_eq!(
73 renameat(Some(old_dirfd), "old", Some(new_dirfd), "new").unwrap_err(),
74 Errno::ENOENT
75 );
76 close(old_dirfd).unwrap();
77 close(new_dirfd).unwrap();
78 assert!(new_dir.path().join("new").exists());
79}
80
81#[test]
82#[cfg(all(
83 target_os = "linux",
84 target_env = "gnu",
85 any(
86 target_arch = "x86_64",
87 target_arch = "x32",
88 target_arch = "powerpc",
89 target_arch = "s390x"
90 )
91))]
92fn test_renameat2_behaves_like_renameat_with_no_flags() {
93 let old_dir = tempfile::tempdir().unwrap();
94 let old_dirfd =
95 open(old_dir.path(), OFlag::empty(), Mode::empty()).unwrap();
96 let old_path = old_dir.path().join("old");
97 File::create(old_path).unwrap();
98 let new_dir = tempfile::tempdir().unwrap();
99 let new_dirfd =
100 open(new_dir.path(), OFlag::empty(), Mode::empty()).unwrap();
101 renameat2(
102 Some(old_dirfd),
103 "old",
104 Some(new_dirfd),
105 "new",
106 RenameFlags::empty(),
107 )
108 .unwrap();
109 assert_eq!(
110 renameat2(
111 Some(old_dirfd),
112 "old",
113 Some(new_dirfd),
114 "new",
115 RenameFlags::empty()
116 )
117 .unwrap_err(),
118 Errno::ENOENT
119 );
120 close(old_dirfd).unwrap();
121 close(new_dirfd).unwrap();
122 assert!(new_dir.path().join("new").exists());
123}
124
125#[test]
126#[cfg(all(
127 target_os = "linux",
128 target_env = "gnu",
129 any(
130 target_arch = "x86_64",
131 target_arch = "x32",
132 target_arch = "powerpc",
133 target_arch = "s390x"
134 )
135))]
136fn test_renameat2_exchange() {
137 let old_dir = tempfile::tempdir().unwrap();
138 let old_dirfd =
139 open(old_dir.path(), OFlag::empty(), Mode::empty()).unwrap();
140 let old_path = old_dir.path().join("old");
141 {
142 let mut old_f = File::create(&old_path).unwrap();
143 old_f.write_all(b"old").unwrap();
144 }
145 let new_dir = tempfile::tempdir().unwrap();
146 let new_dirfd =
147 open(new_dir.path(), OFlag::empty(), Mode::empty()).unwrap();
148 let new_path = new_dir.path().join("new");
149 {
150 let mut new_f = File::create(&new_path).unwrap();
151 new_f.write_all(b"new").unwrap();
152 }
153 renameat2(
154 Some(old_dirfd),
155 "old",
156 Some(new_dirfd),
157 "new",
158 RenameFlags::RENAME_EXCHANGE,
159 )
160 .unwrap();
161 let mut buf = String::new();
162 let mut new_f = File::open(&new_path).unwrap();
163 new_f.read_to_string(&mut buf).unwrap();
164 assert_eq!(buf, "old");
165 buf = "".to_string();
166 let mut old_f = File::open(&old_path).unwrap();
167 old_f.read_to_string(&mut buf).unwrap();
168 assert_eq!(buf, "new");
169 close(old_dirfd).unwrap();
170 close(new_dirfd).unwrap();
171}
172
173#[test]
174#[cfg(all(
175 target_os = "linux",
176 target_env = "gnu",
177 any(
178 target_arch = "x86_64",
179 target_arch = "x32",
180 target_arch = "powerpc",
181 target_arch = "s390x"
182 )
183))]
184fn test_renameat2_noreplace() {
185 let old_dir = tempfile::tempdir().unwrap();
186 let old_dirfd =
187 open(old_dir.path(), OFlag::empty(), Mode::empty()).unwrap();
188 let old_path = old_dir.path().join("old");
189 File::create(old_path).unwrap();
190 let new_dir = tempfile::tempdir().unwrap();
191 let new_dirfd =
192 open(new_dir.path(), OFlag::empty(), Mode::empty()).unwrap();
193 let new_path = new_dir.path().join("new");
194 File::create(new_path).unwrap();
195 assert_eq!(
196 renameat2(
197 Some(old_dirfd),
198 "old",
199 Some(new_dirfd),
200 "new",
201 RenameFlags::RENAME_NOREPLACE
202 )
203 .unwrap_err(),
204 Errno::EEXIST
205 );
206 close(old_dirfd).unwrap();
207 close(new_dirfd).unwrap();
208 assert!(new_dir.path().join("new").exists());
209 assert!(old_dir.path().join("old").exists());
210}
211
212#[test]
213#[cfg(not(target_os = "redox"))]
214fn test_readlink() {
215 let tempdir = tempfile::tempdir().unwrap();
216 let src = tempdir.path().join("a");
217 let dst = tempdir.path().join("b");
218 println!("a: {:?}, b: {:?}", &src, &dst);
219 fs::symlink(src.as_path(), dst.as_path()).unwrap();
220 let dirfd = open(tempdir.path(), OFlag::empty(), Mode::empty()).unwrap();
221 let expected_dir = src.to_str().unwrap();
222
223 assert_eq!(readlink(&dst).unwrap().to_str().unwrap(), expected_dir);
224 assert_eq!(
225 readlinkat(dirfd, "b").unwrap().to_str().unwrap(),
226 expected_dir
227 );
228}
229
230/// This test creates a temporary file containing the contents
231/// 'foobarbaz' and uses the `copy_file_range` call to transfer
232/// 3 bytes at offset 3 (`bar`) to another empty file at offset 0. The
233/// resulting file is read and should contain the contents `bar`.
234/// The from_offset should be updated by the call to reflect
235/// the 3 bytes read (6).
236#[cfg(any(
237 target_os = "linux",
238 // Not available until FreeBSD 13.0
239 all(target_os = "freebsd", fbsd14),
240 target_os = "android"
241))]
242#[test]
243// QEMU does not support copy_file_range. Skip under qemu
244#[cfg_attr(qemu, ignore)]
245fn test_copy_file_range() {
246 use nix::fcntl::copy_file_range;
247 use std::os::unix::io::AsFd;
248
249 const CONTENTS: &[u8] = b"foobarbaz";
250
251 let mut tmp1 = tempfile::tempfile().unwrap();
252 let mut tmp2 = tempfile::tempfile().unwrap();
253
254 tmp1.write_all(CONTENTS).unwrap();
255 tmp1.flush().unwrap();
256
257 let mut from_offset: i64 = 3;
258 copy_file_range(
259 tmp1.as_fd(),
260 Some(&mut from_offset),
261 tmp2.as_fd(),
262 None,
263 3,
264 )
265 .unwrap();
266
267 let mut res: String = String::new();
268 tmp2.rewind().unwrap();
269 tmp2.read_to_string(&mut res).unwrap();
270
271 assert_eq!(res, String::from("bar"));
272 assert_eq!(from_offset, 6);
273}
274
275#[cfg(any(target_os = "linux", target_os = "android"))]
276mod linux_android {
277 use libc::loff_t;
278 use std::io::prelude::*;
279 use std::io::IoSlice;
280 use std::os::unix::prelude::*;
281
282 use nix::fcntl::*;
283 use nix::unistd::{close, pipe, read, write};
284
285 use tempfile::tempfile;
286 #[cfg(target_os = "linux")]
287 use tempfile::NamedTempFile;
288
289 use crate::*;
290
291 #[test]
292 fn test_splice() {
293 const CONTENTS: &[u8] = b"abcdef123456";
294 let mut tmp = tempfile().unwrap();
295 tmp.write_all(CONTENTS).unwrap();
296
297 let (rd, wr) = pipe().unwrap();
298 let mut offset: loff_t = 5;
299 let res = splice(
300 tmp.as_raw_fd(),
301 Some(&mut offset),
302 wr,
303 None,
304 2,
305 SpliceFFlags::empty(),
306 )
307 .unwrap();
308
309 assert_eq!(2, res);
310
311 let mut buf = [0u8; 1024];
312 assert_eq!(2, read(rd, &mut buf).unwrap());
313 assert_eq!(b"f1", &buf[0..2]);
314 assert_eq!(7, offset);
315
316 close(rd).unwrap();
317 close(wr).unwrap();
318 }
319
320 #[test]
321 fn test_tee() {
322 let (rd1, wr1) = pipe().unwrap();
323 let (rd2, wr2) = pipe().unwrap();
324
325 write(wr1, b"abc").unwrap();
326 let res = tee(rd1, wr2, 2, SpliceFFlags::empty()).unwrap();
327
328 assert_eq!(2, res);
329
330 let mut buf = [0u8; 1024];
331
332 // Check the tee'd bytes are at rd2.
333 assert_eq!(2, read(rd2, &mut buf).unwrap());
334 assert_eq!(b"ab", &buf[0..2]);
335
336 // Check all the bytes are still at rd1.
337 assert_eq!(3, read(rd1, &mut buf).unwrap());
338 assert_eq!(b"abc", &buf[0..3]);
339
340 close(rd1).unwrap();
341 close(wr1).unwrap();
342 close(rd2).unwrap();
343 close(wr2).unwrap();
344 }
345
346 #[test]
347 fn test_vmsplice() {
348 let (rd, wr) = pipe().unwrap();
349
350 let buf1 = b"abcdef";
351 let buf2 = b"defghi";
352 let iovecs = [IoSlice::new(&buf1[0..3]), IoSlice::new(&buf2[0..3])];
353
354 let res = vmsplice(wr, &iovecs[..], SpliceFFlags::empty()).unwrap();
355
356 assert_eq!(6, res);
357
358 // Check the bytes can be read at rd.
359 let mut buf = [0u8; 32];
360 assert_eq!(6, read(rd, &mut buf).unwrap());
361 assert_eq!(b"abcdef", &buf[0..6]);
362
363 close(rd).unwrap();
364 close(wr).unwrap();
365 }
366
367 #[cfg(target_os = "linux")]
368 #[test]
369 fn test_fallocate() {
370 let tmp = NamedTempFile::new().unwrap();
371
372 let fd = tmp.as_raw_fd();
373 fallocate(fd, FallocateFlags::empty(), 0, 100).unwrap();
374
375 // Check if we read exactly 100 bytes
376 let mut buf = [0u8; 200];
377 assert_eq!(100, read(fd, &mut buf).unwrap());
378 }
379
380 // The tests below are disabled for the listed targets
381 // due to OFD locks not being available in the kernel/libc
382 // versions used in the CI environment, probably because
383 // they run under QEMU.
384
385 #[test]
386 #[cfg(all(target_os = "linux", not(target_env = "musl")))]
387 #[cfg_attr(target_env = "uclibc", ignore)] // uclibc doesn't support OFD locks, but the test should still compile
388 fn test_ofd_write_lock() {
389 use nix::sys::stat::fstat;
390 use std::mem;
391
392 let tmp = NamedTempFile::new().unwrap();
393
394 let fd = tmp.as_raw_fd();
395 let statfs = nix::sys::statfs::fstatfs(tmp.as_file()).unwrap();
396 if statfs.filesystem_type() == nix::sys::statfs::OVERLAYFS_SUPER_MAGIC {
397 // OverlayFS is a union file system. It returns one inode value in
398 // stat(2), but a different one shows up in /proc/locks. So we must
399 // skip the test.
400 skip!("/proc/locks does not work on overlayfs");
401 }
402 let inode = fstat(fd).expect("fstat failed").st_ino as usize;
403
404 let mut flock: libc::flock = unsafe {
405 mem::zeroed() // required for Linux/mips
406 };
407 flock.l_type = libc::F_WRLCK as libc::c_short;
408 flock.l_whence = libc::SEEK_SET as libc::c_short;
409 flock.l_start = 0;
410 flock.l_len = 0;
411 flock.l_pid = 0;
412 fcntl(fd, FcntlArg::F_OFD_SETLKW(&flock)).expect("write lock failed");
413 assert_eq!(
414 Some(("OFDLCK".to_string(), "WRITE".to_string())),
415 lock_info(inode)
416 );
417
418 flock.l_type = libc::F_UNLCK as libc::c_short;
419 fcntl(fd, FcntlArg::F_OFD_SETLKW(&flock)).expect("write unlock failed");
420 assert_eq!(None, lock_info(inode));
421 }
422
423 #[test]
424 #[cfg(all(target_os = "linux", not(target_env = "musl")))]
425 #[cfg_attr(target_env = "uclibc", ignore)] // uclibc doesn't support OFD locks, but the test should still compile
426 fn test_ofd_read_lock() {
427 use nix::sys::stat::fstat;
428 use std::mem;
429
430 let tmp = NamedTempFile::new().unwrap();
431
432 let fd = tmp.as_raw_fd();
433 let statfs = nix::sys::statfs::fstatfs(tmp.as_file()).unwrap();
434 if statfs.filesystem_type() == nix::sys::statfs::OVERLAYFS_SUPER_MAGIC {
435 // OverlayFS is a union file system. It returns one inode value in
436 // stat(2), but a different one shows up in /proc/locks. So we must
437 // skip the test.
438 skip!("/proc/locks does not work on overlayfs");
439 }
440 let inode = fstat(fd).expect("fstat failed").st_ino as usize;
441
442 let mut flock: libc::flock = unsafe {
443 mem::zeroed() // required for Linux/mips
444 };
445 flock.l_type = libc::F_RDLCK as libc::c_short;
446 flock.l_whence = libc::SEEK_SET as libc::c_short;
447 flock.l_start = 0;
448 flock.l_len = 0;
449 flock.l_pid = 0;
450 fcntl(fd, FcntlArg::F_OFD_SETLKW(&flock)).expect("read lock failed");
451 assert_eq!(
452 Some(("OFDLCK".to_string(), "READ".to_string())),
453 lock_info(inode)
454 );
455
456 flock.l_type = libc::F_UNLCK as libc::c_short;
457 fcntl(fd, FcntlArg::F_OFD_SETLKW(&flock)).expect("read unlock failed");
458 assert_eq!(None, lock_info(inode));
459 }
460
461 #[cfg(all(target_os = "linux", not(target_env = "musl")))]
462 fn lock_info(inode: usize) -> Option<(String, String)> {
463 use std::{fs::File, io::BufReader};
464
465 let file = File::open("/proc/locks").expect("open /proc/locks failed");
466 let buf = BufReader::new(file);
467
468 for line in buf.lines() {
469 let line = line.unwrap();
470 let parts: Vec<_> = line.split_whitespace().collect();
471 let lock_type = parts[1];
472 let lock_access = parts[3];
473 let ino_parts: Vec<_> = parts[5].split(':').collect();
474 let ino: usize = ino_parts[2].parse().unwrap();
475 if ino == inode {
476 return Some((lock_type.to_string(), lock_access.to_string()));
477 }
478 }
479 None
480 }
481}
482
483#[cfg(any(
484 target_os = "linux",
485 target_os = "android",
486 target_os = "emscripten",
487 target_os = "fuchsia",
488 target_os = "wasi",
489 target_env = "uclibc",
490 target_os = "freebsd"
491))]
492mod test_posix_fadvise {
493
494 use nix::errno::Errno;
495 use nix::fcntl::*;
496 use nix::unistd::pipe;
497 use std::os::unix::io::{AsRawFd, RawFd};
498 use tempfile::NamedTempFile;
499
500 #[test]
501 fn test_success() {
502 let tmp = NamedTempFile::new().unwrap();
503 let fd = tmp.as_raw_fd();
504 posix_fadvise(fd, 0, 100, PosixFadviseAdvice::POSIX_FADV_WILLNEED)
505 .expect("posix_fadvise failed");
506 }
507
508 #[test]
509 fn test_errno() {
510 let (rd, _wr) = pipe().unwrap();
511 let res = posix_fadvise(
512 rd as RawFd,
513 0,
514 100,
515 PosixFadviseAdvice::POSIX_FADV_WILLNEED,
516 );
517 assert_eq!(res, Err(Errno::ESPIPE));
518 }
519}
520
521#[cfg(any(
522 target_os = "linux",
523 target_os = "android",
524 target_os = "dragonfly",
525 target_os = "emscripten",
526 target_os = "fuchsia",
527 target_os = "wasi",
528 target_os = "freebsd"
529))]
530mod test_posix_fallocate {
531
532 use nix::errno::Errno;
533 use nix::fcntl::*;
534 use nix::unistd::pipe;
535 use std::{
536 io::Read,
537 os::unix::io::{AsRawFd, RawFd},
538 };
539 use tempfile::NamedTempFile;
540
541 #[test]
542 fn success() {
543 const LEN: usize = 100;
544 let mut tmp = NamedTempFile::new().unwrap();
545 let fd = tmp.as_raw_fd();
546 let res = posix_fallocate(fd, 0, LEN as libc::off_t);
547 match res {
548 Ok(_) => {
549 let mut data = [1u8; LEN];
550 assert_eq!(tmp.read(&mut data).expect("read failure"), LEN);
551 assert_eq!(&data[..], &[0u8; LEN][..]);
552 }
553 Err(Errno::EINVAL) => {
554 // POSIX requires posix_fallocate to return EINVAL both for
555 // invalid arguments (i.e. len < 0) and if the operation is not
556 // supported by the file system.
557 // There's no way to tell for sure whether the file system
558 // supports posix_fallocate, so we must pass the test if it
559 // returns EINVAL.
560 }
561 _ => res.unwrap(),
562 }
563 }
564
565 #[test]
566 fn errno() {
567 let (rd, _wr) = pipe().unwrap();
568 let err = posix_fallocate(rd as RawFd, 0, 100).unwrap_err();
569 match err {
570 Errno::EINVAL | Errno::ENODEV | Errno::ESPIPE | Errno::EBADF => (),
571 errno => panic!("unexpected errno {errno}",),
572 }
573 }
574}
575