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