Skip to main content

std/sys/fs/
unix.rs

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