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