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