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 | |
8 | use crate::fd::OwnedFd; |
9 | #[cfg (apple)] |
10 | use crate::fs::CloneFlags; |
11 | #[cfg (not(any(apple, target_os = "espidf" , target_os = "wasi" )))] |
12 | use crate::fs::FileType; |
13 | #[cfg (linux_kernel)] |
14 | use crate::fs::RenameFlags; |
15 | #[cfg (not(any(target_os = "aix" , target_os = "espidf" )))] |
16 | use crate::fs::Stat; |
17 | #[cfg (not(any(target_os = "espidf" , target_os = "wasi" )))] |
18 | use crate::fs::{Gid, Uid}; |
19 | use crate::fs::{Mode, OFlags}; |
20 | use crate::{backend, io, path}; |
21 | use backend::fd::AsFd; |
22 | #[cfg (feature = "alloc" )] |
23 | use { |
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" ))] |
30 | use { |
31 | crate::fs::{Access, AtFlags, Timestamps}, |
32 | crate::timespec::Nsecs, |
33 | }; |
34 | |
35 | pub 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" )))] |
41 | pub 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" )))] |
47 | pub 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 ] |
64 | pub 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 ] |
87 | pub 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)] |
97 | fn _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 ] |
148 | pub 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 ] |
163 | pub 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 ] |
197 | pub 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 ] |
211 | pub 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" )] |
239 | pub 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 ] |
268 | pub 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" )] |
297 | pub 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" )] |
320 | pub 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 ] |
339 | pub 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" )] |
363 | pub 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 ] |
380 | pub 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 ] |
401 | pub 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" )] |
425 | pub 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 | |