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