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