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