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