Skip to main content

std/sys/fs/
unix.rs

1#![allow(nonstandard_style)]
2#![allow(unsafe_op_in_unsafe_fn)]
3// miri has some special hacks here that make things unused.
4#![cfg_attr(miri, allow(unused))]
5
6#[cfg(test)]
7mod tests;
8
9// Import the file operations under consistent names.
10cfg_select! {
11    not(any(
12        all(target_os = "linux", not(target_env = "musl")),
13        target_os = "android",
14        target_os = "hurd",
15        target_os = "l4re",
16    )) => {
17        use libc::{
18            dirent as dirent64, fstat as fstat64, ftruncate as ftruncate64, lseek as lseek64,
19            lstat as lstat64, off_t as off64_t, open as open64, stat as stat64,
20        };
21    }
22    target_os = "android" => {
23        // Android's `stat`, `dirent`, and related functions are always 64-bit LFS compatible, and
24        // `open` already implies `O_LARGEFILE`, so all those don't need to follow Linux.
25        // However, we still need off64_t, ftruncate64, and lseek64.
26        use libc::{
27            dirent as dirent64, fstat as fstat64, ftruncate64, lseek64, lstat as lstat64, off64_t,
28            open as open64, stat as stat64,
29        };
30    }
31    target_os = "l4re" => {
32        use libc::{
33            dirent64, fstat as fstat64, ftruncate as ftruncate64, lseek as lseek64,
34            lstat as lstat64, off_t as off64_t, open as open64, stat as stat64,
35        };
36    }
37    _ => {
38        use libc::{dirent64, fstat64, ftruncate64, lseek64, lstat64, off64_t, open64, stat64};
39    }
40}
41
42#[cfg(all(target_os = "linux", target_env = "gnu"))]
43use libc::c_char;
44#[cfg(any(
45    all(target_os = "linux", not(target_env = "musl")),
46    target_os = "android",
47    target_os = "fuchsia",
48    target_os = "hurd",
49    target_os = "illumos",
50    target_vendor = "apple",
51))]
52use libc::dirfd;
53use libc::{c_int, mode_t};
54
55use crate::ffi::{CStr, OsStr, OsString};
56use crate::fmt::{self, Write as _};
57use crate::fs::TryLockError;
58use crate::io::{self, BorrowedCursor, Error, IoSlice, IoSliceMut, SeekFrom};
59use crate::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd};
60#[cfg(target_family = "unix")]
61use crate::os::unix::prelude::*;
62#[cfg(target_os = "wasi")]
63use crate::os::wasi::prelude::*;
64use crate::path::{Path, PathBuf};
65use crate::sync::Arc;
66use crate::sys::fd::FileDesc;
67pub use crate::sys::fs::common::exists;
68use crate::sys::helpers::run_path_with_cstr;
69use crate::sys::time::SystemTime;
70#[cfg(all(target_os = "linux", target_env = "gnu"))]
71use crate::sys::weak::syscall;
72#[cfg(target_os = "android")]
73use crate::sys::weak::weak;
74use crate::sys::{AsInner, AsInnerMut, FromInner, IntoInner, cvt, cvt_r};
75use crate::{mem, ptr};
76
77// Used by rustc for checking the definitions of other function with the same symbol names
78//
79// See the `invalid_runtime_symbols_definitions` lint.
80#[cfg(not(test))]
81mod runtime_symbols {
82    use core::ffi::{c_char, c_int, c_size_t, c_ssize_t, c_void};
83
84    unsafe extern "C" {
85        #[rustc_canonical_symbol]
86        fn open(pathname: *const c_char, flags: c_int, ...) -> c_int;
87
88        #[rustc_canonical_symbol]
89        fn read(fd: c_int, buf: *mut c_void, count: c_size_t) -> c_ssize_t;
90
91        #[rustc_canonical_symbol]
92        fn write(fd: c_int, buf: *const c_void, count: c_size_t) -> c_ssize_t;
93
94        #[rustc_canonical_symbol]
95        fn close(fd: c_int) -> c_int;
96    }
97}
98
99pub struct File(FileDesc);
100
101// FIXME: This should be available on Linux with all `target_env`.
102// But currently only glibc exposes `statx` fn and structs.
103// We don't want to import unverified raw C structs here directly.
104// https://github.com/rust-lang/rust/pull/67774
105macro_rules! cfg_has_statx {
106    ({ $($then_tt:tt)* } else { $($else_tt:tt)* }) => {
107        cfg_select! {
108            all(target_os = "linux", target_env = "gnu") => {
109                $($then_tt)*
110            }
111            _ => {
112                $($else_tt)*
113            }
114        }
115    };
116    ($($block_inner:tt)*) => {
117        #[cfg(all(target_os = "linux", target_env = "gnu"))]
118        {
119            $($block_inner)*
120        }
121    };
122}
123
124cfg_has_statx! {{
125    #[derive(Clone)]
126    pub struct FileAttr {
127        stat: stat64,
128        statx_extra_fields: Option<StatxExtraFields>,
129    }
130
131    #[derive(Clone)]
132    struct StatxExtraFields {
133        // This is needed to check if btime is supported by the filesystem.
134        stx_mask: u32,
135        stx_btime: libc::statx_timestamp,
136        // With statx, we can overcome 32-bit `time_t` too.
137        #[cfg(target_pointer_width = "32")]
138        stx_atime: libc::statx_timestamp,
139        #[cfg(target_pointer_width = "32")]
140        stx_ctime: libc::statx_timestamp,
141        #[cfg(target_pointer_width = "32")]
142        stx_mtime: libc::statx_timestamp,
143
144    }
145
146    // We prefer `statx` on Linux if available, which contains file creation time,
147    // as well as 64-bit timestamps of all kinds.
148    // Default `stat64` contains no creation time and may have 32-bit `time_t`.
149    unsafe fn try_statx(
150        fd: c_int,
151        path: *const c_char,
152        flags: i32,
153        mask: u32,
154    ) -> Option<io::Result<FileAttr>> {
155        use crate::sync::atomic::{Atomic, AtomicU8, Ordering};
156
157        // Linux kernel prior to 4.11 or glibc prior to glibc 2.28 don't support `statx`.
158        // We check for it on first failure and remember availability to avoid having to
159        // do it again.
160        #[repr(u8)]
161        enum STATX_STATE{ Unknown = 0, Present, Unavailable }
162        static STATX_SAVED_STATE: Atomic<u8> = AtomicU8::new(STATX_STATE::Unknown as u8);
163
164        syscall!(
165            fn statx(
166                fd: c_int,
167                pathname: *const c_char,
168                flags: c_int,
169                mask: libc::c_uint,
170                statxbuf: *mut libc::statx,
171            ) -> c_int;
172        );
173
174        let statx_availability = STATX_SAVED_STATE.load(Ordering::Relaxed);
175        if statx_availability == STATX_STATE::Unavailable as u8 {
176            return None;
177        }
178
179        let mut buf: libc::statx = mem::zeroed();
180        if let Err(err) = cvt(statx(fd, path, flags, mask, &mut buf)) {
181            if STATX_SAVED_STATE.load(Ordering::Relaxed) == STATX_STATE::Present as u8 {
182                return Some(Err(err));
183            }
184
185            // We're not yet entirely sure whether `statx` is usable on this kernel
186            // or not. Syscalls can return errors from things other than the kernel
187            // per se, e.g. `EPERM` can be returned if seccomp is used to block the
188            // syscall, or `ENOSYS` might be returned from a faulty FUSE driver.
189            //
190            // Availability is checked by performing a call which expects `EFAULT`
191            // if the syscall is usable.
192            //
193            // See: https://github.com/rust-lang/rust/issues/65662
194            //
195            // FIXME what about transient conditions like `ENOMEM`?
196            let err2 = cvt(statx(0, ptr::null(), 0, libc::STATX_BASIC_STATS | libc::STATX_BTIME, ptr::null_mut()))
197                .err()
198                .and_then(|e| e.raw_os_error());
199            if err2 == Some(libc::EFAULT) {
200                STATX_SAVED_STATE.store(STATX_STATE::Present as u8, Ordering::Relaxed);
201                return Some(Err(err));
202            } else {
203                STATX_SAVED_STATE.store(STATX_STATE::Unavailable as u8, Ordering::Relaxed);
204                return None;
205            }
206        }
207        if statx_availability == STATX_STATE::Unknown as u8 {
208            STATX_SAVED_STATE.store(STATX_STATE::Present as u8, Ordering::Relaxed);
209        }
210
211        // We cannot fill `stat64` exhaustively because of private padding fields.
212        let mut stat: stat64 = mem::zeroed();
213        // `c_ulong` on gnu-mips, `dev_t` otherwise
214        stat.st_dev = libc::makedev(buf.stx_dev_major, buf.stx_dev_minor) as _;
215        stat.st_ino = buf.stx_ino as libc::ino64_t;
216        stat.st_nlink = buf.stx_nlink as libc::nlink_t;
217        stat.st_mode = buf.stx_mode as libc::mode_t;
218        stat.st_uid = buf.stx_uid as libc::uid_t;
219        stat.st_gid = buf.stx_gid as libc::gid_t;
220        stat.st_rdev = libc::makedev(buf.stx_rdev_major, buf.stx_rdev_minor) as _;
221        stat.st_size = buf.stx_size as off64_t;
222        stat.st_blksize = buf.stx_blksize as libc::blksize_t;
223        stat.st_blocks = buf.stx_blocks as libc::blkcnt64_t;
224        stat.st_atime = buf.stx_atime.tv_sec as libc::time_t;
225        // `i64` on gnu-x86_64-x32, `c_ulong` otherwise.
226        stat.st_atime_nsec = buf.stx_atime.tv_nsec as _;
227        stat.st_mtime = buf.stx_mtime.tv_sec as libc::time_t;
228        stat.st_mtime_nsec = buf.stx_mtime.tv_nsec as _;
229        stat.st_ctime = buf.stx_ctime.tv_sec as libc::time_t;
230        stat.st_ctime_nsec = buf.stx_ctime.tv_nsec as _;
231
232        let extra = StatxExtraFields {
233            stx_mask: buf.stx_mask,
234            stx_btime: buf.stx_btime,
235            // Store full times to avoid 32-bit `time_t` truncation.
236            #[cfg(target_pointer_width = "32")]
237            stx_atime: buf.stx_atime,
238            #[cfg(target_pointer_width = "32")]
239            stx_ctime: buf.stx_ctime,
240            #[cfg(target_pointer_width = "32")]
241            stx_mtime: buf.stx_mtime,
242        };
243
244        Some(Ok(FileAttr { stat, statx_extra_fields: Some(extra) }))
245    }
246
247} else {
248    #[derive(Clone)]
249    pub struct FileAttr {
250        stat: stat64,
251    }
252}}
253
254// all DirEntry's will have a reference to this struct
255struct InnerReadDir {
256    dirp: DirStream,
257    root: PathBuf,
258}
259
260pub struct ReadDir {
261    inner: Arc<InnerReadDir>,
262    end_of_stream: bool,
263}
264
265impl ReadDir {
266    fn new(inner: InnerReadDir) -> Self {
267        Self { inner: Arc::new(inner), end_of_stream: false }
268    }
269}
270
271struct DirStream(*mut libc::DIR);
272
273// dir::Dir requires openat support
274cfg_select! {
275    any(
276        target_os = "redox",
277        target_os = "espidf",
278        target_os = "horizon",
279        target_os = "vita",
280        target_os = "nto",
281        target_os = "qnx",
282        target_os = "vxworks",
283        target_os = "l4re",
284    ) => {
285        pub use crate::sys::fs::common::Dir;
286    }
287    _ => {
288        mod dir;
289        pub use dir::Dir;
290    }
291}
292
293fn debug_path_fd<'a, 'b>(
294    fd: c_int,
295    f: &'a mut fmt::Formatter<'b>,
296    name: &str,
297) -> fmt::DebugStruct<'a, 'b> {
298    let mut b = f.debug_struct(name);
299
300    fn get_mode(fd: c_int) -> Option<(bool, bool)> {
301        let mode = unsafe { libc::fcntl(fd, libc::F_GETFL) };
302        if mode == -1 {
303            return None;
304        }
305        match mode & libc::O_ACCMODE {
306            libc::O_RDONLY => Some((true, false)),
307            libc::O_RDWR => Some((true, true)),
308            libc::O_WRONLY => Some((false, true)),
309            _ => None,
310        }
311    }
312
313    b.field("fd", &fd);
314    if let Some(path) = get_path_from_fd(fd) {
315        b.field("path", &path);
316    }
317    if let Some((read, write)) = get_mode(fd) {
318        b.field("read", &read).field("write", &write);
319    }
320
321    b
322}
323
324fn get_path_from_fd(fd: c_int) -> Option<PathBuf> {
325    #[cfg(any(target_os = "linux", target_os = "illumos", target_os = "solaris"))]
326    fn get_path(fd: c_int) -> Option<PathBuf> {
327        let mut p = PathBuf::from("/proc/self/fd");
328        p.push(&fd.to_string());
329        run_path_with_cstr(&p, &readlink).ok()
330    }
331
332    #[cfg(any(target_vendor = "apple", target_os = "netbsd"))]
333    fn get_path(fd: c_int) -> Option<PathBuf> {
334        // FIXME: The use of PATH_MAX is generally not encouraged, but it
335        // is inevitable in this case because Apple targets and NetBSD define `fcntl`
336        // with `F_GETPATH` in terms of `MAXPATHLEN`, and there are no
337        // alternatives. If a better method is invented, it should be used
338        // instead.
339        let mut buf = vec![0; libc::PATH_MAX as usize];
340        let n = unsafe { libc::fcntl(fd, libc::F_GETPATH, buf.as_mut_ptr()) };
341        if n == -1 {
342            cfg_select! {
343                target_os = "netbsd" => {
344                    // fallback to procfs as last resort
345                    let mut p = PathBuf::from("/proc/self/fd");
346                    p.push(&fd.to_string());
347                    return run_path_with_cstr(&p, &readlink).ok();
348                }
349                _ => {
350                    return None;
351                }
352            }
353        }
354        let l = buf.iter().position(|&c| c == 0).unwrap();
355        buf.truncate(l as usize);
356        buf.shrink_to_fit();
357        Some(PathBuf::from(OsString::from_vec(buf)))
358    }
359
360    #[cfg(target_os = "freebsd")]
361    fn get_path(fd: c_int) -> Option<PathBuf> {
362        let info = Box::<libc::kinfo_file>::new_zeroed();
363        let mut info = unsafe { info.assume_init() };
364        info.kf_structsize = size_of::<libc::kinfo_file>() as libc::c_int;
365        let n = unsafe { libc::fcntl(fd, libc::F_KINFO, &mut *info) };
366        if n == -1 {
367            return None;
368        }
369        let buf = unsafe { CStr::from_ptr(info.kf_path.as_mut_ptr()).to_bytes().to_vec() };
370        Some(PathBuf::from(OsString::from_vec(buf)))
371    }
372
373    #[cfg(target_os = "vxworks")]
374    fn get_path(fd: c_int) -> Option<PathBuf> {
375        let mut buf = vec![0; libc::PATH_MAX as usize];
376        let n = unsafe { libc::ioctl(fd, libc::FIOGETNAME, buf.as_mut_ptr()) };
377        if n == -1 {
378            return None;
379        }
380        let l = buf.iter().position(|&c| c == 0).unwrap();
381        buf.truncate(l as usize);
382        Some(PathBuf::from(OsString::from_vec(buf)))
383    }
384
385    #[cfg(not(any(
386        target_os = "linux",
387        target_os = "vxworks",
388        target_os = "freebsd",
389        target_os = "netbsd",
390        target_os = "illumos",
391        target_os = "solaris",
392        target_vendor = "apple",
393    )))]
394    fn get_path(_fd: c_int) -> Option<PathBuf> {
395        // FIXME(#24570): implement this for other Unix platforms
396        None
397    }
398
399    get_path(fd)
400}
401
402pub struct DirEntry {
403    dir: Arc<InnerReadDir>,
404    entry: dirent64_min,
405    // We need to store an owned copy of the entry name on platforms that use
406    // readdir() (not readdir_r()), because a) struct dirent may use a flexible
407    // array to store the name, b) it lives only until the next readdir() call.
408    name: crate::ffi::CString,
409}
410
411// Define a minimal subset of fields we need from `dirent64`, especially since
412// we're not using the immediate `d_name` on these targets. Keeping this as an
413// `entry` field in `DirEntry` helps reduce the `cfg` boilerplate elsewhere.
414struct dirent64_min {
415    d_ino: u64,
416    #[cfg(not(any(
417        target_os = "solaris",
418        target_os = "illumos",
419        target_os = "haiku",
420        target_os = "vxworks",
421        target_os = "aix",
422        target_os = "nto",
423        target_os = "qnx",
424        target_os = "vita",
425    )))]
426    d_type: u8,
427}
428
429#[derive(Clone)]
430pub struct OpenOptions {
431    // generic
432    read: bool,
433    write: bool,
434    append: bool,
435    truncate: bool,
436    create: bool,
437    create_new: bool,
438    // system-specific
439    custom_flags: i32,
440    mode: mode_t,
441}
442
443#[derive(Clone, PartialEq, Eq)]
444pub struct FilePermissions {
445    mode: mode_t,
446}
447
448#[derive(Copy, Clone, Debug, Default)]
449pub struct FileTimes {
450    accessed: Option<SystemTime>,
451    modified: Option<SystemTime>,
452    #[cfg(target_vendor = "apple")]
453    created: Option<SystemTime>,
454}
455
456#[derive(Copy, Clone, Eq)]
457pub struct FileType {
458    mode: mode_t,
459}
460
461impl PartialEq for FileType {
462    fn eq(&self, other: &Self) -> bool {
463        self.masked() == other.masked()
464    }
465}
466
467impl core::hash::Hash for FileType {
468    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
469        self.masked().hash(state);
470    }
471}
472
473pub struct DirBuilder {
474    mode: mode_t,
475}
476
477#[derive(Copy, Clone)]
478struct Mode(mode_t);
479
480cfg_has_statx! {{
481    impl FileAttr {
482        fn from_stat64(stat: stat64) -> Self {
483            Self { stat, statx_extra_fields: None }
484        }
485
486        #[cfg(target_pointer_width = "32")]
487        pub fn stx_mtime(&self) -> Option<&libc::statx_timestamp> {
488            if let Some(ext) = &self.statx_extra_fields {
489                if (ext.stx_mask & libc::STATX_MTIME) != 0 {
490                    return Some(&ext.stx_mtime);
491                }
492            }
493            None
494        }
495
496        #[cfg(target_pointer_width = "32")]
497        pub fn stx_atime(&self) -> Option<&libc::statx_timestamp> {
498            if let Some(ext) = &self.statx_extra_fields {
499                if (ext.stx_mask & libc::STATX_ATIME) != 0 {
500                    return Some(&ext.stx_atime);
501                }
502            }
503            None
504        }
505
506        #[cfg(target_pointer_width = "32")]
507        pub fn stx_ctime(&self) -> Option<&libc::statx_timestamp> {
508            if let Some(ext) = &self.statx_extra_fields {
509                if (ext.stx_mask & libc::STATX_CTIME) != 0 {
510                    return Some(&ext.stx_ctime);
511                }
512            }
513            None
514        }
515    }
516} else {
517    impl FileAttr {
518        fn from_stat64(stat: stat64) -> Self {
519            Self { stat }
520        }
521    }
522}}
523
524impl FileAttr {
525    pub fn size(&self) -> u64 {
526        self.stat.st_size as u64
527    }
528    pub fn perm(&self) -> FilePermissions {
529        FilePermissions { mode: (self.stat.st_mode as mode_t) }
530    }
531
532    pub fn file_type(&self) -> FileType {
533        FileType { mode: self.stat.st_mode as mode_t }
534    }
535}
536
537#[cfg(target_os = "netbsd")]
538impl FileAttr {
539    pub fn modified(&self) -> io::Result<SystemTime> {
540        SystemTime::new(self.stat.st_mtime as i64, self.stat.st_mtimensec as i64)
541    }
542
543    pub fn accessed(&self) -> io::Result<SystemTime> {
544        SystemTime::new(self.stat.st_atime as i64, self.stat.st_atimensec as i64)
545    }
546
547    pub fn created(&self) -> io::Result<SystemTime> {
548        SystemTime::new(self.stat.st_birthtime as i64, self.stat.st_birthtimensec as i64)
549    }
550}
551
552#[cfg(target_os = "aix")]
553impl FileAttr {
554    pub fn modified(&self) -> io::Result<SystemTime> {
555        SystemTime::new(self.stat.st_mtim.tv_sec as i64, self.stat.st_mtim.tv_nsec as i64)
556    }
557
558    pub fn accessed(&self) -> io::Result<SystemTime> {
559        SystemTime::new(self.stat.st_atim.tv_sec as i64, self.stat.st_atim.tv_nsec as i64)
560    }
561
562    pub fn created(&self) -> io::Result<SystemTime> {
563        SystemTime::new(self.stat.st_ctim.tv_sec as i64, self.stat.st_ctim.tv_nsec as i64)
564    }
565}
566
567#[cfg(not(any(
568    target_os = "netbsd",
569    target_os = "nto",
570    target_os = "qnx",
571    target_os = "aix",
572    target_os = "wasi",
573    target_os = "l4re"
574)))]
575impl FileAttr {
576    #[cfg(not(any(
577        target_os = "vxworks",
578        target_os = "espidf",
579        target_os = "horizon",
580        target_os = "vita",
581        target_os = "hurd",
582        target_os = "rtems",
583        target_os = "nuttx",
584    )))]
585    pub fn modified(&self) -> io::Result<SystemTime> {
586        #[cfg(target_pointer_width = "32")]
587        cfg_has_statx! {
588            if let Some(mtime) = self.stx_mtime() {
589                return SystemTime::new(mtime.tv_sec, mtime.tv_nsec as i64);
590            }
591        }
592
593        SystemTime::new(self.stat.st_mtime as i64, self.stat.st_mtime_nsec as i64)
594    }
595
596    #[cfg(any(
597        all(target_os = "vxworks", vxworks_lt_25_09),
598        target_os = "espidf",
599        target_os = "vita",
600        target_os = "rtems",
601    ))]
602    pub fn modified(&self) -> io::Result<SystemTime> {
603        SystemTime::new(self.stat.st_mtime as i64, 0)
604    }
605
606    #[cfg(any(
607        target_os = "horizon",
608        target_os = "hurd",
609        target_os = "nuttx",
610        all(target_os = "vxworks", not(vxworks_lt_25_09))
611    ))]
612    pub fn modified(&self) -> io::Result<SystemTime> {
613        SystemTime::new(self.stat.st_mtim.tv_sec as i64, self.stat.st_mtim.tv_nsec as i64)
614    }
615
616    #[cfg(not(any(
617        target_os = "vxworks",
618        target_os = "espidf",
619        target_os = "horizon",
620        target_os = "vita",
621        target_os = "hurd",
622        target_os = "rtems",
623        target_os = "nuttx",
624    )))]
625    pub fn accessed(&self) -> io::Result<SystemTime> {
626        #[cfg(target_pointer_width = "32")]
627        cfg_has_statx! {
628            if let Some(atime) = self.stx_atime() {
629                return SystemTime::new(atime.tv_sec, atime.tv_nsec as i64);
630            }
631        }
632
633        SystemTime::new(self.stat.st_atime as i64, self.stat.st_atime_nsec as i64)
634    }
635
636    #[cfg(any(
637        all(target_os = "vxworks", vxworks_lt_25_09),
638        target_os = "espidf",
639        target_os = "vita",
640        target_os = "rtems"
641    ))]
642    pub fn accessed(&self) -> io::Result<SystemTime> {
643        SystemTime::new(self.stat.st_atime as i64, 0)
644    }
645
646    #[cfg(any(
647        target_os = "horizon",
648        target_os = "hurd",
649        target_os = "nuttx",
650        all(target_os = "vxworks", not(vxworks_lt_25_09))
651    ))]
652    pub fn accessed(&self) -> io::Result<SystemTime> {
653        SystemTime::new(self.stat.st_atim.tv_sec as i64, self.stat.st_atim.tv_nsec as i64)
654    }
655
656    #[cfg(any(
657        target_os = "freebsd",
658        target_os = "openbsd",
659        target_vendor = "apple",
660        target_os = "cygwin",
661    ))]
662    pub fn created(&self) -> io::Result<SystemTime> {
663        SystemTime::new(self.stat.st_birthtime as i64, self.stat.st_birthtime_nsec as i64)
664    }
665
666    #[cfg(not(any(
667        target_os = "freebsd",
668        target_os = "openbsd",
669        target_os = "vita",
670        target_vendor = "apple",
671        target_os = "cygwin",
672    )))]
673    pub fn created(&self) -> io::Result<SystemTime> {
674        cfg_has_statx! {
675            if let Some(ext) = &self.statx_extra_fields {
676                return if (ext.stx_mask & libc::STATX_BTIME) != 0 {
677                    SystemTime::new(ext.stx_btime.tv_sec, ext.stx_btime.tv_nsec as i64)
678                } else {
679                    Err(io::const_error!(
680                        io::ErrorKind::Unsupported,
681                        "creation time is not available for the filesystem",
682                    ))
683                };
684            }
685        }
686
687        Err(io::const_error!(
688            io::ErrorKind::Unsupported,
689            "creation time is not available on this platform currently",
690        ))
691    }
692
693    #[cfg(target_os = "vita")]
694    pub fn created(&self) -> io::Result<SystemTime> {
695        SystemTime::new(self.stat.st_ctime as i64, 0)
696    }
697}
698
699#[cfg(any(target_os = "nto", target_os = "qnx", target_os = "wasi", target_os = "l4re"))]
700impl FileAttr {
701    pub fn modified(&self) -> io::Result<SystemTime> {
702        SystemTime::new(self.stat.st_mtim.tv_sec, self.stat.st_mtim.tv_nsec.into())
703    }
704
705    pub fn accessed(&self) -> io::Result<SystemTime> {
706        SystemTime::new(self.stat.st_atim.tv_sec, self.stat.st_atim.tv_nsec.into())
707    }
708
709    pub fn created(&self) -> io::Result<SystemTime> {
710        SystemTime::new(self.stat.st_ctim.tv_sec, self.stat.st_ctim.tv_nsec.into())
711    }
712}
713
714impl AsInner<stat64> for FileAttr {
715    #[inline]
716    fn as_inner(&self) -> &stat64 {
717        &self.stat
718    }
719}
720
721impl FilePermissions {
722    pub fn readonly(&self) -> bool {
723        // check if any class (owner, group, others) has write permission
724        self.mode & 0o222 == 0
725    }
726
727    pub fn set_readonly(&mut self, readonly: bool) {
728        if readonly {
729            // remove write permission for all classes; equivalent to `chmod a-w <file>`
730            self.mode &= !0o222;
731        } else {
732            // add write permission for all classes; equivalent to `chmod a+w <file>`
733            self.mode |= 0o222;
734        }
735    }
736    #[cfg(not(target_os = "wasi"))]
737    pub fn mode(&self) -> u32 {
738        self.mode as u32
739    }
740}
741
742impl FileTimes {
743    pub fn set_accessed(&mut self, t: SystemTime) {
744        self.accessed = Some(t);
745    }
746
747    pub fn set_modified(&mut self, t: SystemTime) {
748        self.modified = Some(t);
749    }
750
751    #[cfg(target_vendor = "apple")]
752    pub fn set_created(&mut self, t: SystemTime) {
753        self.created = Some(t);
754    }
755}
756
757impl FileType {
758    pub fn is_dir(&self) -> bool {
759        self.is(libc::S_IFDIR)
760    }
761    pub fn is_file(&self) -> bool {
762        self.is(libc::S_IFREG)
763    }
764    pub fn is_symlink(&self) -> bool {
765        self.is(libc::S_IFLNK)
766    }
767
768    pub fn is(&self, mode: mode_t) -> bool {
769        self.masked() == mode
770    }
771
772    fn masked(&self) -> mode_t {
773        self.mode & libc::S_IFMT
774    }
775}
776
777impl fmt::Debug for FileType {
778    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
779        let FileType { mode } = self;
780        f.debug_struct("FileType").field("mode", &Mode(*mode)).finish()
781    }
782}
783
784impl FromInner<u32> for FilePermissions {
785    fn from_inner(mode: u32) -> FilePermissions {
786        FilePermissions { mode: mode as mode_t }
787    }
788}
789
790impl fmt::Debug for FilePermissions {
791    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
792        let FilePermissions { mode } = self;
793        f.debug_struct("FilePermissions").field("mode", &Mode(*mode)).finish()
794    }
795}
796
797impl fmt::Debug for ReadDir {
798    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
799        // This will only be called from std::fs::ReadDir, which will add a "ReadDir()" frame.
800        // Thus the result will be e g 'ReadDir("/home")'
801        fmt::Debug::fmt(&*self.inner.root, f)
802    }
803}
804
805impl Iterator for ReadDir {
806    type Item = io::Result<DirEntry>;
807
808    fn next(&mut self) -> Option<io::Result<DirEntry>> {
809        if self.end_of_stream {
810            return None;
811        }
812
813        unsafe {
814            loop {
815                // POSIX.1-2024 formalized what was already guaranteed by a lot
816                // of implementations and required readdir() to be thread-safe as
817                // long as an individual DIR* is not accessed concurrently. Taking
818                // a mutable reference to the `ReadDir` iterator prevents that.
819                // Even POSIX.1-1994 specified that the data in the returned
820                // dirent
821                // > is not overwritten by another call to readdir() on a
822                // > different directory stream.
823                //
824                // and that guarantee together with the requirement that the
825                // underlying syscalls need to be thread-safe because of readdir_r
826                // make it very unlikely for an implementation to be non-conforming.
827                // Nevertheless, there are still some platforms where we either
828                // cannot confirm `readdir` to be thread-safe or know that it
829                // isn't.
830                cfg_select! {
831                    any(
832                        target_os = "espidf", // readdir truly isn't thread-safe.
833                        target_os = "lynxos178",
834                        target_os = "qurt",
835                        target_os = "rtems",
836                        target_os = "vxworks",
837                    ) => {
838                        use crate::mem::MaybeUninit;
839
840                        let mut entry = MaybeUninit::uninit();
841                        let mut entry_ptr: *mut dirent64 = ptr::null_mut();
842                        let err =
843                            libc::readdir_r(self.inner.dirp.0, entry.as_mut_ptr(), &mut entry_ptr);
844                        if err != 0 {
845                            if entry_ptr.is_null() {
846                                // We encountered an error (which will be returned in this iteration), but
847                                // we also reached the end of the directory stream. The `end_of_stream`
848                                // flag is enabled to make sure that we return `None` in the next iteration
849                                // (instead of looping forever)
850                                self.end_of_stream = true;
851                            }
852                            return Some(Err(Error::from_raw_os_error(err)));
853                        }
854                        if entry_ptr.is_null() {
855                            return None;
856                        }
857
858                        let entry_ptr = entry_ptr.cast_const();
859                    }
860                    _ => {
861                        #[cfg(not(any(
862                            all(target_os = "linux", not(target_env = "musl")),
863                            target_os = "hurd",
864                            target_os = "l4re",
865                        )))]
866                        use libc::readdir as readdir64;
867                        #[cfg(any(
868                            all(target_os = "linux", not(target_env = "musl")),
869                            target_os = "hurd",
870                            target_os = "l4re"
871                        ))]
872                        use libc::readdir64;
873
874                        use crate::sys::io::{errno, set_errno};
875
876                        set_errno(0);
877                        let entry_ptr: *const dirent64 = readdir64(self.inner.dirp.0);
878                        if entry_ptr.is_null() {
879                            // We either encountered an error, or reached the end. Either way,
880                            // the next call to next() should return None.
881                            self.end_of_stream = true;
882
883                            // To distinguish between errors and end-of-directory, we had to clear
884                            // errno beforehand to check for an error now.
885                            return match errno() {
886                                0 => None,
887                                e => Some(Err(Error::from_raw_os_error(e))),
888                            };
889                        }
890                    }
891                }
892
893                // The dirent64 struct is a weird imaginary thing that isn't ever supposed
894                // to be worked with by value. Its trailing d_name field is declared
895                // variously as [c_char; 256] or [c_char; 1] on different systems but
896                // either way that size is meaningless; only the offset of d_name is
897                // meaningful. The dirent64 pointers that libc returns from readdir64 are
898                // allowed to point to allocations smaller _or_ LARGER than implied by the
899                // definition of the struct.
900                //
901                // As such, we need to be even more careful with dirent64 than if its
902                // contents were "simply" partially initialized data.
903                //
904                // Like for uninitialized contents, converting entry_ptr to `&dirent64`
905                // would not be legal. However, we can use `&raw const (*entry_ptr).d_name`
906                // to refer the fields individually, because that operation is equivalent
907                // to `byte_offset` and thus does not require the full extent of `*entry_ptr`
908                // to be in bounds of the same allocation, only the offset of the field
909                // being referenced.
910
911                // d_name is guaranteed to be null-terminated.
912                let name = CStr::from_ptr((&raw const (*entry_ptr).d_name).cast());
913                let name_bytes = name.to_bytes();
914                if name_bytes == b"." || name_bytes == b".." {
915                    continue;
916                }
917
918                // When loading from a field, we can skip the `&raw const`; `(*entry_ptr).d_ino` as
919                // a value expression will do the right thing: `byte_offset` to the field and then
920                // only access those bytes.
921                let entry = dirent64_min {
922                    #[cfg(any(
923                        target_os = "dragonfly",
924                        target_os = "freebsd",
925                        target_os = "netbsd",
926                        target_os = "openbsd",
927                    ))]
928                    d_ino: (*entry_ptr).d_fileno,
929                    #[cfg(any(target_os = "nuttx", target_os = "vita",))]
930                    d_ino: 0,
931                    #[cfg(not(any(
932                        target_os = "dragonfly",
933                        target_os = "freebsd",
934                        target_os = "netbsd",
935                        target_os = "nuttx",
936                        target_os = "openbsd",
937                        target_os = "vita",
938                    )))]
939                    d_ino: (*entry_ptr).d_ino as u64,
940                    #[cfg(not(any(
941                        target_os = "solaris",
942                        target_os = "illumos",
943                        target_os = "haiku",
944                        target_os = "vxworks",
945                        target_os = "aix",
946                        target_os = "nto",
947                        target_os = "qnx",
948                        target_os = "vita",
949                    )))]
950                    d_type: (*entry_ptr).d_type as u8,
951                };
952
953                return Some(Ok(DirEntry {
954                    entry,
955                    name: name.to_owned(),
956                    dir: Arc::clone(&self.inner),
957                }));
958            }
959        }
960    }
961}
962
963/// Aborts the process if a file descriptor is not open, if debug asserts are enabled
964///
965/// Many IO syscalls can't be fully trusted about EBADF error codes because those
966/// might get bubbled up from a remote FUSE server rather than the file descriptor
967/// in the current process being invalid.
968///
969/// So we check file flags instead which live on the file descriptor and not the underlying file.
970/// The downside is that it costs an extra syscall, so we only do it for debug.
971#[inline]
972pub(crate) fn debug_assert_fd_is_open(fd: RawFd) {
973    use crate::sys::io::errno;
974
975    // this is similar to assert_unsafe_precondition!() but it doesn't require const
976    if core::ub_checks::check_library_ub() {
977        if unsafe { libc::fcntl(fd, libc::F_GETFD) } == -1 && errno() == libc::EBADF {
978            rtabort!("IO Safety violation: owned file descriptor already closed");
979        }
980    }
981}
982
983impl Drop for DirStream {
984    fn drop(&mut self) {
985        // dirfd isn't supported everywhere
986        #[cfg(not(any(
987            miri,
988            target_os = "redox",
989            target_os = "nto",
990            target_os = "qnx",
991            target_os = "vita",
992            target_os = "hurd",
993            target_os = "espidf",
994            target_os = "horizon",
995            target_os = "vxworks",
996            target_os = "rtems",
997            target_os = "nuttx",
998        )))]
999        {
1000            let fd = unsafe { libc::dirfd(self.0) };
1001            debug_assert_fd_is_open(fd);
1002        }
1003        let r = unsafe { libc::closedir(self.0) };
1004        assert!(
1005            r == 0 || crate::io::Error::last_os_error().is_interrupted(),
1006            "unexpected error during closedir: {:?}",
1007            crate::io::Error::last_os_error()
1008        );
1009    }
1010}
1011
1012// SAFETY: `int dirfd (DIR *dirstream)` is MT-safe, implying that the pointer
1013// may be safely sent among threads.
1014unsafe impl Send for DirStream {}
1015unsafe impl Sync for DirStream {}
1016
1017impl DirEntry {
1018    pub fn path(&self) -> PathBuf {
1019        self.dir.root.join(self.file_name_os_str())
1020    }
1021
1022    pub fn file_name(&self) -> OsString {
1023        self.file_name_os_str().to_os_string()
1024    }
1025
1026    pub fn metadata(&self) -> io::Result<FileAttr> {
1027        cfg_select! {
1028            // Use directory handle where possible
1029            all(
1030                any(
1031                    all(target_os = "linux", not(target_env = "musl")),
1032                    target_os = "android",
1033                    target_os = "fuchsia",
1034                    target_os = "hurd",
1035                    target_os = "illumos",
1036                    target_vendor = "apple",
1037                ),
1038                not(miri) // no dirfd on Miri
1039            ) => {
1040                let fd = cvt(unsafe { dirfd(self.dir.dirp.0) })?;
1041
1042                // Make this FD into a directory handle. We don't actually drop it,
1043                // so having an `OwnedFd` is fine.
1044                let dir_handle =
1045                    mem::ManuallyDrop::new(dir::Dir(unsafe { OwnedFd::from_raw_fd(fd) }));
1046
1047                dir_handle.metadata_c(&self.name, /* symlink_nofollow */ true)
1048            }
1049
1050            // Fallback based on path
1051            _ => run_path_with_cstr(&self.path(), &lstat),
1052        }
1053    }
1054
1055    pub fn file_type(&self) -> io::Result<FileType> {
1056        // Use `entry.d_type` if available.
1057        #[cfg(not(any(
1058            target_os = "solaris",
1059            target_os = "illumos",
1060            target_os = "haiku",
1061            target_os = "vxworks",
1062            target_os = "aix",
1063            target_os = "nto",
1064            target_os = "qnx",
1065            target_os = "vita",
1066            target_os = "l4re",
1067        )))]
1068        match self.entry.d_type {
1069            libc::DT_CHR => return Ok(FileType { mode: libc::S_IFCHR }),
1070            libc::DT_FIFO => return Ok(FileType { mode: libc::S_IFIFO }),
1071            libc::DT_LNK => return Ok(FileType { mode: libc::S_IFLNK }),
1072            libc::DT_REG => return Ok(FileType { mode: libc::S_IFREG }),
1073            libc::DT_SOCK => return Ok(FileType { mode: libc::S_IFSOCK }),
1074            libc::DT_DIR => return Ok(FileType { mode: libc::S_IFDIR }),
1075            libc::DT_BLK => return Ok(FileType { mode: libc::S_IFBLK }),
1076            _ => {}
1077        }
1078
1079        // Fall back to loading the metadata.
1080        self.metadata().map(|m| m.file_type())
1081    }
1082
1083    pub fn ino(&self) -> u64 {
1084        self.entry.d_ino
1085    }
1086
1087    pub fn file_name_os_str(&self) -> &OsStr {
1088        OsStr::from_bytes(self.name.as_bytes())
1089    }
1090}
1091
1092impl OpenOptions {
1093    pub fn new() -> OpenOptions {
1094        OpenOptions {
1095            // generic
1096            read: false,
1097            write: false,
1098            append: false,
1099            truncate: false,
1100            create: false,
1101            create_new: false,
1102            // system-specific
1103            custom_flags: 0,
1104            mode: 0o666,
1105        }
1106    }
1107
1108    pub fn read(&mut self, read: bool) {
1109        self.read = read;
1110    }
1111    pub fn write(&mut self, write: bool) {
1112        self.write = write;
1113    }
1114    pub fn append(&mut self, append: bool) {
1115        self.append = append;
1116    }
1117    pub fn truncate(&mut self, truncate: bool) {
1118        self.truncate = truncate;
1119    }
1120    pub fn create(&mut self, create: bool) {
1121        self.create = create;
1122    }
1123    pub fn create_new(&mut self, create_new: bool) {
1124        self.create_new = create_new;
1125    }
1126
1127    pub fn custom_flags(&mut self, flags: i32) {
1128        self.custom_flags = flags;
1129    }
1130    #[cfg(not(target_os = "wasi"))]
1131    pub fn mode(&mut self, mode: u32) {
1132        self.mode = mode as mode_t;
1133    }
1134
1135    fn get_access_mode(&self) -> io::Result<c_int> {
1136        match (self.read, self.write, self.append) {
1137            (true, false, false) => Ok(libc::O_RDONLY),
1138            (false, true, false) => Ok(libc::O_WRONLY),
1139            (true, true, false) => Ok(libc::O_RDWR),
1140            (false, _, true) => Ok(libc::O_WRONLY | libc::O_APPEND),
1141            (true, _, true) => Ok(libc::O_RDWR | libc::O_APPEND),
1142            (false, false, false) => {
1143                // If no access mode is set, check if any creation flags are set
1144                // to provide a more descriptive error message
1145                if self.create || self.create_new || self.truncate {
1146                    Err(io::Error::new(
1147                        io::ErrorKind::InvalidInput,
1148                        "creating or truncating a file requires write or append access",
1149                    ))
1150                } else {
1151                    Err(io::Error::new(
1152                        io::ErrorKind::InvalidInput,
1153                        "must specify at least one of read, write, or append access",
1154                    ))
1155                }
1156            }
1157        }
1158    }
1159
1160    fn get_creation_mode(&self) -> io::Result<c_int> {
1161        match (self.write, self.append) {
1162            (true, false) => {}
1163            (false, false) => {
1164                if self.truncate || self.create || self.create_new {
1165                    return Err(io::Error::new(
1166                        io::ErrorKind::InvalidInput,
1167                        "creating or truncating a file requires write or append access",
1168                    ));
1169                }
1170            }
1171            (_, true) => {
1172                if self.truncate && !self.create_new {
1173                    return Err(io::Error::new(
1174                        io::ErrorKind::InvalidInput,
1175                        "append and truncate cannot both be enabled",
1176                    ));
1177                }
1178            }
1179        }
1180
1181        Ok(match (self.create, self.truncate, self.create_new) {
1182            (false, false, false) => 0,
1183            (true, false, false) => libc::O_CREAT,
1184            (false, true, false) => libc::O_TRUNC,
1185            (true, true, false) => libc::O_CREAT | libc::O_TRUNC,
1186            (_, _, true) => libc::O_CREAT | libc::O_EXCL,
1187        })
1188    }
1189}
1190
1191impl fmt::Debug for OpenOptions {
1192    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1193        let OpenOptions { read, write, append, truncate, create, create_new, custom_flags, mode } =
1194            self;
1195        f.debug_struct("OpenOptions")
1196            .field("read", read)
1197            .field("write", write)
1198            .field("append", append)
1199            .field("truncate", truncate)
1200            .field("create", create)
1201            .field("create_new", create_new)
1202            .field("custom_flags", custom_flags)
1203            .field("mode", &Mode(*mode))
1204            .finish()
1205    }
1206}
1207
1208impl File {
1209    pub fn open(path: &Path, opts: &OpenOptions) -> io::Result<File> {
1210        run_path_with_cstr(path, &|path| File::open_c(path, opts))
1211    }
1212
1213    pub fn open_c(path: &CStr, opts: &OpenOptions) -> io::Result<File> {
1214        let flags = libc::O_CLOEXEC
1215            | opts.get_access_mode()?
1216            | opts.get_creation_mode()?
1217            | (opts.custom_flags as c_int & !libc::O_ACCMODE);
1218        // The third argument of `open64` is documented to have type `mode_t`. On
1219        // some platforms (like macOS, where `open64` is actually `open`), `mode_t` is `u16`.
1220        // However, since this is a variadic function, C integer promotion rules mean that on
1221        // the ABI level, this still gets passed as `c_int` (aka `u32` on Unix platforms).
1222        let fd = cvt_r(|| unsafe { open64(path.as_ptr(), flags, opts.mode as c_int) })?;
1223        Ok(File(unsafe { FileDesc::from_raw_fd(fd) }))
1224    }
1225
1226    pub fn file_attr(&self) -> io::Result<FileAttr> {
1227        let fd = self.as_raw_fd();
1228
1229        cfg_has_statx! {
1230            if let Some(ret) = unsafe { try_statx(
1231                fd,
1232                c"".as_ptr() as *const c_char,
1233                libc::AT_EMPTY_PATH | libc::AT_STATX_SYNC_AS_STAT,
1234                libc::STATX_BASIC_STATS | libc::STATX_BTIME,
1235            ) } {
1236                return ret;
1237            }
1238        }
1239
1240        let mut stat: stat64 = unsafe { mem::zeroed() };
1241        cvt(unsafe { fstat64(fd, &mut stat) })?;
1242        Ok(FileAttr::from_stat64(stat))
1243    }
1244
1245    pub fn fsync(&self) -> io::Result<()> {
1246        cvt_r(|| unsafe { os_fsync(self.as_raw_fd()) })?;
1247        return Ok(());
1248
1249        #[cfg(target_vendor = "apple")]
1250        unsafe fn os_fsync(fd: c_int) -> c_int {
1251            libc::fcntl(fd, libc::F_FULLFSYNC)
1252        }
1253        #[cfg(not(target_vendor = "apple"))]
1254        unsafe fn os_fsync(fd: c_int) -> c_int {
1255            libc::fsync(fd)
1256        }
1257    }
1258
1259    pub fn datasync(&self) -> io::Result<()> {
1260        cvt_r(|| unsafe { os_datasync(self.as_raw_fd()) })?;
1261        return Ok(());
1262
1263        #[cfg(target_vendor = "apple")]
1264        unsafe fn os_datasync(fd: c_int) -> c_int {
1265            libc::fcntl(fd, libc::F_FULLFSYNC)
1266        }
1267        #[cfg(any(
1268            target_os = "freebsd",
1269            target_os = "fuchsia",
1270            target_os = "linux",
1271            target_os = "cygwin",
1272            target_os = "android",
1273            target_os = "netbsd",
1274            target_os = "openbsd",
1275            target_os = "nto",
1276            target_os = "qnx",
1277            target_os = "hurd",
1278            target_os = "l4re",
1279        ))]
1280        unsafe fn os_datasync(fd: c_int) -> c_int {
1281            libc::fdatasync(fd)
1282        }
1283        #[cfg(not(any(
1284            target_os = "android",
1285            target_os = "fuchsia",
1286            target_os = "freebsd",
1287            target_os = "linux",
1288            target_os = "cygwin",
1289            target_os = "netbsd",
1290            target_os = "openbsd",
1291            target_os = "nto",
1292            target_os = "qnx",
1293            target_os = "hurd",
1294            target_os = "l4re",
1295            target_vendor = "apple",
1296        )))]
1297        unsafe fn os_datasync(fd: c_int) -> c_int {
1298            libc::fsync(fd)
1299        }
1300    }
1301
1302    pub fn lock(&self) -> io::Result<()> {
1303        cfg_select! {
1304            any(
1305                target_os = "freebsd",
1306                target_os = "fuchsia",
1307                target_os = "hurd",
1308                target_os = "linux",
1309                target_os = "netbsd",
1310                target_os = "openbsd",
1311                target_os = "cygwin",
1312                target_os = "illumos",
1313                target_os = "aix",
1314                target_os = "android",
1315                target_os = "redox",
1316                target_vendor = "apple",
1317            ) => {
1318                cvt(unsafe { libc::flock(self.as_raw_fd(), libc::LOCK_EX) })?;
1319                Ok(())
1320            }
1321            _ => Err(io::const_error!(io::ErrorKind::Unsupported, "lock() not supported")),
1322        }
1323    }
1324
1325    pub fn lock_shared(&self) -> io::Result<()> {
1326        cfg_select! {
1327            any(
1328                target_os = "freebsd",
1329                target_os = "fuchsia",
1330                target_os = "hurd",
1331                target_os = "linux",
1332                target_os = "netbsd",
1333                target_os = "openbsd",
1334                target_os = "cygwin",
1335                target_os = "illumos",
1336                target_os = "aix",
1337                target_os = "android",
1338                target_os = "redox",
1339                target_vendor = "apple",
1340            ) => {
1341                cvt(unsafe { libc::flock(self.as_raw_fd(), libc::LOCK_SH) })?;
1342                Ok(())
1343            }
1344            _ => Err(io::const_error!(io::ErrorKind::Unsupported, "lock_shared() not supported")),
1345        }
1346    }
1347
1348    pub fn try_lock(&self) -> Result<(), TryLockError> {
1349        cfg_select! {
1350            any(
1351                target_os = "freebsd",
1352                target_os = "fuchsia",
1353                target_os = "hurd",
1354                target_os = "linux",
1355                target_os = "netbsd",
1356                target_os = "openbsd",
1357                target_os = "cygwin",
1358                target_os = "illumos",
1359                target_os = "aix",
1360                target_os = "android",
1361                target_os = "redox",
1362                target_vendor = "apple",
1363            ) => {
1364                let result =
1365                    cvt(unsafe { libc::flock(self.as_raw_fd(), libc::LOCK_EX | libc::LOCK_NB) });
1366                if let Err(err) = result {
1367                    if err.kind() == io::ErrorKind::WouldBlock {
1368                        Err(TryLockError::WouldBlock)
1369                    } else {
1370                        Err(TryLockError::Error(err))
1371                    }
1372                } else {
1373                    Ok(())
1374                }
1375            }
1376            _ => Err(TryLockError::Error(io::const_error!(
1377                io::ErrorKind::Unsupported,
1378                "try_lock() not supported"
1379            ))),
1380        }
1381    }
1382
1383    pub fn try_lock_shared(&self) -> Result<(), TryLockError> {
1384        cfg_select! {
1385            any(
1386                target_os = "freebsd",
1387                target_os = "fuchsia",
1388                target_os = "hurd",
1389                target_os = "linux",
1390                target_os = "netbsd",
1391                target_os = "openbsd",
1392                target_os = "cygwin",
1393                target_os = "illumos",
1394                target_os = "aix",
1395                target_os = "android",
1396                target_os = "redox",
1397                target_vendor = "apple",
1398            ) => {
1399                let result =
1400                    cvt(unsafe { libc::flock(self.as_raw_fd(), libc::LOCK_SH | libc::LOCK_NB) });
1401                if let Err(err) = result {
1402                    if err.kind() == io::ErrorKind::WouldBlock {
1403                        Err(TryLockError::WouldBlock)
1404                    } else {
1405                        Err(TryLockError::Error(err))
1406                    }
1407                } else {
1408                    Ok(())
1409                }
1410            }
1411            _ => Err(TryLockError::Error(io::const_error!(
1412                io::ErrorKind::Unsupported,
1413                "try_lock_shared() not supported"
1414            ))),
1415        }
1416    }
1417
1418    pub fn unlock(&self) -> io::Result<()> {
1419        cfg_select! {
1420            any(
1421                target_os = "freebsd",
1422                target_os = "fuchsia",
1423                target_os = "hurd",
1424                target_os = "linux",
1425                target_os = "netbsd",
1426                target_os = "openbsd",
1427                target_os = "cygwin",
1428                target_os = "illumos",
1429                target_os = "aix",
1430                target_os = "android",
1431                target_os = "redox",
1432                target_vendor = "apple",
1433            ) => {
1434                cvt(unsafe { libc::flock(self.as_raw_fd(), libc::LOCK_UN) })?;
1435                Ok(())
1436            }
1437            _ => Err(io::const_error!(io::ErrorKind::Unsupported, "unlock() not supported")),
1438        }
1439    }
1440
1441    pub fn truncate(&self, size: u64) -> io::Result<()> {
1442        let size: off64_t =
1443            size.try_into().map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
1444        cvt_r(|| unsafe { ftruncate64(self.as_raw_fd(), size) }).map(drop)
1445    }
1446
1447    pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
1448        self.0.read(buf)
1449    }
1450
1451    pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
1452        self.0.read_vectored(bufs)
1453    }
1454
1455    #[inline]
1456    pub fn is_read_vectored(&self) -> bool {
1457        self.0.is_read_vectored()
1458    }
1459
1460    pub fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
1461        self.0.read_at(buf, offset)
1462    }
1463
1464    pub fn read_buf(&self, cursor: BorrowedCursor<'_, u8>) -> io::Result<()> {
1465        self.0.read_buf(cursor)
1466    }
1467
1468    pub fn read_buf_at(&self, cursor: BorrowedCursor<'_, u8>, offset: u64) -> io::Result<()> {
1469        self.0.read_buf_at(cursor, offset)
1470    }
1471
1472    pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
1473        self.0.read_vectored_at(bufs, offset)
1474    }
1475
1476    pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
1477        self.0.write(buf)
1478    }
1479
1480    pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
1481        self.0.write_vectored(bufs)
1482    }
1483
1484    #[inline]
1485    pub fn is_write_vectored(&self) -> bool {
1486        self.0.is_write_vectored()
1487    }
1488
1489    pub fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
1490        self.0.write_at(buf, offset)
1491    }
1492
1493    pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
1494        self.0.write_vectored_at(bufs, offset)
1495    }
1496
1497    #[inline]
1498    pub fn flush(&self) -> io::Result<()> {
1499        Ok(())
1500    }
1501
1502    pub fn seek(&self, pos: SeekFrom) -> io::Result<u64> {
1503        let (whence, pos) = match pos {
1504            // Casting to `i64` is fine, too large values will end up as
1505            // negative which will cause an error in `lseek64`.
1506            SeekFrom::Start(off) => (libc::SEEK_SET, off as i64),
1507            SeekFrom::End(off) => (libc::SEEK_END, off),
1508            SeekFrom::Current(off) => (libc::SEEK_CUR, off),
1509        };
1510        let n = cvt(unsafe { lseek64(self.as_raw_fd(), pos as off64_t, whence) })?;
1511        Ok(n as u64)
1512    }
1513
1514    pub fn size(&self) -> Option<io::Result<u64>> {
1515        match self.file_attr().map(|attr| attr.size()) {
1516            // Fall back to default implementation if the returned size is 0,
1517            // we might be in a proc mount.
1518            Ok(0) => None,
1519            result => Some(result),
1520        }
1521    }
1522
1523    pub fn tell(&self) -> io::Result<u64> {
1524        self.seek(SeekFrom::Current(0))
1525    }
1526
1527    pub fn duplicate(&self) -> io::Result<File> {
1528        self.0.duplicate().map(File)
1529    }
1530
1531    pub fn set_permissions(&self, perm: FilePermissions) -> io::Result<()> {
1532        cvt_r(|| unsafe { libc::fchmod(self.as_raw_fd(), perm.mode) })?;
1533        Ok(())
1534    }
1535
1536    pub fn set_times(&self, times: FileTimes) -> io::Result<()> {
1537        cfg_select! {
1538            any(
1539                target_os = "redox",
1540                target_os = "espidf",
1541                target_os = "horizon",
1542                target_os = "nuttx",
1543                target_os = "l4re"
1544            ) => {
1545                // Redox doesn't appear to support `UTIME_OMIT`.
1546                // ESP-IDF and HorizonOS do not support `futimens` at all and the behavior for those OS is therefore
1547                // the same as for Redox.
1548                let _ = times;
1549                Err(io::const_error!(
1550                    io::ErrorKind::Unsupported,
1551                    "setting file times not supported",
1552                ))
1553            }
1554            target_vendor = "apple" => {
1555                let ta = TimesAttrlist::from_times(&times)?;
1556                cvt(unsafe {
1557                    libc::fsetattrlist(
1558                        self.as_raw_fd(),
1559                        ta.attrlist(),
1560                        ta.times_buf(),
1561                        ta.times_buf_size(),
1562                        0,
1563                    )
1564                })?;
1565                Ok(())
1566            }
1567            target_os = "android" => {
1568                let times = [
1569                    file_time_to_timespec(times.accessed)?,
1570                    file_time_to_timespec(times.modified)?,
1571                ];
1572                // futimens requires Android API level 19
1573                cvt(unsafe {
1574                    weak!(
1575                        fn futimens(fd: c_int, times: *const libc::timespec) -> c_int;
1576                    );
1577                    match futimens.get() {
1578                        Some(futimens) => futimens(self.as_raw_fd(), times.as_ptr()),
1579                        None => {
1580                            return Err(io::const_error!(
1581                                io::ErrorKind::Unsupported,
1582                                "setting file times requires Android API level >= 19",
1583                            ));
1584                        }
1585                    }
1586                })?;
1587                Ok(())
1588            }
1589            _ => {
1590                #[cfg(all(
1591                    target_os = "linux",
1592                    target_env = "gnu",
1593                    target_pointer_width = "32",
1594                    not(target_arch = "riscv32")
1595                ))]
1596                {
1597                    use crate::sys::pal::time::__timespec64;
1598                    use crate::sys::pal::weak::weak;
1599
1600                    // Added in glibc 2.34
1601                    weak!(
1602                        fn __futimens64(fd: c_int, times: *const __timespec64) -> c_int;
1603                    );
1604
1605                    if let Some(futimens64) = __futimens64.get() {
1606                        let to_timespec = |time: Option<SystemTime>| {
1607                            time.map(|time| time.t.to_timespec64())
1608                                .unwrap_or(__timespec64::new(0, libc::UTIME_OMIT as _))
1609                        };
1610                        let times = [to_timespec(times.accessed), to_timespec(times.modified)];
1611                        cvt(unsafe { futimens64(self.as_raw_fd(), times.as_ptr()) })?;
1612                        return Ok(());
1613                    }
1614                }
1615                let times = [
1616                    file_time_to_timespec(times.accessed)?,
1617                    file_time_to_timespec(times.modified)?,
1618                ];
1619                cvt(unsafe { libc::futimens(self.as_raw_fd(), times.as_ptr()) })?;
1620                Ok(())
1621            }
1622        }
1623    }
1624}
1625
1626#[cfg(not(any(
1627    target_os = "redox",
1628    target_os = "espidf",
1629    target_os = "horizon",
1630    target_os = "nuttx",
1631)))]
1632fn file_time_to_timespec(time: Option<SystemTime>) -> io::Result<libc::timespec> {
1633    match time {
1634        Some(time) if let Some(ts) = time.t.to_timespec() => Ok(ts),
1635        Some(time) if time > crate::sys::time::UNIX_EPOCH => Err(io::const_error!(
1636            io::ErrorKind::InvalidInput,
1637            "timestamp is too large to set as a file time",
1638        )),
1639        Some(_) => Err(io::const_error!(
1640            io::ErrorKind::InvalidInput,
1641            "timestamp is too small to set as a file time",
1642        )),
1643        None => Ok({
1644            let mut ts = libc::timespec::default();
1645            ts.tv_sec = 0;
1646            ts.tv_nsec = libc::UTIME_OMIT as _;
1647            ts
1648        }),
1649    }
1650}
1651
1652#[cfg(target_vendor = "apple")]
1653struct TimesAttrlist {
1654    buf: [mem::MaybeUninit<libc::timespec>; 3],
1655    attrlist: libc::attrlist,
1656    num_times: usize,
1657}
1658
1659#[cfg(target_vendor = "apple")]
1660impl TimesAttrlist {
1661    fn from_times(times: &FileTimes) -> io::Result<Self> {
1662        let mut this = Self {
1663            buf: [mem::MaybeUninit::<libc::timespec>::uninit(); 3],
1664            attrlist: unsafe { mem::zeroed() },
1665            num_times: 0,
1666        };
1667        this.attrlist.bitmapcount = libc::ATTR_BIT_MAP_COUNT;
1668        if times.created.is_some() {
1669            this.buf[this.num_times].write(file_time_to_timespec(times.created)?);
1670            this.num_times += 1;
1671            this.attrlist.commonattr |= libc::ATTR_CMN_CRTIME;
1672        }
1673        if times.modified.is_some() {
1674            this.buf[this.num_times].write(file_time_to_timespec(times.modified)?);
1675            this.num_times += 1;
1676            this.attrlist.commonattr |= libc::ATTR_CMN_MODTIME;
1677        }
1678        if times.accessed.is_some() {
1679            this.buf[this.num_times].write(file_time_to_timespec(times.accessed)?);
1680            this.num_times += 1;
1681            this.attrlist.commonattr |= libc::ATTR_CMN_ACCTIME;
1682        }
1683        Ok(this)
1684    }
1685
1686    fn attrlist(&self) -> *mut libc::c_void {
1687        (&raw const self.attrlist).cast::<libc::c_void>().cast_mut()
1688    }
1689
1690    fn times_buf(&self) -> *mut libc::c_void {
1691        self.buf.as_ptr().cast::<libc::c_void>().cast_mut()
1692    }
1693
1694    fn times_buf_size(&self) -> usize {
1695        self.num_times * size_of::<libc::timespec>()
1696    }
1697}
1698
1699impl DirBuilder {
1700    pub fn new() -> DirBuilder {
1701        DirBuilder { mode: 0o777 }
1702    }
1703
1704    pub fn mkdir(&self, p: &Path) -> io::Result<()> {
1705        run_path_with_cstr(p, &|p| cvt(unsafe { libc::mkdir(p.as_ptr(), self.mode) }).map(|_| ()))
1706    }
1707
1708    #[cfg(not(target_os = "wasi"))]
1709    pub fn set_mode(&mut self, mode: u32) {
1710        self.mode = mode as mode_t;
1711    }
1712}
1713
1714impl fmt::Debug for DirBuilder {
1715    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1716        let DirBuilder { mode } = self;
1717        f.debug_struct("DirBuilder").field("mode", &Mode(*mode)).finish()
1718    }
1719}
1720
1721impl AsInner<FileDesc> for File {
1722    #[inline]
1723    fn as_inner(&self) -> &FileDesc {
1724        &self.0
1725    }
1726}
1727
1728impl AsInnerMut<FileDesc> for File {
1729    #[inline]
1730    fn as_inner_mut(&mut self) -> &mut FileDesc {
1731        &mut self.0
1732    }
1733}
1734
1735impl IntoInner<FileDesc> for File {
1736    fn into_inner(self) -> FileDesc {
1737        self.0
1738    }
1739}
1740
1741impl FromInner<FileDesc> for File {
1742    fn from_inner(file_desc: FileDesc) -> Self {
1743        Self(file_desc)
1744    }
1745}
1746
1747impl AsFd for File {
1748    #[inline]
1749    fn as_fd(&self) -> BorrowedFd<'_> {
1750        self.0.as_fd()
1751    }
1752}
1753
1754impl AsRawFd for File {
1755    #[inline]
1756    fn as_raw_fd(&self) -> RawFd {
1757        self.0.as_raw_fd()
1758    }
1759}
1760
1761impl IntoRawFd for File {
1762    fn into_raw_fd(self) -> RawFd {
1763        self.0.into_raw_fd()
1764    }
1765}
1766
1767impl FromRawFd for File {
1768    unsafe fn from_raw_fd(raw_fd: RawFd) -> Self {
1769        Self(FromRawFd::from_raw_fd(raw_fd))
1770    }
1771}
1772
1773impl fmt::Debug for File {
1774    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1775        let fd = self.as_raw_fd();
1776        let mut b = debug_path_fd(fd, f, "File");
1777        b.finish()
1778    }
1779}
1780
1781// Format in octal, followed by the mode format used in `ls -l`.
1782//
1783// References:
1784//   https://pubs.opengroup.org/onlinepubs/9799919799/utilities/ls.html
1785//   https://www.gnu.org/software/libc/manual/html_node/Testing-File-Type.html
1786//   https://www.gnu.org/software/libc/manual/html_node/Permission-Bits.html
1787//
1788// Example:
1789//   0o100664 (-rw-rw-r--)
1790impl fmt::Debug for Mode {
1791    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1792        let Self(mode) = *self;
1793        write!(f, "0o{mode:06o}")?;
1794
1795        let entry_type = match mode & libc::S_IFMT {
1796            libc::S_IFDIR => 'd',
1797            libc::S_IFBLK => 'b',
1798            libc::S_IFCHR => 'c',
1799            libc::S_IFLNK => 'l',
1800            libc::S_IFIFO => 'p',
1801            libc::S_IFREG => '-',
1802            _ => return Ok(()),
1803        };
1804
1805        f.write_str(" (")?;
1806        f.write_char(entry_type)?;
1807
1808        // Owner permissions
1809        f.write_char(if mode & libc::S_IRUSR != 0 { 'r' } else { '-' })?;
1810        f.write_char(if mode & libc::S_IWUSR != 0 { 'w' } else { '-' })?;
1811        let owner_executable = mode & libc::S_IXUSR != 0;
1812        let setuid = mode as c_int & libc::S_ISUID as c_int != 0;
1813        f.write_char(match (owner_executable, setuid) {
1814            (true, true) => 's',  // executable and setuid
1815            (false, true) => 'S', // setuid
1816            (true, false) => 'x', // executable
1817            (false, false) => '-',
1818        })?;
1819
1820        // Group permissions
1821        f.write_char(if mode & libc::S_IRGRP != 0 { 'r' } else { '-' })?;
1822        f.write_char(if mode & libc::S_IWGRP != 0 { 'w' } else { '-' })?;
1823        let group_executable = mode & libc::S_IXGRP != 0;
1824        let setgid = mode as c_int & libc::S_ISGID as c_int != 0;
1825        f.write_char(match (group_executable, setgid) {
1826            (true, true) => 's',  // executable and setgid
1827            (false, true) => 'S', // setgid
1828            (true, false) => 'x', // executable
1829            (false, false) => '-',
1830        })?;
1831
1832        // Other permissions
1833        f.write_char(if mode & libc::S_IROTH != 0 { 'r' } else { '-' })?;
1834        f.write_char(if mode & libc::S_IWOTH != 0 { 'w' } else { '-' })?;
1835        let other_executable = mode & libc::S_IXOTH != 0;
1836        let sticky = mode as c_int & libc::S_ISVTX as c_int != 0;
1837        f.write_char(match (entry_type, other_executable, sticky) {
1838            ('d', true, true) => 't',  // searchable and restricted deletion
1839            ('d', false, true) => 'T', // restricted deletion
1840            (_, true, _) => 'x',       // executable
1841            (_, false, _) => '-',
1842        })?;
1843
1844        f.write_char(')')
1845    }
1846}
1847
1848pub fn readdir(path: &Path) -> io::Result<ReadDir> {
1849    let ptr = run_path_with_cstr(path, &|p| unsafe { Ok(libc::opendir(p.as_ptr())) })?;
1850    if ptr.is_null() {
1851        Err(Error::last_os_error())
1852    } else {
1853        let root = path.to_path_buf();
1854        let inner = InnerReadDir { dirp: DirStream(ptr), root };
1855        Ok(ReadDir::new(inner))
1856    }
1857}
1858
1859pub fn unlink(p: &CStr) -> io::Result<()> {
1860    cvt(unsafe { libc::unlink(p.as_ptr()) }).map(|_| ())
1861}
1862
1863pub fn rename(old: &CStr, new: &CStr) -> io::Result<()> {
1864    cvt(unsafe { libc::rename(old.as_ptr(), new.as_ptr()) }).map(|_| ())
1865}
1866
1867pub fn set_perm(p: &CStr, perm: FilePermissions) -> io::Result<()> {
1868    cvt_r(|| unsafe { libc::chmod(p.as_ptr(), perm.mode) }).map(|_| ())
1869}
1870
1871#[cfg(target_os = "vxworks")]
1872pub fn set_perm_nofollow(_p: &CStr, _perm: FilePermissions) -> io::Result<()> {
1873    // VxWorks has no `O_NOFOLLOW`, and its `fchmodat` rejects
1874    // `AT_SYMLINK_NOFOLLOW` with `ENOTSUP`, so a no-follow chmod is unsupported.
1875    Err(crate::io::ErrorKind::Unsupported.into())
1876}
1877
1878#[cfg(target_os = "android")]
1879pub fn set_perm_nofollow(_p: &CStr, _perm: FilePermissions) -> io::Result<()> {
1880    // Currently Android seems to be having inconsistent behavior with fchmodat
1881    // with `AT_SYMLINK_NOFOLLOW` or openat with `O_NOFOLLOW` + fchmod.
1882    // See this issue here mentioning inconsistent behavior on fchmodat:
1883    // https://github.com/android/ndk/issues/1258
1884    // On the arm-android CI job, using fchmodat with `AT_SYMLINK_NOFOLLOW` +
1885    // fallback behavior on a symlink sets the target file's permissions,
1886    // which is incorrect behavior.
1887    Err(crate::io::ErrorKind::Unsupported.into())
1888}
1889
1890#[cfg(not(any(target_os = "android", target_os = "vxworks")))]
1891pub fn set_perm_nofollow(p: &CStr, perm: FilePermissions) -> io::Result<()> {
1892    #[inline]
1893    /// Helper function for fallback open with `O_NOFOLLOW` + `fchmod` behavior
1894    fn open_and_set_permissions(p: &CStr, perm: FilePermissions) -> io::Result<()> {
1895        use crate::fs::{OpenOptions, Permissions};
1896
1897        let mut options = OpenOptions::new();
1898        options.read(true);
1899
1900        // ESP-IDF and Horizon do not support O_NOFOLLOW, so we skip setting it.
1901        // Their filesystems do not have symbolic links, so no special handling is required.
1902        #[cfg(not(any(target_os = "espidf", target_os = "horizon")))]
1903        {
1904            #[cfg(not(target_os = "wasi"))]
1905            use crate::os::unix::fs::OpenOptionsExt;
1906            #[cfg(target_os = "wasi")]
1907            use crate::os::wasi::fs::OpenOptionsExt;
1908            options.custom_flags(libc::O_NOFOLLOW);
1909        }
1910
1911        // SAFETY: Since this function is called with `with_native_path`
1912        // and that successfully converted the `&Path` to a `CString`,
1913        // it should be safe to convert the `&CStr` back to a `Path`.
1914        let os_str = unsafe { OsStr::from_encoded_bytes_unchecked(p.to_bytes()) };
1915        options.open(Path::new(os_str))?.set_permissions(Permissions::from_inner(perm))
1916    }
1917
1918    // This res value is modified for platforms that support the `fchmodat` syscall.
1919    #[allow(unused)]
1920    let mut res: Result<(), core::io::Error> = Err(crate::io::ErrorKind::Unsupported.into());
1921
1922    // These platforms support `fchmodat`, so utilize this syscall over `open` + `fchmod`
1923    #[cfg(any(
1924        target_os = "linux",
1925        target_os = "macos",
1926        target_os = "freebsd",
1927        target_os = "openbsd",
1928        target_os = "netbsd",
1929        target_os = "dragonfly",
1930        target_os = "nto",
1931        target_os = "qnx"
1932    ))]
1933    {
1934        res = cvt_r(|| unsafe {
1935            libc::fchmodat(libc::AT_FDCWD, p.as_ptr(), perm.mode, libc::AT_SYMLINK_NOFOLLOW)
1936        })
1937        .map(|_| ());
1938    }
1939
1940    // If fchmodat fails with `ErrorKind::Unsupported` fallback to using open + fchmod. This is just in case
1941    // for older systems like Ubuntu 20.04 where fchmodat fails with EOPNOTSUPP on both regular files and
1942    // symlinks when AT_SYMLINK_NOFOLLOW is passed in.
1943    match res {
1944        Ok(_) => Ok(()),
1945        Err(err) => {
1946            if err.kind() == crate::io::ErrorKind::Unsupported {
1947                match open_and_set_permissions(p, perm) {
1948                    Ok(_) => return Ok(()),
1949                    Err(e) => {
1950                        if e.kind() == crate::io::ErrorKind::FilesystemLoop {
1951                            // When open is used with O_NOFOLLOW flag, if the trailing component of
1952                            // a path is a symbolic link, it should fail with ELOOP error. Instead of
1953                            // returning `FilesystemLoop`, this returns `Unsupported` to keep it consistent
1954                            // with what `fchmodat` would return when chmoding a symlink using AT_SYMLINK_NOFOLLOW.
1955                            return Err(err);
1956                        }
1957                        return Err(e);
1958                    }
1959                }
1960            }
1961
1962            Err(err)
1963        }
1964    }
1965}
1966
1967pub fn rmdir(p: &CStr) -> io::Result<()> {
1968    cvt(unsafe { libc::rmdir(p.as_ptr()) }).map(|_| ())
1969}
1970
1971pub fn readlink(c_path: &CStr) -> io::Result<PathBuf> {
1972    let p = c_path.as_ptr();
1973
1974    let mut buf = Vec::with_capacity(256);
1975
1976    loop {
1977        let buf_read =
1978            cvt(unsafe { libc::readlink(p, buf.as_mut_ptr() as *mut _, buf.capacity()) })? as usize;
1979
1980        unsafe {
1981            buf.set_len(buf_read);
1982        }
1983
1984        if buf_read != buf.capacity() {
1985            buf.shrink_to_fit();
1986
1987            return Ok(PathBuf::from(OsString::from_vec(buf)));
1988        }
1989
1990        // Trigger the internal buffer resizing logic of `Vec` by requiring
1991        // more space than the current capacity. The length is guaranteed to be
1992        // the same as the capacity due to the if statement above.
1993        buf.reserve(1);
1994    }
1995}
1996
1997pub fn symlink(original: &CStr, link: &CStr) -> io::Result<()> {
1998    cvt(unsafe { libc::symlink(original.as_ptr(), link.as_ptr()) }).map(|_| ())
1999}
2000
2001pub fn link(original: &CStr, link: &CStr) -> io::Result<()> {
2002    cfg_select! {
2003        any(
2004            // VxWorks, Redox and ESP-IDF lack `linkat`, so use `link` instead.
2005            // POSIX leaves it implementation-defined whether `link` follows
2006            // symlinks, so rely on the `symlink_hard_link` test in
2007            // library/std/src/fs/tests.rs to check the behavior.
2008            target_os = "vxworks",
2009            target_os = "redox",
2010            target_os = "espidf",
2011            // Other misc platforms
2012            target_os = "horizon",
2013            target_os = "vita",
2014            target_os = "l4re",
2015            target_env = "nto70",
2016        ) => {
2017            cvt(unsafe { libc::link(original.as_ptr(), link.as_ptr()) })?;
2018        }
2019        _ => {
2020            // Where we can, use `linkat` instead of `link`; see the comment above
2021            // this one for details on why.
2022            cvt(unsafe {
2023                libc::linkat(libc::AT_FDCWD, original.as_ptr(), libc::AT_FDCWD, link.as_ptr(), 0)
2024            })?;
2025        }
2026    }
2027    Ok(())
2028}
2029
2030pub fn stat(p: &CStr) -> io::Result<FileAttr> {
2031    cfg_has_statx! {
2032        if let Some(ret) = unsafe { try_statx(
2033            libc::AT_FDCWD,
2034            p.as_ptr(),
2035            libc::AT_STATX_SYNC_AS_STAT,
2036            libc::STATX_BASIC_STATS | libc::STATX_BTIME,
2037        ) } {
2038            return ret;
2039        }
2040    }
2041
2042    let mut stat: stat64 = unsafe { mem::zeroed() };
2043    cvt(unsafe { stat64(p.as_ptr(), &mut stat) })?;
2044    Ok(FileAttr::from_stat64(stat))
2045}
2046
2047pub fn lstat(p: &CStr) -> io::Result<FileAttr> {
2048    cfg_has_statx! {
2049        if let Some(ret) = unsafe { try_statx(
2050            libc::AT_FDCWD,
2051            p.as_ptr(),
2052            libc::AT_SYMLINK_NOFOLLOW | libc::AT_STATX_SYNC_AS_STAT,
2053            libc::STATX_BASIC_STATS | libc::STATX_BTIME,
2054        ) } {
2055            return ret;
2056        }
2057    }
2058
2059    let mut stat: stat64 = unsafe { mem::zeroed() };
2060    cvt(unsafe { lstat64(p.as_ptr(), &mut stat) })?;
2061    Ok(FileAttr::from_stat64(stat))
2062}
2063
2064pub fn canonicalize(path: &CStr) -> io::Result<PathBuf> {
2065    let r = unsafe { libc::realpath(path.as_ptr(), ptr::null_mut()) };
2066    if r.is_null() {
2067        return Err(io::Error::last_os_error());
2068    }
2069    Ok(PathBuf::from(OsString::from_vec(unsafe {
2070        let buf = CStr::from_ptr(r).to_bytes().to_vec();
2071        libc::free(r as *mut _);
2072        buf
2073    })))
2074}
2075
2076fn open_from(from: &Path) -> io::Result<(crate::fs::File, crate::fs::Metadata)> {
2077    use crate::fs::File;
2078    use crate::sys::fs::common::NOT_FILE_ERROR;
2079
2080    let reader = File::open(from)?;
2081    let metadata = reader.metadata()?;
2082    if !metadata.is_file() {
2083        return Err(NOT_FILE_ERROR);
2084    }
2085    Ok((reader, metadata))
2086}
2087
2088fn set_times_impl(p: &CStr, times: FileTimes, follow_symlinks: bool) -> io::Result<()> {
2089    cfg_select! {
2090        any(
2091            target_os = "redox",
2092            target_os = "espidf",
2093            target_os = "horizon",
2094            target_os = "nuttx",
2095            target_os = "vita",
2096            target_os = "rtems"
2097        ) => {
2098            let _ = (p, times, follow_symlinks);
2099            Err(io::const_error!(io::ErrorKind::Unsupported, "setting file times not supported"))
2100        }
2101        target_vendor = "apple" => {
2102            // Apple platforms use setattrlist which supports setting times on symlinks
2103            let ta = TimesAttrlist::from_times(&times)?;
2104            let options = if follow_symlinks { 0 } else { libc::FSOPT_NOFOLLOW };
2105
2106            cvt(unsafe {
2107                libc::setattrlist(
2108                    p.as_ptr(),
2109                    ta.attrlist(),
2110                    ta.times_buf(),
2111                    ta.times_buf_size(),
2112                    options as u32,
2113                )
2114            })?;
2115            Ok(())
2116        }
2117        target_os = "android" => {
2118            let times =
2119                [file_time_to_timespec(times.accessed)?, file_time_to_timespec(times.modified)?];
2120            let flags = if follow_symlinks { 0 } else { libc::AT_SYMLINK_NOFOLLOW };
2121            // utimensat requires Android API level 19
2122            cvt(unsafe {
2123                weak!(
2124                    fn utimensat(
2125                        dirfd: c_int,
2126                        path: *const libc::c_char,
2127                        times: *const libc::timespec,
2128                        flags: c_int,
2129                    ) -> c_int;
2130                );
2131                match utimensat.get() {
2132                    Some(utimensat) => utimensat(libc::AT_FDCWD, p.as_ptr(), times.as_ptr(), flags),
2133                    None => {
2134                        return Err(io::const_error!(
2135                            io::ErrorKind::Unsupported,
2136                            "setting file times requires Android API level >= 19",
2137                        ));
2138                    }
2139                }
2140            })?;
2141            Ok(())
2142        }
2143        _ => {
2144            let flags = if follow_symlinks { 0 } else { libc::AT_SYMLINK_NOFOLLOW };
2145            #[cfg(all(
2146                target_os = "linux",
2147                target_env = "gnu",
2148                target_pointer_width = "32",
2149                not(target_arch = "riscv32")
2150            ))]
2151            {
2152                use crate::sys::pal::time::__timespec64;
2153                use crate::sys::pal::weak::weak;
2154
2155                // Added in glibc 2.34
2156                weak!(
2157                    fn __utimensat64(
2158                        dirfd: c_int,
2159                        path: *const c_char,
2160                        times: *const __timespec64,
2161                        flags: c_int,
2162                    ) -> c_int;
2163                );
2164
2165                if let Some(utimensat64) = __utimensat64.get() {
2166                    let to_timespec = |time: Option<SystemTime>| {
2167                        time.map(|time| time.t.to_timespec64())
2168                            .unwrap_or(__timespec64::new(0, libc::UTIME_OMIT as _))
2169                    };
2170                    let times = [to_timespec(times.accessed), to_timespec(times.modified)];
2171                    cvt(unsafe { utimensat64(libc::AT_FDCWD, p.as_ptr(), times.as_ptr(), flags) })?;
2172                    return Ok(());
2173                }
2174            }
2175            let times =
2176                [file_time_to_timespec(times.accessed)?, file_time_to_timespec(times.modified)?];
2177            cvt(unsafe { libc::utimensat(libc::AT_FDCWD, p.as_ptr(), times.as_ptr(), flags) })?;
2178            Ok(())
2179        }
2180    }
2181}
2182
2183#[inline(always)]
2184pub fn set_times(p: &CStr, times: FileTimes) -> io::Result<()> {
2185    set_times_impl(p, times, true)
2186}
2187
2188#[inline(always)]
2189pub fn set_times_nofollow(p: &CStr, times: FileTimes) -> io::Result<()> {
2190    set_times_impl(p, times, false)
2191}
2192
2193#[cfg(any(target_os = "espidf", target_os = "wasi"))]
2194fn open_to_and_set_permissions(
2195    to: &Path,
2196    _reader_metadata: &crate::fs::Metadata,
2197) -> io::Result<(crate::fs::File, crate::fs::Metadata)> {
2198    use crate::fs::OpenOptions;
2199    let writer = OpenOptions::new().write(true).create(true).truncate(true).open(to)?;
2200    let writer_metadata = writer.metadata()?;
2201    Ok((writer, writer_metadata))
2202}
2203
2204#[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
2205fn open_to_and_set_permissions(
2206    to: &Path,
2207    reader_metadata: &crate::fs::Metadata,
2208) -> io::Result<(crate::fs::File, crate::fs::Metadata)> {
2209    use crate::fs::OpenOptions;
2210    use crate::os::unix::fs::{OpenOptionsExt, PermissionsExt};
2211
2212    let perm = reader_metadata.permissions();
2213    let writer = OpenOptions::new()
2214        // create the file with the correct mode right away
2215        .mode(perm.mode())
2216        .write(true)
2217        .create(true)
2218        .truncate(true)
2219        .open(to)?;
2220    let writer_metadata = writer.metadata()?;
2221    // fchmod is broken on vita
2222    #[cfg(not(target_os = "vita"))]
2223    if writer_metadata.is_file() {
2224        // Set the correct file permissions, in case the file already existed.
2225        // Don't set the permissions on already existing non-files like
2226        // pipes/FIFOs or device nodes.
2227        writer.set_permissions(perm)?;
2228    }
2229    Ok((writer, writer_metadata))
2230}
2231
2232mod cfm {
2233    use crate::fs::{File, Metadata};
2234    use crate::io::{BorrowedCursor, IoSlice, IoSliceMut, Read, Result, Write};
2235
2236    #[allow(dead_code)]
2237    pub struct CachedFileMetadata(pub File, pub Metadata);
2238
2239    impl Read for CachedFileMetadata {
2240        fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
2241            self.0.read(buf)
2242        }
2243        fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> Result<usize> {
2244            self.0.read_vectored(bufs)
2245        }
2246        fn read_buf(&mut self, cursor: BorrowedCursor<'_, u8>) -> Result<()> {
2247            self.0.read_buf(cursor)
2248        }
2249        #[inline]
2250        fn is_read_vectored(&self) -> bool {
2251            self.0.is_read_vectored()
2252        }
2253        fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize> {
2254            self.0.read_to_end(buf)
2255        }
2256        fn read_to_string(&mut self, buf: &mut String) -> Result<usize> {
2257            self.0.read_to_string(buf)
2258        }
2259    }
2260    impl Write for CachedFileMetadata {
2261        fn write(&mut self, buf: &[u8]) -> Result<usize> {
2262            self.0.write(buf)
2263        }
2264        fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> Result<usize> {
2265            self.0.write_vectored(bufs)
2266        }
2267        #[inline]
2268        fn is_write_vectored(&self) -> bool {
2269            self.0.is_write_vectored()
2270        }
2271        #[inline]
2272        fn flush(&mut self) -> Result<()> {
2273            self.0.flush()
2274        }
2275    }
2276}
2277#[cfg(any(target_os = "linux", target_os = "android"))]
2278pub(in crate::sys) use cfm::CachedFileMetadata;
2279
2280#[cfg(not(target_vendor = "apple"))]
2281pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
2282    let (reader, reader_metadata) = open_from(from)?;
2283    let (writer, writer_metadata) = open_to_and_set_permissions(to, &reader_metadata)?;
2284
2285    io::copy(
2286        &mut cfm::CachedFileMetadata(reader, reader_metadata),
2287        &mut cfm::CachedFileMetadata(writer, writer_metadata),
2288    )
2289}
2290
2291#[cfg(target_vendor = "apple")]
2292pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
2293    const COPYFILE_ALL: libc::copyfile_flags_t = libc::COPYFILE_METADATA | libc::COPYFILE_DATA;
2294
2295    struct FreeOnDrop(libc::copyfile_state_t);
2296    impl Drop for FreeOnDrop {
2297        fn drop(&mut self) {
2298            // The code below ensures that `FreeOnDrop` is never a null pointer
2299            unsafe {
2300                // `copyfile_state_free` returns -1 if the `to` or `from` files
2301                // cannot be closed. However, this is not considered an error.
2302                libc::copyfile_state_free(self.0);
2303            }
2304        }
2305    }
2306
2307    let (reader, reader_metadata) = open_from(from)?;
2308
2309    let clonefile_result = run_path_with_cstr(to, &|to| {
2310        cvt(unsafe { libc::fclonefileat(reader.as_raw_fd(), libc::AT_FDCWD, to.as_ptr(), 0) })
2311    });
2312    match clonefile_result {
2313        Ok(_) => return Ok(reader_metadata.len()),
2314        Err(e) => match e.raw_os_error() {
2315            // `fclonefileat` will fail on non-APFS volumes, if the
2316            // destination already exists, or if the source and destination
2317            // are on different devices. In all these cases `fcopyfile`
2318            // should succeed.
2319            Some(libc::ENOTSUP) | Some(libc::EEXIST) | Some(libc::EXDEV) => (),
2320            _ => return Err(e),
2321        },
2322    }
2323
2324    // Fall back to using `fcopyfile` if `fclonefileat` does not succeed.
2325    let (writer, writer_metadata) = open_to_and_set_permissions(to, &reader_metadata)?;
2326
2327    // We ensure that `FreeOnDrop` never contains a null pointer so it is
2328    // always safe to call `copyfile_state_free`
2329    let state = unsafe {
2330        let state = libc::copyfile_state_alloc();
2331        if state.is_null() {
2332            return Err(crate::io::Error::last_os_error());
2333        }
2334        FreeOnDrop(state)
2335    };
2336
2337    let flags = if writer_metadata.is_file() { COPYFILE_ALL } else { libc::COPYFILE_DATA };
2338
2339    cvt(unsafe { libc::fcopyfile(reader.as_raw_fd(), writer.as_raw_fd(), state.0, flags) })?;
2340
2341    let mut bytes_copied: libc::off_t = 0;
2342    cvt(unsafe {
2343        libc::copyfile_state_get(
2344            state.0,
2345            libc::COPYFILE_STATE_COPIED as u32,
2346            (&raw mut bytes_copied) as *mut libc::c_void,
2347        )
2348    })?;
2349    Ok(bytes_copied as u64)
2350}
2351
2352#[cfg(not(target_os = "wasi"))]
2353pub fn chown(path: &Path, uid: u32, gid: u32) -> io::Result<()> {
2354    run_path_with_cstr(path, &|path| {
2355        cvt(unsafe { libc::chown(path.as_ptr(), uid as libc::uid_t, gid as libc::gid_t) })
2356            .map(|_| ())
2357    })
2358}
2359
2360#[cfg(not(target_os = "wasi"))]
2361pub fn fchown(fd: c_int, uid: u32, gid: u32) -> io::Result<()> {
2362    cvt(unsafe { libc::fchown(fd, uid as libc::uid_t, gid as libc::gid_t) })?;
2363    Ok(())
2364}
2365
2366#[cfg(not(any(target_os = "vxworks", target_os = "wasi")))]
2367pub fn lchown(path: &Path, uid: u32, gid: u32) -> io::Result<()> {
2368    run_path_with_cstr(path, &|path| {
2369        cvt(unsafe { libc::lchown(path.as_ptr(), uid as libc::uid_t, gid as libc::gid_t) })
2370            .map(|_| ())
2371    })
2372}
2373
2374#[cfg(target_os = "vxworks")]
2375pub fn lchown(path: &Path, uid: u32, gid: u32) -> io::Result<()> {
2376    let (_, _, _) = (path, uid, gid);
2377    Err(io::const_error!(io::ErrorKind::Unsupported, "lchown not supported by vxworks"))
2378}
2379
2380#[cfg(not(any(target_os = "fuchsia", target_os = "vxworks", target_os = "wasi")))]
2381pub fn chroot(dir: &Path) -> io::Result<()> {
2382    run_path_with_cstr(dir, &|dir| cvt(unsafe { libc::chroot(dir.as_ptr()) }).map(|_| ()))
2383}
2384
2385#[cfg(target_os = "vxworks")]
2386pub fn chroot(dir: &Path) -> io::Result<()> {
2387    let _ = dir;
2388    Err(io::const_error!(io::ErrorKind::Unsupported, "chroot not supported by vxworks"))
2389}
2390
2391#[cfg(not(target_os = "wasi"))]
2392pub fn mkfifo(path: &Path, mode: u32) -> io::Result<()> {
2393    run_path_with_cstr(path, &|path| {
2394        cvt(unsafe { libc::mkfifo(path.as_ptr(), mode.try_into().unwrap()) }).map(|_| ())
2395    })
2396}
2397
2398pub use remove_dir_impl::remove_dir_all;
2399
2400// Fallback for REDOX, ESP-ID, Horizon, Vita, Vxworks and Miri
2401#[cfg(any(
2402    target_os = "redox",
2403    target_os = "espidf",
2404    target_os = "horizon",
2405    target_os = "vita",
2406    target_os = "nto",
2407    target_os = "qnx",
2408    target_os = "vxworks",
2409    target_os = "l4re",
2410    miri
2411))]
2412mod remove_dir_impl {
2413    pub use crate::sys::fs::common::remove_dir_all;
2414}
2415
2416// Modern implementation using openat(), unlinkat() and fdopendir()
2417#[cfg(not(any(
2418    target_os = "redox",
2419    target_os = "espidf",
2420    target_os = "horizon",
2421    target_os = "vita",
2422    target_os = "nto",
2423    target_os = "qnx",
2424    target_os = "vxworks",
2425    target_os = "l4re",
2426    miri
2427)))]
2428mod remove_dir_impl {
2429    #[cfg(not(all(target_os = "linux", target_env = "gnu")))]
2430    use libc::{fdopendir, openat, unlinkat};
2431    #[cfg(all(target_os = "linux", target_env = "gnu"))]
2432    use libc::{fdopendir, openat64 as openat, unlinkat};
2433
2434    use super::{
2435        AsRawFd, DirEntry, DirStream, FromRawFd, InnerReadDir, IntoRawFd, OwnedFd, RawFd, ReadDir,
2436        lstat,
2437    };
2438    use crate::ffi::CStr;
2439    use crate::io;
2440    use crate::path::{Path, PathBuf};
2441    use crate::sys::helpers::{ignore_notfound, run_path_with_cstr};
2442    use crate::sys::{cvt, cvt_r};
2443
2444    pub fn openat_nofollow_dironly(parent_fd: Option<RawFd>, p: &CStr) -> io::Result<OwnedFd> {
2445        let fd = cvt_r(|| unsafe {
2446            openat(
2447                parent_fd.unwrap_or(libc::AT_FDCWD),
2448                p.as_ptr(),
2449                libc::O_CLOEXEC | libc::O_RDONLY | libc::O_NOFOLLOW | libc::O_DIRECTORY,
2450            )
2451        })?;
2452        Ok(unsafe { OwnedFd::from_raw_fd(fd) })
2453    }
2454
2455    fn fdreaddir(dir_fd: OwnedFd) -> io::Result<(ReadDir, RawFd)> {
2456        let ptr = unsafe { fdopendir(dir_fd.as_raw_fd()) };
2457        if ptr.is_null() {
2458            return Err(io::Error::last_os_error());
2459        }
2460        let dirp = DirStream(ptr);
2461        // file descriptor is automatically closed by libc::closedir() now, so give up ownership
2462        let new_parent_fd = dir_fd.into_raw_fd();
2463        // a valid root is not needed because we do not call any functions involving the full path
2464        // of the `DirEntry`s.
2465        let dummy_root = PathBuf::new();
2466        let inner = InnerReadDir { dirp, root: dummy_root };
2467        Ok((ReadDir::new(inner), new_parent_fd))
2468    }
2469
2470    #[cfg(any(
2471        target_os = "solaris",
2472        target_os = "illumos",
2473        target_os = "haiku",
2474        target_os = "vxworks",
2475        target_os = "aix",
2476    ))]
2477    fn is_dir(_ent: &DirEntry) -> Option<bool> {
2478        None
2479    }
2480
2481    #[cfg(not(any(
2482        target_os = "solaris",
2483        target_os = "illumos",
2484        target_os = "haiku",
2485        target_os = "vxworks",
2486        target_os = "aix",
2487    )))]
2488    fn is_dir(ent: &DirEntry) -> Option<bool> {
2489        match ent.entry.d_type {
2490            libc::DT_UNKNOWN => None,
2491            libc::DT_DIR => Some(true),
2492            _ => Some(false),
2493        }
2494    }
2495
2496    fn is_enoent(result: &io::Result<()>) -> bool {
2497        if let Err(err) = result
2498            && matches!(err.raw_os_error(), Some(libc::ENOENT))
2499        {
2500            true
2501        } else {
2502            false
2503        }
2504    }
2505
2506    fn remove_dir_all_recursive(parent_fd: Option<RawFd>, path: &CStr) -> io::Result<()> {
2507        // try opening as directory
2508        let fd = match openat_nofollow_dironly(parent_fd, path) {
2509            Err(err) if matches!(err.raw_os_error(), Some(libc::ENOTDIR | libc::ELOOP)) => {
2510                // not a directory - don't traverse further
2511                // (for symlinks, older Linux kernels may return ELOOP instead of ENOTDIR)
2512                return match parent_fd {
2513                    // unlink...
2514                    Some(parent_fd) => {
2515                        cvt(unsafe { unlinkat(parent_fd, path.as_ptr(), 0) }).map(drop)
2516                    }
2517                    // ...unless this was supposed to be the deletion root directory
2518                    None => Err(err),
2519                };
2520            }
2521            result => result?,
2522        };
2523
2524        // open the directory passing ownership of the fd
2525        let (dir, fd) = fdreaddir(fd)?;
2526
2527        // For WASI all directory entries for this directory are read first
2528        // before any removal is done. This works around the fact that the
2529        // WASIp1 API for reading directories is not well-designed for handling
2530        // mutations between invocations of reading a directory. By reading all
2531        // the entries at once this ensures that, at least without concurrent
2532        // modifications, it should be possible to delete everything.
2533        #[cfg(target_os = "wasi")]
2534        let dir = dir.collect::<Vec<_>>();
2535
2536        for child in dir {
2537            let child = child?;
2538            // we need an inner try block, because if one of these
2539            // directories has already been deleted, then we need to
2540            // continue the loop, not return ok.
2541            let result: io::Result<()> = try {
2542                match is_dir(&child) {
2543                    Some(true) => {
2544                        remove_dir_all_recursive(Some(fd), &child.name)?;
2545                    }
2546                    Some(false) => {
2547                        cvt(unsafe { unlinkat(fd, child.name.as_ptr(), 0) })?;
2548                    }
2549                    None => {
2550                        // POSIX specifies that calling unlink()/unlinkat(..., 0) on a directory can succeed
2551                        // if the process has the appropriate privileges. This however can causing orphaned
2552                        // directories requiring an fsck e.g. on Solaris and Illumos. So we try recursing
2553                        // into it first instead of trying to unlink() it.
2554                        remove_dir_all_recursive(Some(fd), &child.name)?;
2555                    }
2556                }
2557            };
2558            if result.is_err() && !is_enoent(&result) {
2559                return result;
2560            }
2561        }
2562
2563        // unlink the directory after removing its contents
2564        ignore_notfound(cvt(unsafe {
2565            unlinkat(parent_fd.unwrap_or(libc::AT_FDCWD), path.as_ptr(), libc::AT_REMOVEDIR)
2566        }))?;
2567        Ok(())
2568    }
2569
2570    fn remove_dir_all_modern(p: &CStr) -> io::Result<()> {
2571        // We cannot just call remove_dir_all_recursive() here because that would not delete a passed
2572        // symlink. No need to worry about races, because remove_dir_all_recursive() does not recurse
2573        // into symlinks.
2574        let attr = lstat(p)?;
2575        if attr.file_type().is_symlink() {
2576            super::unlink(p)
2577        } else {
2578            remove_dir_all_recursive(None, p)
2579        }
2580    }
2581
2582    pub fn remove_dir_all(p: &Path) -> io::Result<()> {
2583        run_path_with_cstr(p, &remove_dir_all_modern)
2584    }
2585}