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