1use rustix::fd::{AsFd, AsRawFd, OwnedFd};
2use rustix::fs::{cwd, mkdirat, openat, openat2, symlinkat, Mode, OFlags, ResolveFlags};
3use rustix::{io, path};
4
5/// Like `openat2`, but keep retrying until it fails or succeeds.
6fn openat2_more<Fd: AsFd, P: path::Arg>(
7    dirfd: Fd,
8    path: P,
9    oflags: OFlags,
10    mode: Mode,
11    resolve: ResolveFlags,
12) -> io::Result<OwnedFd> {
13    let path = path.as_cow_c_str().unwrap().into_owned();
14    loop {
15        match openat2(dirfd.as_fd(), &path, oflags, mode, resolve) {
16            Ok(file) => return Ok(file),
17            Err(io::Errno::AGAIN) => continue,
18            Err(err) => return Err(err),
19        }
20    }
21}
22
23#[test]
24fn test_openat2() {
25    let tmp = tempfile::tempdir().unwrap();
26    let dir = openat(cwd(), tmp.path(), OFlags::RDONLY, Mode::empty()).unwrap();
27
28    // Detect whether `openat2` is available.
29    match openat2(
30        &dir,
31        ".",
32        OFlags::RDONLY | OFlags::CLOEXEC,
33        Mode::empty(),
34        ResolveFlags::empty(),
35    ) {
36        Ok(_file) => (),
37        Err(io::Errno::NOSYS) => return,
38        Err(_err) => return,
39    }
40
41    // Create a file.
42    let _ = openat2_more(
43        &dir,
44        "test.txt",
45        OFlags::WRONLY | OFlags::CREATE | OFlags::TRUNC | OFlags::CLOEXEC,
46        Mode::RUSR,
47        ResolveFlags::empty(),
48    )
49    .unwrap();
50
51    // Test `NO_SYMLINKS`.
52    symlinkat("test.txt", &dir, "symlink.txt").unwrap();
53    let _ = openat2_more(
54        &dir,
55        "symlink.txt",
56        OFlags::RDONLY | OFlags::CLOEXEC,
57        Mode::empty(),
58        ResolveFlags::empty(),
59    )
60    .unwrap();
61    let _ = openat2_more(
62        &dir,
63        "symlink.txt",
64        OFlags::RDONLY | OFlags::CLOEXEC,
65        Mode::empty(),
66        ResolveFlags::NO_MAGICLINKS,
67    )
68    .unwrap();
69    let _ = openat2_more(
70        &dir,
71        "symlink.txt",
72        OFlags::RDONLY | OFlags::CLOEXEC,
73        Mode::empty(),
74        ResolveFlags::NO_SYMLINKS,
75    )
76    .unwrap_err();
77
78    // Test `NO_MAGICLINKS`.
79    let test = openat2_more(
80        &dir,
81        "test.txt",
82        OFlags::RDONLY | OFlags::CLOEXEC,
83        Mode::empty(),
84        ResolveFlags::empty(),
85    )
86    .unwrap();
87    let _ = openat2_more(
88        &dir,
89        &format!("/proc/self/fd/{}", test.as_fd().as_raw_fd()),
90        OFlags::RDONLY | OFlags::CLOEXEC,
91        Mode::empty(),
92        ResolveFlags::empty(),
93    )
94    .unwrap();
95    let _ = openat2_more(
96        &dir,
97        &format!("/proc/self/fd/{}", test.as_fd().as_raw_fd()),
98        OFlags::RDONLY | OFlags::CLOEXEC,
99        Mode::empty(),
100        ResolveFlags::NO_SYMLINKS,
101    )
102    .unwrap_err();
103    let _ = openat2_more(
104        &dir,
105        &format!("/proc/self/fd/{}", test.as_fd().as_raw_fd()),
106        OFlags::RDONLY | OFlags::CLOEXEC,
107        Mode::empty(),
108        ResolveFlags::NO_MAGICLINKS,
109    )
110    .unwrap_err();
111
112    // Test `NO_XDEV`.
113    let root = openat2_more(
114        &dir,
115        "/",
116        OFlags::RDONLY | OFlags::CLOEXEC,
117        Mode::empty(),
118        ResolveFlags::empty(),
119    )
120    .unwrap();
121    let _ = openat2_more(
122        &root,
123        "proc",
124        OFlags::RDONLY | OFlags::CLOEXEC,
125        Mode::empty(),
126        ResolveFlags::empty(),
127    )
128    .unwrap();
129    let _ = openat2_more(
130        &root,
131        "proc",
132        OFlags::RDONLY | OFlags::CLOEXEC,
133        Mode::empty(),
134        ResolveFlags::NO_XDEV,
135    )
136    .unwrap_err();
137
138    // Test `BENEATH`.
139    let _ = openat2_more(
140        &dir,
141        "..",
142        OFlags::RDONLY | OFlags::CLOEXEC,
143        Mode::empty(),
144        ResolveFlags::empty(),
145    )
146    .unwrap();
147    let _ = openat2_more(
148        &dir,
149        "..",
150        OFlags::RDONLY | OFlags::CLOEXEC,
151        Mode::empty(),
152        ResolveFlags::BENEATH,
153    )
154    .unwrap_err();
155
156    // Test `IN_ROOT`.
157    let _ = openat2_more(
158        &dir,
159        "/proc",
160        OFlags::RDONLY | OFlags::CLOEXEC,
161        Mode::empty(),
162        ResolveFlags::empty(),
163    )
164    .unwrap();
165    let _ = openat2_more(
166        &dir,
167        "/proc",
168        OFlags::RDONLY | OFlags::CLOEXEC,
169        Mode::empty(),
170        ResolveFlags::IN_ROOT,
171    )
172    .unwrap_err();
173    mkdirat(&dir, "proc", Mode::RUSR | Mode::XUSR).unwrap();
174    let _ = openat2_more(
175        &dir,
176        "/proc",
177        OFlags::RDONLY | OFlags::CLOEXEC,
178        Mode::empty(),
179        ResolveFlags::IN_ROOT,
180    )
181    .unwrap();
182}
183