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