Skip to main content

std/sys/fs/
unix.rs

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