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