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