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 [`CWD`].
5 //!
6 //! [`cwd`]: crate::fs::CWD
7 
8 use crate::fd::OwnedFd;
9 use crate::ffi::CStr;
10 #[cfg(not(any(target_os = "espidf", target_os = "vita")))]
11 use crate::fs::Access;
12 #[cfg(not(target_os = "espidf"))]
13 use crate::fs::AtFlags;
14 #[cfg(apple)]
15 use crate::fs::CloneFlags;
16 #[cfg(linux_kernel)]
17 use crate::fs::RenameFlags;
18 #[cfg(not(target_os = "espidf"))]
19 use crate::fs::Stat;
20 #[cfg(not(any(apple, target_os = "espidf", target_os = "vita", target_os = "wasi")))]
21 use crate::fs::{Dev, FileType};
22 #[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
23 use crate::fs::{Gid, Uid};
24 use crate::fs::{Mode, OFlags};
25 use crate::{backend, io, path};
26 use backend::fd::{AsFd, BorrowedFd};
27 use core::mem::MaybeUninit;
28 use core::slice;
29 #[cfg(feature = "alloc")]
30 use {crate::ffi::CString, crate::path::SMALL_PATH_BUFFER_SIZE, alloc::vec::Vec};
31 #[cfg(not(any(target_os = "espidf", target_os = "vita")))]
32 use {crate::fs::Timestamps, crate::timespec::Nsecs};
33 
34 /// `UTIME_NOW` for use with [`utimensat`].
35 ///
36 /// [`utimensat`]: crate::fs::utimensat
37 #[cfg(not(any(target_os = "espidf", target_os = "redox", target_os = "vita")))]
38 pub const UTIME_NOW: Nsecs = backend::c::UTIME_NOW as Nsecs;
39 
40 /// `UTIME_OMIT` for use with [`utimensat`].
41 ///
42 /// [`utimensat`]: crate::fs::utimensat
43 #[cfg(not(any(target_os = "espidf", target_os = "redox", target_os = "vita")))]
44 pub const UTIME_OMIT: Nsecs = backend::c::UTIME_OMIT as Nsecs;
45 
46 /// `openat(dirfd, path, oflags, mode)`—Opens a file.
47 ///
48 /// POSIX guarantees that `openat` will use the lowest unused file descriptor,
49 /// however it is not safe in general to rely on this, as file descriptors may
50 /// be unexpectedly allocated on other threads or in libraries.
51 ///
52 /// The `Mode` argument is only significant when creating a file.
53 ///
54 /// # References
55 ///  - [POSIX]
56 ///  - [Linux]
57 ///
58 /// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/openat.html
59 /// [Linux]: https://man7.org/linux/man-pages/man2/openat.2.html
60 #[inline]
openat<P: path::Arg, Fd: AsFd>( dirfd: Fd, path: P, oflags: OFlags, create_mode: Mode, ) -> io::Result<OwnedFd>61 pub fn openat<P: path::Arg, Fd: AsFd>(
62     dirfd: Fd,
63     path: P,
64     oflags: OFlags,
65     create_mode: Mode,
66 ) -> io::Result<OwnedFd> {
67     path.into_with_c_str(|path| {
68         backend::fs::syscalls::openat(dirfd.as_fd(), path, oflags, create_mode)
69     })
70 }
71 
72 /// `readlinkat(fd, path)`—Reads the contents of a symlink.
73 ///
74 /// If `reuse` already has available capacity, reuse it if possible.
75 ///
76 /// # References
77 ///  - [POSIX]
78 ///  - [Linux]
79 ///
80 /// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/readlinkat.html
81 /// [Linux]: https://man7.org/linux/man-pages/man2/readlinkat.2.html
82 #[cfg(feature = "alloc")]
83 #[inline]
readlinkat<P: path::Arg, Fd: AsFd, B: Into<Vec<u8>>>( dirfd: Fd, path: P, reuse: B, ) -> io::Result<CString>84 pub fn readlinkat<P: path::Arg, Fd: AsFd, B: Into<Vec<u8>>>(
85     dirfd: Fd,
86     path: P,
87     reuse: B,
88 ) -> io::Result<CString> {
89     path.into_with_c_str(|path| _readlinkat(dirfd.as_fd(), path, reuse.into()))
90 }
91 
92 #[cfg(feature = "alloc")]
93 #[allow(unsafe_code)]
_readlinkat(dirfd: BorrowedFd<'_>, path: &CStr, mut buffer: Vec<u8>) -> io::Result<CString>94 fn _readlinkat(dirfd: BorrowedFd<'_>, path: &CStr, mut buffer: Vec<u8>) -> io::Result<CString> {
95     buffer.clear();
96     buffer.reserve(SMALL_PATH_BUFFER_SIZE);
97 
98     loop {
99         let nread =
100             backend::fs::syscalls::readlinkat(dirfd.as_fd(), path, buffer.spare_capacity_mut())?;
101 
102         debug_assert!(nread <= buffer.capacity());
103         if nread < buffer.capacity() {
104             // SAFETY: From the [documentation]: “On success, these calls
105             // return the number of bytes placed in buf.”
106             //
107             // [documentation]: https://man7.org/linux/man-pages/man2/readlinkat.2.html
108             unsafe {
109                 buffer.set_len(nread);
110             }
111 
112             // SAFETY:
113             // - “readlink places the contents of the symbolic link pathname in
114             //   the buffer buf”
115             // - [POSIX definition 3.271: Pathname]: “A string that is used to
116             //   identify a file.”
117             // - [POSIX definition 3.375: String]: “A contiguous sequence of
118             //   bytes terminated by and including the first null byte.”
119             // - “readlink does not append a terminating null byte to buf.”
120             //
121             // Thus, there will be no NUL bytes in the string.
122             //
123             // [POSIX definition 3.271: Pathname]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_271
124             // [POSIX definition 3.375: String]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_375
125             unsafe {
126                 return Ok(CString::from_vec_unchecked(buffer));
127             }
128         }
129 
130         // Use `Vec` reallocation strategy to grow capacity exponentially.
131         buffer.reserve(buffer.capacity() + 1);
132     }
133 }
134 
135 /// `readlinkat(fd, path)`—Reads the contents of a symlink, without
136 /// allocating.
137 ///
138 /// This is the "raw" version which avoids allocating, but which is
139 /// significantly trickier to use; most users should use plain [`readlinkat`].
140 ///
141 /// This version writes bytes into the buffer and returns two slices, one
142 /// containing the written bytes, and one containing the remaining
143 /// uninitialized space. If the number of written bytes is equal to the length
144 /// of the buffer, it means the buffer wasn't big enough to hold the full
145 /// string, and callers should try again with a bigger buffer.
146 ///
147 /// # References
148 ///  - [POSIX]
149 ///  - [Linux]
150 ///
151 /// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/readlinkat.html
152 /// [Linux]: https://man7.org/linux/man-pages/man2/readlinkat.2.html
153 #[inline]
readlinkat_raw<P: path::Arg, Fd: AsFd>( dirfd: Fd, path: P, buf: &mut [MaybeUninit<u8>], ) -> io::Result<(&mut [u8], &mut [MaybeUninit<u8>])>154 pub fn readlinkat_raw<P: path::Arg, Fd: AsFd>(
155     dirfd: Fd,
156     path: P,
157     buf: &mut [MaybeUninit<u8>],
158 ) -> io::Result<(&mut [u8], &mut [MaybeUninit<u8>])> {
159     path.into_with_c_str(|path| _readlinkat_raw(dirfd.as_fd(), path, buf))
160 }
161 
162 #[allow(unsafe_code)]
_readlinkat_raw<'a>( dirfd: BorrowedFd<'_>, path: &CStr, buf: &'a mut [MaybeUninit<u8>], ) -> io::Result<(&'a mut [u8], &'a mut [MaybeUninit<u8>])>163 fn _readlinkat_raw<'a>(
164     dirfd: BorrowedFd<'_>,
165     path: &CStr,
166     buf: &'a mut [MaybeUninit<u8>],
167 ) -> io::Result<(&'a mut [u8], &'a mut [MaybeUninit<u8>])> {
168     let n = backend::fs::syscalls::readlinkat(dirfd.as_fd(), path, buf)?;
169     unsafe {
170         Ok((
171             slice::from_raw_parts_mut(buf.as_mut_ptr().cast::<u8>(), n),
172             &mut buf[n..],
173         ))
174     }
175 }
176 
177 /// `mkdirat(fd, path, mode)`—Creates a directory.
178 ///
179 /// # References
180 ///  - [POSIX]
181 ///  - [Linux]
182 ///
183 /// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/mkdirat.html
184 /// [Linux]: https://man7.org/linux/man-pages/man2/mkdirat.2.html
185 #[inline]
mkdirat<P: path::Arg, Fd: AsFd>(dirfd: Fd, path: P, mode: Mode) -> io::Result<()>186 pub fn mkdirat<P: path::Arg, Fd: AsFd>(dirfd: Fd, path: P, mode: Mode) -> io::Result<()> {
187     path.into_with_c_str(|path| backend::fs::syscalls::mkdirat(dirfd.as_fd(), path, mode))
188 }
189 
190 /// `linkat(old_dirfd, old_path, new_dirfd, new_path, flags)`—Creates a hard
191 /// link.
192 ///
193 /// # References
194 ///  - [POSIX]
195 ///  - [Linux]
196 ///
197 /// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/linkat.html
198 /// [Linux]: https://man7.org/linux/man-pages/man2/linkat.2.html
199 #[cfg(not(target_os = "espidf"))]
200 #[inline]
linkat<P: path::Arg, Q: path::Arg, PFd: AsFd, QFd: AsFd>( old_dirfd: PFd, old_path: P, new_dirfd: QFd, new_path: Q, flags: AtFlags, ) -> io::Result<()>201 pub fn linkat<P: path::Arg, Q: path::Arg, PFd: AsFd, QFd: AsFd>(
202     old_dirfd: PFd,
203     old_path: P,
204     new_dirfd: QFd,
205     new_path: Q,
206     flags: AtFlags,
207 ) -> io::Result<()> {
208     old_path.into_with_c_str(|old_path| {
209         new_path.into_with_c_str(|new_path| {
210             backend::fs::syscalls::linkat(
211                 old_dirfd.as_fd(),
212                 old_path,
213                 new_dirfd.as_fd(),
214                 new_path,
215                 flags,
216             )
217         })
218     })
219 }
220 
221 /// `unlinkat(fd, path, flags)`—Unlinks a file or remove a directory.
222 ///
223 /// With the [`REMOVEDIR`] flag, this removes a directory. This is in place of
224 /// a `rmdirat` function.
225 ///
226 /// # References
227 ///  - [POSIX]
228 ///  - [Linux]
229 ///
230 /// [`REMOVEDIR`]: AtFlags::REMOVEDIR
231 /// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/unlinkat.html
232 /// [Linux]: https://man7.org/linux/man-pages/man2/unlinkat.2.html
233 #[cfg(not(target_os = "espidf"))]
234 #[inline]
unlinkat<P: path::Arg, Fd: AsFd>(dirfd: Fd, path: P, flags: AtFlags) -> io::Result<()>235 pub fn unlinkat<P: path::Arg, Fd: AsFd>(dirfd: Fd, path: P, flags: AtFlags) -> io::Result<()> {
236     path.into_with_c_str(|path| backend::fs::syscalls::unlinkat(dirfd.as_fd(), path, flags))
237 }
238 
239 /// `renameat(old_dirfd, old_path, new_dirfd, new_path)`—Renames a file or
240 /// directory.
241 ///
242 /// # References
243 ///  - [POSIX]
244 ///  - [Linux]
245 ///
246 /// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/renameat.html
247 /// [Linux]: https://man7.org/linux/man-pages/man2/renameat.2.html
248 #[inline]
renameat<P: path::Arg, Q: path::Arg, PFd: AsFd, QFd: AsFd>( old_dirfd: PFd, old_path: P, new_dirfd: QFd, new_path: Q, ) -> io::Result<()>249 pub fn renameat<P: path::Arg, Q: path::Arg, PFd: AsFd, QFd: AsFd>(
250     old_dirfd: PFd,
251     old_path: P,
252     new_dirfd: QFd,
253     new_path: Q,
254 ) -> io::Result<()> {
255     old_path.into_with_c_str(|old_path| {
256         new_path.into_with_c_str(|new_path| {
257             backend::fs::syscalls::renameat(
258                 old_dirfd.as_fd(),
259                 old_path,
260                 new_dirfd.as_fd(),
261                 new_path,
262             )
263         })
264     })
265 }
266 
267 /// `renameat2(old_dirfd, old_path, new_dirfd, new_path, flags)`—Renames a
268 /// file or directory.
269 ///
270 /// # References
271 ///  - [Linux]
272 ///
273 /// [Linux]: https://man7.org/linux/man-pages/man2/renameat2.2.html
274 #[cfg(linux_kernel)]
275 #[inline]
276 #[doc(alias = "renameat2")]
renameat_with<P: path::Arg, Q: path::Arg, PFd: AsFd, QFd: AsFd>( old_dirfd: PFd, old_path: P, new_dirfd: QFd, new_path: Q, flags: RenameFlags, ) -> io::Result<()>277 pub fn renameat_with<P: path::Arg, Q: path::Arg, PFd: AsFd, QFd: AsFd>(
278     old_dirfd: PFd,
279     old_path: P,
280     new_dirfd: QFd,
281     new_path: Q,
282     flags: RenameFlags,
283 ) -> io::Result<()> {
284     old_path.into_with_c_str(|old_path| {
285         new_path.into_with_c_str(|new_path| {
286             backend::fs::syscalls::renameat2(
287                 old_dirfd.as_fd(),
288                 old_path,
289                 new_dirfd.as_fd(),
290                 new_path,
291                 flags,
292             )
293         })
294     })
295 }
296 
297 /// `symlinkat(old_path, new_dirfd, new_path)`—Creates a symlink.
298 ///
299 /// # References
300 ///  - [POSIX]
301 ///  - [Linux]
302 ///
303 /// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/symlinkat.html
304 /// [Linux]: https://man7.org/linux/man-pages/man2/symlinkat.2.html
305 #[inline]
symlinkat<P: path::Arg, Q: path::Arg, Fd: AsFd>( old_path: P, new_dirfd: Fd, new_path: Q, ) -> io::Result<()>306 pub fn symlinkat<P: path::Arg, Q: path::Arg, Fd: AsFd>(
307     old_path: P,
308     new_dirfd: Fd,
309     new_path: Q,
310 ) -> io::Result<()> {
311     old_path.into_with_c_str(|old_path| {
312         new_path.into_with_c_str(|new_path| {
313             backend::fs::syscalls::symlinkat(old_path, new_dirfd.as_fd(), new_path)
314         })
315     })
316 }
317 
318 /// `fstatat(dirfd, path, flags)`—Queries metadata for a file or directory.
319 ///
320 /// [`Mode::from_raw_mode`] and [`FileType::from_raw_mode`] may be used to
321 /// interpret the `st_mode` field.
322 ///
323 /// # References
324 ///  - [POSIX]
325 ///  - [Linux]
326 ///
327 /// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/fstatat.html
328 /// [Linux]: https://man7.org/linux/man-pages/man2/fstatat.2.html
329 /// [`Mode::from_raw_mode`]: crate::fs::Mode::from_raw_mode
330 /// [`FileType::from_raw_mode`]: crate::fs::FileType::from_raw_mode
331 #[cfg(not(target_os = "espidf"))]
332 #[inline]
333 #[doc(alias = "fstatat")]
statat<P: path::Arg, Fd: AsFd>(dirfd: Fd, path: P, flags: AtFlags) -> io::Result<Stat>334 pub fn statat<P: path::Arg, Fd: AsFd>(dirfd: Fd, path: P, flags: AtFlags) -> io::Result<Stat> {
335     path.into_with_c_str(|path| backend::fs::syscalls::statat(dirfd.as_fd(), path, flags))
336 }
337 
338 /// `faccessat(dirfd, path, access, flags)`—Tests permissions for a file or
339 /// directory.
340 ///
341 /// On Linux before 5.8, this function uses the `faccessat` system call which
342 /// doesn't support any flags. This function emulates support for the
343 /// [`AtFlags::EACCESS`] flag by checking whether the uid and gid of the
344 /// process match the effective uid and gid, in which case the `EACCESS` flag
345 /// can be ignored. In Linux 5.8 and beyond `faccessat2` is used, which
346 /// supports flags.
347 ///
348 /// # References
349 ///  - [POSIX]
350 ///  - [Linux]
351 ///
352 /// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/faccessat.html
353 /// [Linux]: https://man7.org/linux/man-pages/man2/faccessat.2.html
354 #[cfg(not(any(target_os = "espidf", target_os = "vita")))]
355 #[inline]
356 #[doc(alias = "faccessat")]
accessat<P: path::Arg, Fd: AsFd>( dirfd: Fd, path: P, access: Access, flags: AtFlags, ) -> io::Result<()>357 pub fn accessat<P: path::Arg, Fd: AsFd>(
358     dirfd: Fd,
359     path: P,
360     access: Access,
361     flags: AtFlags,
362 ) -> io::Result<()> {
363     path.into_with_c_str(|path| backend::fs::syscalls::accessat(dirfd.as_fd(), path, access, flags))
364 }
365 
366 /// `utimensat(dirfd, path, times, flags)`—Sets file or directory timestamps.
367 ///
368 /// # References
369 ///  - [POSIX]
370 ///  - [Linux]
371 ///
372 /// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/utimensat.html
373 /// [Linux]: https://man7.org/linux/man-pages/man2/utimensat.2.html
374 #[cfg(not(any(target_os = "espidf", target_os = "vita")))]
375 #[inline]
utimensat<P: path::Arg, Fd: AsFd>( dirfd: Fd, path: P, times: &Timestamps, flags: AtFlags, ) -> io::Result<()>376 pub fn utimensat<P: path::Arg, Fd: AsFd>(
377     dirfd: Fd,
378     path: P,
379     times: &Timestamps,
380     flags: AtFlags,
381 ) -> io::Result<()> {
382     path.into_with_c_str(|path| backend::fs::syscalls::utimensat(dirfd.as_fd(), path, times, flags))
383 }
384 
385 /// `fchmodat(dirfd, path, mode, flags)`—Sets file or directory permissions.
386 ///
387 /// Platform support for flags varies widely, for example on Linux
388 /// [`AtFlags::SYMLINK_NOFOLLOW`] is not implemented and therefore
389 /// [`io::Errno::OPNOTSUPP`] will be returned.
390 ///
391 /// # References
392 ///  - [POSIX]
393 ///  - [Linux]
394 ///
395 /// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/fchmodat.html
396 /// [Linux]: https://man7.org/linux/man-pages/man2/fchmodat.2.html
397 #[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
398 #[inline]
399 #[doc(alias = "fchmodat")]
chmodat<P: path::Arg, Fd: AsFd>( dirfd: Fd, path: P, mode: Mode, flags: AtFlags, ) -> io::Result<()>400 pub fn chmodat<P: path::Arg, Fd: AsFd>(
401     dirfd: Fd,
402     path: P,
403     mode: Mode,
404     flags: AtFlags,
405 ) -> io::Result<()> {
406     path.into_with_c_str(|path| backend::fs::syscalls::chmodat(dirfd.as_fd(), path, mode, flags))
407 }
408 
409 /// `fclonefileat(src, dst_dir, dst, flags)`—Efficiently copies between files.
410 ///
411 /// # References
412 ///  - [Apple]
413 ///
414 /// [Apple]: https://opensource.apple.com/source/xnu/xnu-3789.21.4/bsd/man/man2/clonefile.2.auto.html
415 #[cfg(apple)]
416 #[inline]
fclonefileat<Fd: AsFd, DstFd: AsFd, P: path::Arg>( src: Fd, dst_dir: DstFd, dst: P, flags: CloneFlags, ) -> io::Result<()>417 pub fn fclonefileat<Fd: AsFd, DstFd: AsFd, P: path::Arg>(
418     src: Fd,
419     dst_dir: DstFd,
420     dst: P,
421     flags: CloneFlags,
422 ) -> io::Result<()> {
423     dst.into_with_c_str(|dst| {
424         backend::fs::syscalls::fclonefileat(src.as_fd(), dst_dir.as_fd(), dst, flags)
425     })
426 }
427 
428 /// `mknodat(dirfd, path, mode, dev)`—Creates special or normal files.
429 ///
430 /// # References
431 ///  - [POSIX]
432 ///  - [Linux]
433 ///
434 /// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/mknodat.html
435 /// [Linux]: https://man7.org/linux/man-pages/man2/mknodat.2.html
436 #[cfg(not(any(apple, target_os = "espidf", target_os = "vita", target_os = "wasi")))]
437 #[inline]
mknodat<P: path::Arg, Fd: AsFd>( dirfd: Fd, path: P, file_type: FileType, mode: Mode, dev: Dev, ) -> io::Result<()>438 pub fn mknodat<P: path::Arg, Fd: AsFd>(
439     dirfd: Fd,
440     path: P,
441     file_type: FileType,
442     mode: Mode,
443     dev: Dev,
444 ) -> io::Result<()> {
445     path.into_with_c_str(|path| {
446         backend::fs::syscalls::mknodat(dirfd.as_fd(), path, file_type, mode, dev)
447     })
448 }
449 
450 /// `fchownat(dirfd, path, owner, group, flags)`—Sets file or directory
451 /// ownership.
452 ///
453 /// # References
454 ///  - [POSIX]
455 ///  - [Linux]
456 ///
457 /// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/fchownat.html
458 /// [Linux]: https://man7.org/linux/man-pages/man2/fchownat.2.html
459 #[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
460 #[inline]
461 #[doc(alias = "fchownat")]
chownat<P: path::Arg, Fd: AsFd>( dirfd: Fd, path: P, owner: Option<Uid>, group: Option<Gid>, flags: AtFlags, ) -> io::Result<()>462 pub fn chownat<P: path::Arg, Fd: AsFd>(
463     dirfd: Fd,
464     path: P,
465     owner: Option<Uid>,
466     group: Option<Gid>,
467     flags: AtFlags,
468 ) -> io::Result<()> {
469     path.into_with_c_str(|path| {
470         backend::fs::syscalls::chownat(dirfd.as_fd(), path, owner, group, flags)
471     })
472 }
473