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}