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