1 | //! POSIX-style `*at` functions. |
2 | //! |
3 | //! The `dirfd` argument to these functions may be a file descriptor for a |
4 | //! directory, or the special value returned by [`cwd`]. |
5 | //! |
6 | //! [`cwd`]: crate::fs::cwd::cwd |
7 | |
8 | use crate::fd::OwnedFd; |
9 | use crate::ffi::{CStr, CString}; |
10 | #[cfg (apple)] |
11 | use crate::fs::CloneFlags; |
12 | #[cfg (not(any(apple, target_os = "wasi" )))] |
13 | use crate::fs::FileType; |
14 | #[cfg (linux_kernel)] |
15 | use crate::fs::RenameFlags; |
16 | use crate::fs::{Access, AtFlags, Mode, OFlags, Stat, Timestamps}; |
17 | use crate::path::SMALL_PATH_BUFFER_SIZE; |
18 | #[cfg (not(target_os = "wasi" ))] |
19 | use crate::process::{Gid, Uid}; |
20 | use crate::{backend, io, path}; |
21 | use alloc::vec::Vec; |
22 | use backend::fd::{AsFd, BorrowedFd}; |
23 | use backend::time::types::Nsecs; |
24 | |
25 | pub use backend::fs::types::{Dev, RawMode}; |
26 | |
27 | /// `UTIME_NOW` for use with [`utimensat`]. |
28 | /// |
29 | /// [`utimensat`]: crate::fs::utimensat |
30 | #[cfg (not(target_os = "redox" ))] |
31 | pub const UTIME_NOW: Nsecs = backend::c::UTIME_NOW as Nsecs; |
32 | |
33 | /// `UTIME_OMIT` for use with [`utimensat`]. |
34 | /// |
35 | /// [`utimensat`]: crate::fs::utimensat |
36 | #[cfg (not(target_os = "redox" ))] |
37 | pub const UTIME_OMIT: Nsecs = backend::c::UTIME_OMIT as Nsecs; |
38 | |
39 | /// `openat(dirfd, path, oflags, mode)`—Opens a file. |
40 | /// |
41 | /// POSIX guarantees that `openat` will use the lowest unused file descriptor, |
42 | /// however it is not safe in general to rely on this, as file descriptors may |
43 | /// be unexpectedly allocated on other threads or in libraries. |
44 | /// |
45 | /// The `Mode` argument is only significant when creating a file. |
46 | /// |
47 | /// # References |
48 | /// - [POSIX] |
49 | /// - [Linux] |
50 | /// |
51 | /// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/openat.html |
52 | /// [Linux]: https://man7.org/linux/man-pages/man2/openat.2.html |
53 | #[inline ] |
54 | pub fn openat<P: path::Arg, Fd: AsFd>( |
55 | dirfd: Fd, |
56 | path: P, |
57 | oflags: OFlags, |
58 | create_mode: Mode, |
59 | ) -> io::Result<OwnedFd> { |
60 | path.into_with_c_str(|path: &CStr| { |
61 | backend::fs::syscalls::openat(dirfd:dirfd.as_fd(), filename:path, flags:oflags, create_mode) |
62 | }) |
63 | } |
64 | |
65 | /// `readlinkat(fd, path)`—Reads the contents of a symlink. |
66 | /// |
67 | /// If `reuse` is non-empty, reuse its buffer to store the result if possible. |
68 | /// |
69 | /// # References |
70 | /// - [POSIX] |
71 | /// - [Linux] |
72 | /// |
73 | /// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/readlinkat.html |
74 | /// [Linux]: https://man7.org/linux/man-pages/man2/readlinkat.2.html |
75 | #[inline ] |
76 | pub fn readlinkat<P: path::Arg, Fd: AsFd, B: Into<Vec<u8>>>( |
77 | dirfd: Fd, |
78 | path: P, |
79 | reuse: B, |
80 | ) -> io::Result<CString> { |
81 | path.into_with_c_str(|path: &CStr| _readlinkat(dirfd:dirfd.as_fd(), path, buffer:reuse.into())) |
82 | } |
83 | |
84 | fn _readlinkat(dirfd: BorrowedFd<'_>, path: &CStr, mut buffer: Vec<u8>) -> io::Result<CString> { |
85 | // This code would benefit from having a better way to read into |
86 | // uninitialized memory, but that requires `unsafe`. |
87 | buffer.clear(); |
88 | buffer.reserve(SMALL_PATH_BUFFER_SIZE); |
89 | buffer.resize(new_len:buffer.capacity(), value:0_u8); |
90 | |
91 | loop { |
92 | let nread: usize = backend::fs::syscalls::readlinkat(dirfd:dirfd.as_fd(), path, &mut buffer)?; |
93 | |
94 | let nread: usize = nread as usize; |
95 | assert!(nread <= buffer.len()); |
96 | if nread < buffer.len() { |
97 | buffer.resize(new_len:nread, value:0_u8); |
98 | return Ok(CString::new(buffer).unwrap()); |
99 | } |
100 | buffer.reserve(additional:1); // use `Vec` reallocation strategy to grow capacity exponentially |
101 | buffer.resize(new_len:buffer.capacity(), value:0_u8); |
102 | } |
103 | } |
104 | |
105 | /// `mkdirat(fd, path, mode)`—Creates a directory. |
106 | /// |
107 | /// # References |
108 | /// - [POSIX] |
109 | /// - [Linux] |
110 | /// |
111 | /// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/mkdirat.html |
112 | /// [Linux]: https://man7.org/linux/man-pages/man2/mkdirat.2.html |
113 | #[inline ] |
114 | pub fn mkdirat<P: path::Arg, Fd: AsFd>(dirfd: Fd, path: P, mode: Mode) -> io::Result<()> { |
115 | path.into_with_c_str(|path: &CStr| backend::fs::syscalls::mkdirat(dirfd:dirfd.as_fd(), pathname:path, mode)) |
116 | } |
117 | |
118 | /// `linkat(old_dirfd, old_path, new_dirfd, new_path, flags)`—Creates a hard |
119 | /// link. |
120 | /// |
121 | /// # References |
122 | /// - [POSIX] |
123 | /// - [Linux] |
124 | /// |
125 | /// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/linkat.html |
126 | /// [Linux]: https://man7.org/linux/man-pages/man2/linkat.2.html |
127 | #[inline ] |
128 | pub fn linkat<P: path::Arg, Q: path::Arg, PFd: AsFd, QFd: AsFd>( |
129 | old_dirfd: PFd, |
130 | old_path: P, |
131 | new_dirfd: QFd, |
132 | new_path: Q, |
133 | flags: AtFlags, |
134 | ) -> io::Result<()> { |
135 | old_path.into_with_c_str(|old_path: &CStr| { |
136 | new_path.into_with_c_str(|new_path: &CStr| { |
137 | backend::fs::syscalls::linkat( |
138 | old_dirfd:old_dirfd.as_fd(), |
139 | oldname:old_path, |
140 | new_dirfd:new_dirfd.as_fd(), |
141 | newname:new_path, |
142 | flags, |
143 | ) |
144 | }) |
145 | }) |
146 | } |
147 | |
148 | /// `unlinkat(fd, path, flags)`—Unlinks a file or remove a directory. |
149 | /// |
150 | /// With the [`REMOVEDIR`] flag, this removes a directory. This is in place |
151 | /// of a `rmdirat` function. |
152 | /// |
153 | /// # References |
154 | /// - [POSIX] |
155 | /// - [Linux] |
156 | /// |
157 | /// [`REMOVEDIR`]: AtFlags::REMOVEDIR |
158 | /// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/unlinkat.html |
159 | /// [Linux]: https://man7.org/linux/man-pages/man2/unlinkat.2.html |
160 | #[inline ] |
161 | pub fn unlinkat<P: path::Arg, Fd: AsFd>(dirfd: Fd, path: P, flags: AtFlags) -> io::Result<()> { |
162 | path.into_with_c_str(|path: &CStr| backend::fs::syscalls::unlinkat(dirfd:dirfd.as_fd(), pathname:path, flags)) |
163 | } |
164 | |
165 | /// `renameat(old_dirfd, old_path, new_dirfd, new_path)`—Renames a file or |
166 | /// directory. |
167 | /// |
168 | /// # References |
169 | /// - [POSIX] |
170 | /// - [Linux] |
171 | /// |
172 | /// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/renameat.html |
173 | /// [Linux]: https://man7.org/linux/man-pages/man2/renameat.2.html |
174 | #[inline ] |
175 | pub fn renameat<P: path::Arg, Q: path::Arg, PFd: AsFd, QFd: AsFd>( |
176 | old_dirfd: PFd, |
177 | old_path: P, |
178 | new_dirfd: QFd, |
179 | new_path: Q, |
180 | ) -> io::Result<()> { |
181 | old_path.into_with_c_str(|old_path: &CStr| { |
182 | new_path.into_with_c_str(|new_path: &CStr| { |
183 | backend::fs::syscalls::renameat( |
184 | old_dirfd:old_dirfd.as_fd(), |
185 | oldname:old_path, |
186 | new_dirfd:new_dirfd.as_fd(), |
187 | newname:new_path, |
188 | ) |
189 | }) |
190 | }) |
191 | } |
192 | |
193 | /// `renameat2(old_dirfd, old_path, new_dirfd, new_path, flags)`—Renames a |
194 | /// file or directory. |
195 | /// |
196 | /// # References |
197 | /// - [Linux] |
198 | /// |
199 | /// [Linux]: https://man7.org/linux/man-pages/man2/renameat2.2.html |
200 | #[cfg (linux_kernel)] |
201 | #[inline ] |
202 | #[doc (alias = "renameat2" )] |
203 | pub fn renameat_with<P: path::Arg, Q: path::Arg, PFd: AsFd, QFd: AsFd>( |
204 | old_dirfd: PFd, |
205 | old_path: P, |
206 | new_dirfd: QFd, |
207 | new_path: Q, |
208 | flags: RenameFlags, |
209 | ) -> io::Result<()> { |
210 | old_path.into_with_c_str(|old_path: &CStr| { |
211 | new_path.into_with_c_str(|new_path: &CStr| { |
212 | backend::fs::syscalls::renameat2( |
213 | old_dirfd:old_dirfd.as_fd(), |
214 | oldname:old_path, |
215 | new_dirfd:new_dirfd.as_fd(), |
216 | newname:new_path, |
217 | flags, |
218 | ) |
219 | }) |
220 | }) |
221 | } |
222 | |
223 | /// `symlinkat(old_path, new_dirfd, new_path)`—Creates a symlink. |
224 | /// |
225 | /// # References |
226 | /// - [POSIX] |
227 | /// - [Linux] |
228 | /// |
229 | /// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/symlinkat.html |
230 | /// [Linux]: https://man7.org/linux/man-pages/man2/symlinkat.2.html |
231 | #[inline ] |
232 | pub fn symlinkat<P: path::Arg, Q: path::Arg, Fd: AsFd>( |
233 | old_path: P, |
234 | new_dirfd: Fd, |
235 | new_path: Q, |
236 | ) -> io::Result<()> { |
237 | old_path.into_with_c_str(|old_path: &CStr| { |
238 | new_path.into_with_c_str(|new_path: &CStr| { |
239 | backend::fs::syscalls::symlinkat(oldname:old_path, dirfd:new_dirfd.as_fd(), newname:new_path) |
240 | }) |
241 | }) |
242 | } |
243 | |
244 | /// `fstatat(dirfd, path, flags)`—Queries metadata for a file or directory. |
245 | /// |
246 | /// [`Mode::from_raw_mode`] and [`FileType::from_raw_mode`] may be used to |
247 | /// interpret the `st_mode` field. |
248 | /// |
249 | /// # References |
250 | /// - [POSIX] |
251 | /// - [Linux] |
252 | /// |
253 | /// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/fstatat.html |
254 | /// [Linux]: https://man7.org/linux/man-pages/man2/fstatat.2.html |
255 | /// [`Mode::from_raw_mode`]: crate::fs::Mode::from_raw_mode |
256 | /// [`FileType::from_raw_mode`]: crate::fs::FileType::from_raw_mode |
257 | #[inline ] |
258 | #[doc (alias = "fstatat" )] |
259 | pub fn statat<P: path::Arg, Fd: AsFd>(dirfd: Fd, path: P, flags: AtFlags) -> io::Result<Stat> { |
260 | path.into_with_c_str(|path: &CStr| backend::fs::syscalls::statat(dirfd:dirfd.as_fd(), filename:path, flags)) |
261 | } |
262 | |
263 | /// `faccessat(dirfd, path, access, flags)`—Tests permissions for a file or |
264 | /// directory. |
265 | /// |
266 | /// On Linux before 5.8, this function uses the `faccessat` system call which |
267 | /// doesn't support any flags. This function emulates support for the |
268 | /// [`AtFlags::EACCESS`] flag by checking whether the uid and gid of the |
269 | /// process match the effective uid and gid, in which case the `EACCESS` flag |
270 | /// can be ignored. In Linux 5.8 and beyond `faccessat2` is used, which |
271 | /// supports flags. |
272 | /// |
273 | /// # References |
274 | /// - [POSIX] |
275 | /// - [Linux] |
276 | /// |
277 | /// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/faccessat.html |
278 | /// [Linux]: https://man7.org/linux/man-pages/man2/faccessat.2.html |
279 | #[inline ] |
280 | #[doc (alias = "faccessat" )] |
281 | pub fn accessat<P: path::Arg, Fd: AsFd>( |
282 | dirfd: Fd, |
283 | path: P, |
284 | access: Access, |
285 | flags: AtFlags, |
286 | ) -> io::Result<()> { |
287 | path.into_with_c_str(|path: &CStr| backend::fs::syscalls::accessat(dirfd:dirfd.as_fd(), path, access, flags)) |
288 | } |
289 | |
290 | /// `utimensat(dirfd, path, times, flags)`—Sets file or directory timestamps. |
291 | /// |
292 | /// # References |
293 | /// - [POSIX] |
294 | /// - [Linux] |
295 | /// |
296 | /// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/utimensat.html |
297 | /// [Linux]: https://man7.org/linux/man-pages/man2/utimensat.2.html |
298 | #[inline ] |
299 | pub fn utimensat<P: path::Arg, Fd: AsFd>( |
300 | dirfd: Fd, |
301 | path: P, |
302 | times: &Timestamps, |
303 | flags: AtFlags, |
304 | ) -> io::Result<()> { |
305 | path.into_with_c_str(|path: &CStr| backend::fs::syscalls::utimensat(dirfd:dirfd.as_fd(), pathname:path, times, flags)) |
306 | } |
307 | |
308 | /// `fchmodat(dirfd, path, mode, 0)`—Sets file or directory permissions. |
309 | /// |
310 | /// See `fchmodat_with` for a version that does take flags. |
311 | /// |
312 | /// # References |
313 | /// - [POSIX] |
314 | /// - [Linux] |
315 | /// |
316 | /// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/fchmodat.html |
317 | /// [Linux]: https://man7.org/linux/man-pages/man2/fchmodat.2.html |
318 | #[cfg (not(target_os = "wasi" ))] |
319 | #[inline ] |
320 | #[doc (alias = "fchmodat" )] |
321 | pub fn chmodat<P: path::Arg, Fd: AsFd>(dirfd: Fd, path: P, mode: Mode) -> io::Result<()> { |
322 | chmodat_with(dirfd, path, mode, flags:AtFlags::empty()) |
323 | } |
324 | |
325 | /// `fchmodat(dirfd, path, mode, flags)`—Sets file or directory permissions. |
326 | /// |
327 | /// Platform support for flags varies widely, for example on Linux |
328 | /// [`AtFlags::SYMLINK_NOFOLLOW`] is not implemented and therefore |
329 | /// [`io::Errno::OPNOTSUPP`] will be returned. |
330 | /// |
331 | /// # References |
332 | /// - [POSIX] |
333 | /// - [Linux] |
334 | /// |
335 | /// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/fchmodat.html |
336 | /// [Linux]: https://man7.org/linux/man-pages/man2/fchmodat.2.html |
337 | #[cfg (not(target_os = "wasi" ))] |
338 | #[inline ] |
339 | #[doc (alias = "fchmodat_with" )] |
340 | pub fn chmodat_with<P: path::Arg, Fd: AsFd>( |
341 | dirfd: Fd, |
342 | path: P, |
343 | mode: Mode, |
344 | flags: AtFlags, |
345 | ) -> io::Result<()> { |
346 | path.into_with_c_str(|path: &CStr| backend::fs::syscalls::chmodat(dirfd:dirfd.as_fd(), filename:path, mode, flags)) |
347 | } |
348 | |
349 | /// `fclonefileat(src, dst_dir, dst, flags)`—Efficiently copies between files. |
350 | /// |
351 | /// # References |
352 | /// - [Apple] |
353 | /// |
354 | /// [Apple]: https://opensource.apple.com/source/xnu/xnu-3789.21.4/bsd/man/man2/clonefile.2.auto.html |
355 | #[cfg (apple)] |
356 | #[inline ] |
357 | pub fn fclonefileat<Fd: AsFd, DstFd: AsFd, P: path::Arg>( |
358 | src: Fd, |
359 | dst_dir: DstFd, |
360 | dst: P, |
361 | flags: CloneFlags, |
362 | ) -> io::Result<()> { |
363 | dst.into_with_c_str(|dst| { |
364 | backend::fs::syscalls::fclonefileat(src.as_fd(), dst_dir.as_fd(), dst, flags) |
365 | }) |
366 | } |
367 | |
368 | /// `mknodat(dirfd, path, mode, dev)`—Creates special or normal files. |
369 | /// |
370 | /// # References |
371 | /// - [POSIX] |
372 | /// - [Linux] |
373 | /// |
374 | /// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/mknodat.html |
375 | /// [Linux]: https://man7.org/linux/man-pages/man2/mknodat.2.html |
376 | #[cfg (not(any(apple, target_os = "wasi" )))] |
377 | #[inline ] |
378 | pub fn mknodat<P: path::Arg, Fd: AsFd>( |
379 | dirfd: Fd, |
380 | path: P, |
381 | file_type: FileType, |
382 | mode: Mode, |
383 | dev: Dev, |
384 | ) -> io::Result<()> { |
385 | path.into_with_c_str(|path: &CStr| { |
386 | backend::fs::syscalls::mknodat(dirfd:dirfd.as_fd(), filename:path, file_type, mode, dev) |
387 | }) |
388 | } |
389 | |
390 | /// `fchownat(dirfd, path, owner, group, flags)`—Sets file or directory |
391 | /// ownership. |
392 | /// |
393 | /// # References |
394 | /// - [POSIX] |
395 | /// - [Linux] |
396 | /// |
397 | /// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/fchownat.html |
398 | /// [Linux]: https://man7.org/linux/man-pages/man2/fchownat.2.html |
399 | #[cfg (not(target_os = "wasi" ))] |
400 | #[inline ] |
401 | #[doc (alias = "fchownat" )] |
402 | pub fn chownat<P: path::Arg, Fd: AsFd>( |
403 | dirfd: Fd, |
404 | path: P, |
405 | owner: Option<Uid>, |
406 | group: Option<Gid>, |
407 | flags: AtFlags, |
408 | ) -> io::Result<()> { |
409 | path.into_with_c_str(|path: &CStr| { |
410 | backend::fs::syscalls::chownat(dirfd:dirfd.as_fd(), filename:path, owner, group, flags) |
411 | }) |
412 | } |
413 | |