xref: /third_party/rust/crates/rustix/src/fs/at.rs (revision b8a62b91)
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 returned by [`cwd`].
5//!
6//! [`cwd`]: crate::fs::cwd
7
8use crate::fd::OwnedFd;
9use crate::ffi::{CStr, CString};
10#[cfg(not(any(target_os = "illumos", target_os = "solaris")))]
11use crate::fs::Access;
12#[cfg(any(target_os = "ios", target_os = "macos"))]
13use crate::fs::CloneFlags;
14#[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "wasi")))]
15use crate::fs::FileType;
16#[cfg(any(target_os = "android", target_os = "linux"))]
17use crate::fs::RenameFlags;
18use crate::fs::{AtFlags, Mode, OFlags, Stat, Timestamps};
19use crate::path::SMALL_PATH_BUFFER_SIZE;
20#[cfg(not(target_os = "wasi"))]
21use crate::process::{Gid, Uid};
22use crate::{backend, io, path};
23use alloc::vec::Vec;
24use backend::fd::{AsFd, BorrowedFd};
25use backend::time::types::Nsecs;
26
27pub use backend::fs::types::{Dev, RawMode};
28
29/// `UTIME_NOW` for use with [`utimensat`].
30///
31/// [`utimensat`]: crate::fs::utimensat
32#[cfg(not(target_os = "redox"))]
33pub const UTIME_NOW: Nsecs = backend::fs::types::UTIME_NOW as Nsecs;
34
35/// `UTIME_OMIT` for use with [`utimensat`].
36///
37/// [`utimensat`]: crate::fs::utimensat
38#[cfg(not(target_os = "redox"))]
39pub const UTIME_OMIT: Nsecs = backend::fs::types::UTIME_OMIT as Nsecs;
40
41/// `openat(dirfd, path, oflags, mode)`—Opens a file.
42///
43/// POSIX guarantees that `openat` will use the lowest unused file descriptor,
44/// however it is not safe in general to rely on this, as file descriptors may
45/// be unexpectedly allocated on other threads or in libraries.
46///
47/// The `Mode` argument is only significant when creating a file.
48///
49/// # References
50///  - [POSIX]
51///  - [Linux]
52///
53/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/openat.html
54/// [Linux]: https://man7.org/linux/man-pages/man2/open.2.html
55#[inline]
56pub fn openat<P: path::Arg, Fd: AsFd>(
57    dirfd: Fd,
58    path: P,
59    oflags: OFlags,
60    create_mode: Mode,
61) -> io::Result<OwnedFd> {
62    path.into_with_c_str(|path| {
63        backend::fs::syscalls::openat(dirfd.as_fd(), path, oflags, create_mode)
64    })
65}
66
67/// `readlinkat(fd, path)`—Reads the contents of a symlink.
68///
69/// If `reuse` is non-empty, reuse its buffer to store the result if possible.
70///
71/// # References
72///  - [POSIX]
73///  - [Linux]
74///
75/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/readlinkat.html
76/// [Linux]: https://man7.org/linux/man-pages/man2/readlinkat.2.html
77#[inline]
78pub fn readlinkat<P: path::Arg, Fd: AsFd, B: Into<Vec<u8>>>(
79    dirfd: Fd,
80    path: P,
81    reuse: B,
82) -> io::Result<CString> {
83    path.into_with_c_str(|path| _readlinkat(dirfd.as_fd(), path, reuse.into()))
84}
85
86fn _readlinkat(dirfd: BorrowedFd<'_>, path: &CStr, mut buffer: Vec<u8>) -> io::Result<CString> {
87    // This code would benefit from having a better way to read into
88    // uninitialized memory, but that requires `unsafe`.
89    buffer.clear();
90    buffer.reserve(SMALL_PATH_BUFFER_SIZE);
91    buffer.resize(buffer.capacity(), 0_u8);
92
93    loop {
94        let nread = backend::fs::syscalls::readlinkat(dirfd.as_fd(), path, &mut buffer)?;
95
96        let nread = nread as usize;
97        assert!(nread <= buffer.len());
98        if nread < buffer.len() {
99            buffer.resize(nread, 0_u8);
100            return Ok(CString::new(buffer).unwrap());
101        }
102        buffer.reserve(1); // use `Vec` reallocation strategy to grow capacity exponentially
103        buffer.resize(buffer.capacity(), 0_u8);
104    }
105}
106
107/// `mkdirat(fd, path, mode)`—Creates a directory.
108///
109/// # References
110///  - [POSIX]
111///  - [Linux]
112///
113/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/mkdirat.html
114/// [Linux]: https://man7.org/linux/man-pages/man2/mkdirat.2.html
115#[inline]
116pub fn mkdirat<P: path::Arg, Fd: AsFd>(dirfd: Fd, path: P, mode: Mode) -> io::Result<()> {
117    path.into_with_c_str(|path| backend::fs::syscalls::mkdirat(dirfd.as_fd(), path, mode))
118}
119
120/// `linkat(old_dirfd, old_path, new_dirfd, new_path, flags)`—Creates a hard
121/// link.
122///
123/// # References
124///  - [POSIX]
125///  - [Linux]
126///
127/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/linkat.html
128/// [Linux]: https://man7.org/linux/man-pages/man2/linkat.2.html
129#[inline]
130pub fn linkat<P: path::Arg, Q: path::Arg, PFd: AsFd, QFd: AsFd>(
131    old_dirfd: PFd,
132    old_path: P,
133    new_dirfd: QFd,
134    new_path: Q,
135    flags: AtFlags,
136) -> io::Result<()> {
137    old_path.into_with_c_str(|old_path| {
138        new_path.into_with_c_str(|new_path| {
139            backend::fs::syscalls::linkat(
140                old_dirfd.as_fd(),
141                old_path,
142                new_dirfd.as_fd(),
143                new_path,
144                flags,
145            )
146        })
147    })
148}
149
150/// `unlinkat(fd, path, flags)`—Unlinks a file or remove a directory.
151///
152/// With the [`REMOVEDIR`] flag, this removes a directory. This is in place
153/// of a `rmdirat` function.
154///
155/// # References
156///  - [POSIX]
157///  - [Linux]
158///
159/// [`REMOVEDIR`]: AtFlags::REMOVEDIR
160/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/unlinkat.html
161/// [Linux]: https://man7.org/linux/man-pages/man2/unlinkat.2.html
162#[inline]
163pub fn unlinkat<P: path::Arg, Fd: AsFd>(dirfd: Fd, path: P, flags: AtFlags) -> io::Result<()> {
164    path.into_with_c_str(|path| backend::fs::syscalls::unlinkat(dirfd.as_fd(), path, flags))
165}
166
167/// `renameat(old_dirfd, old_path, new_dirfd, new_path)`—Renames a file or
168/// directory.
169///
170/// # References
171///  - [POSIX]
172///  - [Linux]
173///
174/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/renameat.html
175/// [Linux]: https://man7.org/linux/man-pages/man2/renameat.2.html
176#[inline]
177pub fn renameat<P: path::Arg, Q: path::Arg, PFd: AsFd, QFd: AsFd>(
178    old_dirfd: PFd,
179    old_path: P,
180    new_dirfd: QFd,
181    new_path: Q,
182) -> io::Result<()> {
183    old_path.into_with_c_str(|old_path| {
184        new_path.into_with_c_str(|new_path| {
185            backend::fs::syscalls::renameat(
186                old_dirfd.as_fd(),
187                old_path,
188                new_dirfd.as_fd(),
189                new_path,
190            )
191        })
192    })
193}
194
195/// `renameat2(old_dirfd, old_path, new_dirfd, new_path, flags)`—Renames a
196/// file or directory.
197///
198/// # References
199///  - [Linux]
200///
201/// [Linux]: https://man7.org/linux/man-pages/man2/renameat2.2.html
202#[cfg(any(target_os = "android", target_os = "linux"))]
203#[inline]
204#[doc(alias = "renameat2")]
205pub fn renameat_with<P: path::Arg, Q: path::Arg, PFd: AsFd, QFd: AsFd>(
206    old_dirfd: PFd,
207    old_path: P,
208    new_dirfd: QFd,
209    new_path: Q,
210    flags: RenameFlags,
211) -> io::Result<()> {
212    old_path.into_with_c_str(|old_path| {
213        new_path.into_with_c_str(|new_path| {
214            backend::fs::syscalls::renameat2(
215                old_dirfd.as_fd(),
216                old_path,
217                new_dirfd.as_fd(),
218                new_path,
219                flags,
220            )
221        })
222    })
223}
224
225/// `symlinkat(old_path, new_dirfd, new_path)`—Creates a symlink.
226///
227/// # References
228///  - [POSIX]
229///  - [Linux]
230///
231/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/symlinkat.html
232/// [Linux]: https://man7.org/linux/man-pages/man2/symlinkat.2.html
233#[inline]
234pub fn symlinkat<P: path::Arg, Q: path::Arg, Fd: AsFd>(
235    old_path: P,
236    new_dirfd: Fd,
237    new_path: Q,
238) -> io::Result<()> {
239    old_path.into_with_c_str(|old_path| {
240        new_path.into_with_c_str(|new_path| {
241            backend::fs::syscalls::symlinkat(old_path, new_dirfd.as_fd(), new_path)
242        })
243    })
244}
245
246/// `fstatat(dirfd, path, flags)`—Queries metadata for a file or directory.
247///
248/// [`Mode::from_raw_mode`] and [`FileType::from_raw_mode`] may be used to
249/// interpret the `st_mode` field.
250///
251/// # References
252///  - [POSIX]
253///  - [Linux]
254///
255/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/fstatat.html
256/// [Linux]: https://man7.org/linux/man-pages/man2/fstatat.2.html
257/// [`Mode::from_raw_mode`]: crate::fs::Mode::from_raw_mode
258/// [`FileType::from_raw_mode`]: crate::fs::FileType::from_raw_mode
259#[inline]
260#[doc(alias = "fstatat")]
261pub fn statat<P: path::Arg, Fd: AsFd>(dirfd: Fd, path: P, flags: AtFlags) -> io::Result<Stat> {
262    path.into_with_c_str(|path| backend::fs::syscalls::statat(dirfd.as_fd(), path, flags))
263}
264
265/// `faccessat(dirfd, path, access, flags)`—Tests permissions for a file or
266/// directory.
267///
268/// # References
269///  - [POSIX]
270///  - [Linux]
271///
272/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/faccessat.html
273/// [Linux]: https://man7.org/linux/man-pages/man2/faccessat.2.html
274#[cfg(not(any(target_os = "illumos", target_os = "solaris")))]
275#[inline]
276#[doc(alias = "faccessat")]
277pub fn accessat<P: path::Arg, Fd: AsFd>(
278    dirfd: Fd,
279    path: P,
280    access: Access,
281    flags: AtFlags,
282) -> io::Result<()> {
283    path.into_with_c_str(|path| backend::fs::syscalls::accessat(dirfd.as_fd(), path, access, flags))
284}
285
286/// `utimensat(dirfd, path, times, flags)`—Sets file or directory timestamps.
287///
288/// # References
289///  - [POSIX]
290///  - [Linux]
291///
292/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/utimensat.html
293/// [Linux]: https://man7.org/linux/man-pages/man2/utimensat.2.html
294#[inline]
295pub fn utimensat<P: path::Arg, Fd: AsFd>(
296    dirfd: Fd,
297    path: P,
298    times: &Timestamps,
299    flags: AtFlags,
300) -> io::Result<()> {
301    path.into_with_c_str(|path| backend::fs::syscalls::utimensat(dirfd.as_fd(), path, times, flags))
302}
303
304/// `fchmodat(dirfd, path, mode, 0)`—Sets file or directory permissions.
305///
306/// The flags argument is fixed to 0, so `AT_SYMLINK_NOFOLLOW` is not
307/// supported. <details>Platform support for this flag varies widely.</details>
308///
309/// This implementation does not support `O_PATH` file descriptors, even on
310/// platforms where the host libc emulates it.
311///
312/// # References
313///  - [POSIX]
314///  - [Linux]
315///
316/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/fchmodat.html
317/// [Linux]: https://man7.org/linux/man-pages/man2/fchmodat.2.html
318#[cfg(not(target_os = "wasi"))]
319#[inline]
320#[doc(alias = "fchmodat")]
321pub fn chmodat<P: path::Arg, Fd: AsFd>(dirfd: Fd, path: P, mode: Mode) -> io::Result<()> {
322    path.into_with_c_str(|path| backend::fs::syscalls::chmodat(dirfd.as_fd(), path, mode))
323}
324
325/// `fclonefileat(src, dst_dir, dst, flags)`—Efficiently copies between files.
326///
327/// # References
328///  - [Apple]
329///
330/// [Apple]: https://opensource.apple.com/source/xnu/xnu-3789.21.4/bsd/man/man2/clonefile.2.auto.html
331#[cfg(any(target_os = "ios", target_os = "macos"))]
332#[inline]
333pub fn fclonefileat<Fd: AsFd, DstFd: AsFd, P: path::Arg>(
334    src: Fd,
335    dst_dir: DstFd,
336    dst: P,
337    flags: CloneFlags,
338) -> io::Result<()> {
339    dst.into_with_c_str(|dst| {
340        backend::fs::syscalls::fclonefileat(src.as_fd(), dst_dir.as_fd(), dst, flags)
341    })
342}
343
344/// `mknodat(dirfd, path, mode, dev)`—Creates special or normal files.
345///
346/// # References
347///  - [POSIX]
348///  - [Linux]
349///
350/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/mknodat.html
351/// [Linux]: https://man7.org/linux/man-pages/man2/mknodat.2.html
352#[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "wasi")))]
353#[inline]
354pub fn mknodat<P: path::Arg, Fd: AsFd>(
355    dirfd: Fd,
356    path: P,
357    file_type: FileType,
358    mode: Mode,
359    dev: Dev,
360) -> io::Result<()> {
361    path.into_with_c_str(|path| {
362        backend::fs::syscalls::mknodat(dirfd.as_fd(), path, file_type, mode, dev)
363    })
364}
365
366/// `fchownat(dirfd, path, owner, group, flags)`—Sets file or directory
367/// ownership.
368///
369/// # References
370///  - [POSIX]
371///  - [Linux]
372///
373/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/fchownat.html
374/// [Linux]: https://man7.org/linux/man-pages/man2/fchownat.2.html
375#[cfg(not(target_os = "wasi"))]
376#[inline]
377#[doc(alias = "fchownat")]
378pub fn chownat<P: path::Arg, Fd: AsFd>(
379    dirfd: Fd,
380    path: P,
381    owner: Option<Uid>,
382    group: Option<Gid>,
383    flags: AtFlags,
384) -> io::Result<()> {
385    path.into_with_c_str(|path| {
386        backend::fs::syscalls::chownat(dirfd.as_fd(), path, owner, group, flags)
387    })
388}
389