Skip to main content

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