1#[cfg(any(target_os = "android", target_os = "linux"))]
2use rustix::fs::Stat;
3
4#[cfg(any(target_os = "android", target_os = "linux"))]
5fn same(a: &Stat, b: &Stat) -> bool {
6    a.st_ino == b.st_ino && a.st_dev == b.st_dev
7}
8
9#[cfg(any(target_os = "android", target_os = "linux"))]
10#[test]
11fn test_renameat() {
12    use rustix::fs::{cwd, openat, renameat, statat, AtFlags, Mode, OFlags};
13
14    let tmp = tempfile::tempdir().unwrap();
15    let dir = openat(
16        cwd(),
17        tmp.path(),
18        OFlags::RDONLY | OFlags::PATH,
19        Mode::empty(),
20    )
21    .unwrap();
22
23    let _ = openat(&dir, "foo", OFlags::CREATE | OFlags::WRONLY, Mode::empty()).unwrap();
24    let before = statat(&dir, "foo", AtFlags::empty()).unwrap();
25    renameat(&dir, "foo", &dir, "bar").unwrap();
26    let renamed = statat(&dir, "bar", AtFlags::empty()).unwrap();
27    assert!(same(&before, &renamed));
28}
29
30/// Like `test_renameat` but the file already exists, so `renameat`
31/// overwrites it.
32#[cfg(any(target_os = "android", target_os = "linux"))]
33#[test]
34fn test_renameat_overwrite() {
35    use rustix::fs::{cwd, openat, renameat, statat, AtFlags, Mode, OFlags};
36
37    let tmp = tempfile::tempdir().unwrap();
38    let dir = openat(
39        cwd(),
40        tmp.path(),
41        OFlags::RDONLY | OFlags::PATH,
42        Mode::empty(),
43    )
44    .unwrap();
45
46    let _ = openat(&dir, "foo", OFlags::CREATE | OFlags::WRONLY, Mode::empty()).unwrap();
47    let _ = openat(&dir, "bar", OFlags::CREATE | OFlags::WRONLY, Mode::empty()).unwrap();
48    let before = statat(&dir, "foo", AtFlags::empty()).unwrap();
49    renameat(&dir, "foo", &dir, "bar").unwrap();
50    let renamed = statat(&dir, "bar", AtFlags::empty()).unwrap();
51    assert!(same(&before, &renamed));
52}
53
54#[cfg(any(target_os = "android", target_os = "linux"))]
55#[test]
56fn test_renameat_with() {
57    use rustix::fs::{cwd, openat, renameat_with, statat, AtFlags, Mode, OFlags, RenameFlags};
58
59    let tmp = tempfile::tempdir().unwrap();
60    let dir = openat(
61        cwd(),
62        tmp.path(),
63        OFlags::RDONLY | OFlags::PATH,
64        Mode::empty(),
65    )
66    .unwrap();
67
68    let _ = openat(&dir, "foo", OFlags::CREATE | OFlags::WRONLY, Mode::empty()).unwrap();
69    let before = statat(&dir, "foo", AtFlags::empty()).unwrap();
70
71    match renameat_with(&dir, "foo", &dir, "red", RenameFlags::empty()) {
72        Ok(()) => (),
73        Err(err) if err == rustix::io::Errno::NOSYS => return,
74        Err(err) => unreachable!("unexpected error from renameat_with: {:?}", err),
75    }
76
77    let renamed = statat(&dir, "red", AtFlags::empty()).unwrap();
78    assert!(same(&before, &renamed));
79
80    let _ = openat(
81        &dir,
82        "green",
83        OFlags::CREATE | OFlags::WRONLY,
84        Mode::empty(),
85    )
86    .unwrap();
87
88    #[cfg(all(target_os = "linux", target_env = "gnu"))]
89    {
90        let green = statat(&dir, "green", AtFlags::empty()).unwrap();
91
92        renameat_with(&dir, "red", &dir, "green", RenameFlags::NOREPLACE).unwrap_err();
93        let renamed = statat(&dir, "red", AtFlags::empty()).unwrap();
94        assert!(same(&before, &renamed));
95        let orig = statat(&dir, "green", AtFlags::empty()).unwrap();
96        assert!(same(&green, &orig));
97
98        renameat_with(&dir, "red", &dir, "green", RenameFlags::EXCHANGE).unwrap();
99        let renamed = statat(&dir, "red", AtFlags::empty()).unwrap();
100        assert!(same(&green, &renamed));
101        let orig = statat(&dir, "green", AtFlags::empty()).unwrap();
102        assert!(same(&before, &orig));
103    }
104}
105