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