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
8use crate::fd::OwnedFd;
9use crate::ffi::CStr;
10#[cfg(not(any(target_os = "espidf", target_os = "vita")))]
11use crate::fs::Access;
12#[cfg(not(target_os = "espidf"))]
13use crate::fs::AtFlags;
14#[cfg(apple)]
15use crate::fs::CloneFlags;
16#[cfg(linux_kernel)]
17use crate::fs::RenameFlags;
18#[cfg(not(target_os = "espidf"))]
19use crate::fs::Stat;
20#[cfg(not(any(apple, target_os = "espidf", target_os = "vita", target_os = "wasi")))]
21use crate::fs::{Dev, FileType};
22#[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
23use crate::fs::{Gid, Uid};
24use crate::fs::{Mode, OFlags};
25use crate::{backend, io, path};
26use backend::fd::{AsFd, BorrowedFd};
27use core::mem::MaybeUninit;
28use core::slice;
29#[cfg(feature = "alloc")]
30use {crate::ffi::CString, crate::path::SMALL_PATH_BUFFER_SIZE, alloc::vec::Vec};
31#[cfg(not(any(target_os = "espidf", target_os = "vita")))]
32use {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")))]
38pub 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")))]
44pub 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]
61pub 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: &CStr| {
68 backend::fs::syscalls::openat(dirfd:dirfd.as_fd(), path, flags: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]
84pub 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: &CStr| _readlinkat(dirfd:dirfd.as_fd(), path, buffer:reuse.into()))
90}
91
92#[cfg(feature = "alloc")]
93#[allow(unsafe_code)]
94fn _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]
154pub 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: &CStr| _readlinkat_raw(dirfd:dirfd.as_fd(), path, buf))
160}
161
162#[allow(unsafe_code)]
163fn _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: usize = backend::fs::syscalls::readlinkat(dirfd:dirfd.as_fd(), path, buf)?;
169 unsafe {
170 Ok((
171 slice::from_raw_parts_mut(data:buf.as_mut_ptr().cast::<u8>(), len: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]
186pub fn mkdirat<P: path::Arg, Fd: AsFd>(dirfd: Fd, path: P, mode: Mode) -> io::Result<()> {
187 path.into_with_c_str(|path: &CStr| backend::fs::syscalls::mkdirat(dirfd: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]
201pub 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: &CStr| {
209 new_path.into_with_c_str(|new_path: &CStr| {
210 backend::fs::syscalls::linkat(
211 old_dirfd:old_dirfd.as_fd(),
212 old_path,
213 new_dirfd: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]
235pub fn unlinkat<P: path::Arg, Fd: AsFd>(dirfd: Fd, path: P, flags: AtFlags) -> io::Result<()> {
236 path.into_with_c_str(|path: &CStr| backend::fs::syscalls::unlinkat(dirfd: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]
249pub 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: &CStr| {
256 new_path.into_with_c_str(|new_path: &CStr| {
257 backend::fs::syscalls::renameat(
258 old_dirfd:old_dirfd.as_fd(),
259 old_path,
260 new_dirfd: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")]
277pub 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: &CStr| {
285 new_path.into_with_c_str(|new_path: &CStr| {
286 backend::fs::syscalls::renameat2(
287 old_dirfd:old_dirfd.as_fd(),
288 old_path,
289 new_dirfd: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]
306pub 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: &CStr| {
312 new_path.into_with_c_str(|new_path: &CStr| {
313 backend::fs::syscalls::symlinkat(old_path, dirfd: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")]
334pub fn statat<P: path::Arg, Fd: AsFd>(dirfd: Fd, path: P, flags: AtFlags) -> io::Result<Stat> {
335 path.into_with_c_str(|path: &CStr| backend::fs::syscalls::statat(dirfd: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")]
357pub 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: &CStr| backend::fs::syscalls::accessat(dirfd: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]
376pub 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: &CStr| backend::fs::syscalls::utimensat(dirfd: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")]
400pub 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: &CStr| backend::fs::syscalls::chmodat(dirfd: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]
417pub 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]
438pub 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: &CStr| {
446 backend::fs::syscalls::mknodat(dirfd: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")]
462pub 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: &CStr| {
470 backend::fs::syscalls::chownat(dirfd:dirfd.as_fd(), path, owner, group, flags)
471 })
472}
473