Skip to main content

std/sys/fs/
unix.rs

1#![allow(nonstandard_style)]
2#![allow(unsafe_op_in_unsafe_fn)]
3// miri has some special hacks here that make things unused.
4#![cfg_attr(miri, allow(unused))]
5
6#[cfg(test)]
7mod tests;
8
9#[cfg(all(target_os = "linux", target_env = "gnu"))]
10use libc::c_char;
11#[cfg(any(
12    all(target_os = "linux", not(target_env = "musl")),
13    target_os = "android",
14    target_os = "fuchsia",
15    target_os = "hurd",
16    target_os = "illumos",
17    target_vendor = "apple",
18))]
19use libc::dirfd;
20#[cfg(any(target_os = "fuchsia", target_os = "illumos", target_vendor = "apple"))]
21use libc::fstatat as fstatat64;
22#[cfg(any(all(target_os = "linux", not(target_env = "musl")), target_os = "hurd"))]
23use libc::fstatat64;
24use libc::{c_int, mode_t};
25#[cfg(target_os = "android")]
26use libc::{
27    dirent as dirent64, fstat as fstat64, fstatat as fstatat64, ftruncate64, lseek64,
28    lstat as lstat64, off64_t, open as open64, stat as stat64,
29};
30#[cfg(not(any(
31    all(target_os = "linux", not(target_env = "musl")),
32    target_os = "l4re",
33    target_os = "android",
34    target_os = "hurd",
35)))]
36use libc::{
37    dirent as dirent64, fstat as fstat64, ftruncate as ftruncate64, lseek as lseek64,
38    lstat as lstat64, off_t as off64_t, open as open64, stat as stat64,
39};
40#[cfg(any(
41    all(target_os = "linux", not(target_env = "musl")),
42    target_os = "l4re",
43    target_os = "hurd"
44))]
45use libc::{dirent64, fstat64, ftruncate64, lseek64, lstat64, off64_t, open64, stat64};
46
47use crate::ffi::{CStr, OsStr, OsString};
48use crate::fmt::{self, Write as _};
49use crate::fs::TryLockError;
50use crate::io::{self, BorrowedCursor, Error, IoSlice, IoSliceMut, SeekFrom};
51use crate::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd};
52#[cfg(target_family = "unix")]
53use crate::os::unix::prelude::*;
54#[cfg(target_os = "wasi")]
55use crate::os::wasi::prelude::*;
56use crate::path::{Path, PathBuf};
57use crate::sync::Arc;
58use crate::sys::fd::FileDesc;
59pub use crate::sys::fs::common::exists;
60use crate::sys::helpers::run_path_with_cstr;
61use crate::sys::time::SystemTime;
62#[cfg(all(target_os = "linux", target_env = "gnu"))]
63use crate::sys::weak::syscall;
64#[cfg(target_os = "android")]
65use crate::sys::weak::weak;
66use crate::sys::{AsInner, AsInnerMut, FromInner, IntoInner, cvt, cvt_r};
67use crate::{mem, ptr};
68
69// Used by rustc for checking the definitions of other function with the same symbol names
70//
71// See the `invalid_runtime_symbols_definitions` lint.
72#[cfg(not(test))]
73mod runtime_symbols {
74    use core::ffi::{c_char, c_int, c_size_t, c_ssize_t, c_void};
75
76    unsafe extern "C" {
77        #[rustc_canonical_symbol]
78        fn open(pathname: *const c_char, flags: c_int, ...) -> c_int;
79
80        #[rustc_canonical_symbol]
81        fn read(fd: c_int, buf: *mut c_void, count: c_size_t) -> c_ssize_t;
82
83        #[rustc_canonical_symbol]
84        fn write(fd: c_int, buf: *const c_void, count: c_size_t) -> c_ssize_t;
85
86        #[rustc_canonical_symbol]
87        fn close(fd: c_int) -> c_int;
88    }
89}
90
91pub struct File(FileDesc);
92
93// FIXME: This should be available on Linux with all `target_env`.
94// But currently only glibc exposes `statx` fn and structs.
95// We don't want to import unverified raw C structs here directly.
96// https://github.com/rust-lang/rust/pull/67774
97macro_rules! cfg_has_statx {
98    ({ $($then_tt:tt)* } else { $($else_tt:tt)* }) => {
99        cfg_select! {
100            all(target_os = "linux", target_env = "gnu") => {
101                $($then_tt)*
102            }
103            _ => {
104                $($else_tt)*
105            }
106        }
107    };
108    ($($block_inner:tt)*) => {
109        #[cfg(all(target_os = "linux", target_env = "gnu"))]
110        {
111            $($block_inner)*
112        }
113    };
114}
115
116cfg_has_statx! {{
117    #[derive(#[automatically_derived]
impl ::core::clone::Clone for FileAttr {
    #[inline]
    fn clone(&self) -> FileAttr {
        FileAttr {
            stat: ::core::clone::Clone::clone(&self.stat),
            statx_extra_fields: ::core::clone::Clone::clone(&self.statx_extra_fields),
        }
    }
}Clone)]
118    pub struct FileAttr {
119        stat: stat64,
120        statx_extra_fields: Option<StatxExtraFields>,
121    }
122
123    #[derive(#[automatically_derived]
impl ::core::clone::Clone for StatxExtraFields {
    #[inline]
    fn clone(&self) -> StatxExtraFields {
        StatxExtraFields {
            stx_mask: ::core::clone::Clone::clone(&self.stx_mask),
            stx_btime: ::core::clone::Clone::clone(&self.stx_btime),
        }
    }
}Clone)]
124    struct StatxExtraFields {
125        // This is needed to check if btime is supported by the filesystem.
126        stx_mask: u32,
127        stx_btime: libc::statx_timestamp,
128        // With statx, we can overcome 32-bit `time_t` too.
129        #[cfg(target_pointer_width = "32")]
130        stx_atime: libc::statx_timestamp,
131        #[cfg(target_pointer_width = "32")]
132        stx_ctime: libc::statx_timestamp,
133        #[cfg(target_pointer_width = "32")]
134        stx_mtime: libc::statx_timestamp,
135
136    }
137
138    // We prefer `statx` on Linux if available, which contains file creation time,
139    // as well as 64-bit timestamps of all kinds.
140    // Default `stat64` contains no creation time and may have 32-bit `time_t`.
141    unsafe fn try_statx(
142        fd: c_int,
143        path: *const c_char,
144        flags: i32,
145        mask: u32,
146    ) -> Option<io::Result<FileAttr>> {
147        use crate::sync::atomic::{Atomic, AtomicU8, Ordering};
148
149        // Linux kernel prior to 4.11 or glibc prior to glibc 2.28 don't support `statx`.
150        // We check for it on first failure and remember availability to avoid having to
151        // do it again.
152        #[repr(u8)]
153        enum STATX_STATE{ Unknown = 0, Present, Unavailable }
154        static STATX_SAVED_STATE: Atomic<u8> = AtomicU8::new(STATX_STATE::Unknown as u8);
155
156        unsafe fn statx(fd: c_int, pathname: *const c_char, flags: c_int,
    mask: libc::c_uint, statxbuf: *mut libc::statx) -> c_int {
    let ref statx:
            ExternWeak<unsafe extern "C" fn(c_int, *const c_char, c_int,
                libc::c_uint, *mut libc::statx) -> c_int> =
        {
            unsafe extern "C" {
                #[linkage = "extern_weak"]
                static statx:
                    Option<unsafe extern "C" fn(c_int, *const c_char, c_int,
                        libc::c_uint, *mut libc::statx) -> c_int>;
            }

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