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