rustix/fs/
at.rs

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/9799919799/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| {
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/9799919799/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| _readlinkat(dirfd.as_fd(), path, 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
114            //   in the buffer buf”
115            // - [POSIX definition 3.271: Pathname]: “A string that is used
116            //   to 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/9799919799/basedefs/V1_chap03.html#tag_03_271
124            // [POSIX definition 3.375: String]: https://pubs.opengroup.org/onlinepubs/9799919799/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/9799919799/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| _readlinkat_raw(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 = 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/9799919799/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| 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/9799919799/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| {
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/9799919799/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| 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/9799919799/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| {
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")]
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| {
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/9799919799/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| {
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/9799919799/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| 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/9799919799/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| 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/9799919799/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| 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/9799919799/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| 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]
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/9799919799/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| {
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/9799919799/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| {
470        backend::fs::syscalls::chownat(dirfd.as_fd(), path, owner, group, flags)
471    })
472}