]> git.lizzy.rs Git - rust.git/blob - library/std/src/sys/unix/fs.rs
Rollup merge of #105697 - fee1-dead-contrib:rm-fee1-dead, r=Mark-Simulacrum
[rust.git] / library / std / src / sys / unix / fs.rs
1 // miri has some special hacks here that make things unused.
2 #![cfg_attr(miri, allow(unused))]
3
4 use crate::os::unix::prelude::*;
5
6 use crate::ffi::{CStr, OsStr, OsString};
7 use crate::fmt;
8 use crate::io::{self, BorrowedCursor, Error, IoSlice, IoSliceMut, SeekFrom};
9 use crate::mem;
10 #[cfg(any(
11     target_os = "android",
12     target_os = "linux",
13     target_os = "solaris",
14     target_os = "fuchsia",
15     target_os = "redox",
16     target_os = "illumos"
17 ))]
18 use crate::mem::MaybeUninit;
19 use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd};
20 use crate::path::{Path, PathBuf};
21 use crate::ptr;
22 use crate::sync::Arc;
23 use crate::sys::common::small_c_string::run_path_with_cstr;
24 use crate::sys::fd::FileDesc;
25 use crate::sys::time::SystemTime;
26 use crate::sys::{cvt, cvt_r};
27 use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner};
28
29 #[cfg(any(
30     all(target_os = "linux", target_env = "gnu"),
31     target_os = "macos",
32     target_os = "ios",
33     target_os = "watchos",
34 ))]
35 use crate::sys::weak::syscall;
36 #[cfg(any(target_os = "android", target_os = "macos"))]
37 use crate::sys::weak::weak;
38
39 use libc::{c_int, mode_t};
40
41 #[cfg(any(
42     target_os = "macos",
43     target_os = "ios",
44     target_os = "watchos",
45     all(target_os = "linux", target_env = "gnu")
46 ))]
47 use libc::c_char;
48 #[cfg(any(target_os = "linux", target_os = "emscripten", target_os = "android"))]
49 use libc::dirfd;
50 #[cfg(any(target_os = "linux", target_os = "emscripten"))]
51 use libc::fstatat64;
52 #[cfg(any(
53     target_os = "android",
54     target_os = "solaris",
55     target_os = "fuchsia",
56     target_os = "redox",
57     target_os = "illumos"
58 ))]
59 use libc::readdir as readdir64;
60 #[cfg(target_os = "linux")]
61 use libc::readdir64;
62 #[cfg(any(target_os = "emscripten", target_os = "l4re"))]
63 use libc::readdir64_r;
64 #[cfg(not(any(
65     target_os = "android",
66     target_os = "linux",
67     target_os = "emscripten",
68     target_os = "solaris",
69     target_os = "illumos",
70     target_os = "l4re",
71     target_os = "fuchsia",
72     target_os = "redox"
73 )))]
74 use libc::readdir_r as readdir64_r;
75 #[cfg(target_os = "android")]
76 use libc::{
77     dirent as dirent64, fstat as fstat64, fstatat as fstatat64, ftruncate64, lseek64,
78     lstat as lstat64, off64_t, open as open64, stat as stat64,
79 };
80 #[cfg(not(any(
81     target_os = "linux",
82     target_os = "emscripten",
83     target_os = "l4re",
84     target_os = "android"
85 )))]
86 use libc::{
87     dirent as dirent64, fstat as fstat64, ftruncate as ftruncate64, lseek as lseek64,
88     lstat as lstat64, off_t as off64_t, open as open64, stat as stat64,
89 };
90 #[cfg(any(target_os = "linux", target_os = "emscripten", target_os = "l4re"))]
91 use libc::{dirent64, fstat64, ftruncate64, lseek64, lstat64, off64_t, open64, stat64};
92
93 pub use crate::sys_common::fs::try_exists;
94
95 pub struct File(FileDesc);
96
97 // FIXME: This should be available on Linux with all `target_env`.
98 // But currently only glibc exposes `statx` fn and structs.
99 // We don't want to import unverified raw C structs here directly.
100 // https://github.com/rust-lang/rust/pull/67774
101 macro_rules! cfg_has_statx {
102     ({ $($then_tt:tt)* } else { $($else_tt:tt)* }) => {
103         cfg_if::cfg_if! {
104             if #[cfg(all(target_os = "linux", target_env = "gnu"))] {
105                 $($then_tt)*
106             } else {
107                 $($else_tt)*
108             }
109         }
110     };
111     ($($block_inner:tt)*) => {
112         #[cfg(all(target_os = "linux", target_env = "gnu"))]
113         {
114             $($block_inner)*
115         }
116     };
117 }
118
119 cfg_has_statx! {{
120     #[derive(Clone)]
121     pub struct FileAttr {
122         stat: stat64,
123         statx_extra_fields: Option<StatxExtraFields>,
124     }
125
126     #[derive(Clone)]
127     struct StatxExtraFields {
128         // This is needed to check if btime is supported by the filesystem.
129         stx_mask: u32,
130         stx_btime: libc::statx_timestamp,
131         // With statx, we can overcome 32-bit `time_t` too.
132         #[cfg(target_pointer_width = "32")]
133         stx_atime: libc::statx_timestamp,
134         #[cfg(target_pointer_width = "32")]
135         stx_ctime: libc::statx_timestamp,
136         #[cfg(target_pointer_width = "32")]
137         stx_mtime: libc::statx_timestamp,
138
139     }
140
141     // We prefer `statx` on Linux if available, which contains file creation time,
142     // as well as 64-bit timestamps of all kinds.
143     // Default `stat64` contains no creation time and may have 32-bit `time_t`.
144     unsafe fn try_statx(
145         fd: c_int,
146         path: *const c_char,
147         flags: i32,
148         mask: u32,
149     ) -> Option<io::Result<FileAttr>> {
150         use crate::sync::atomic::{AtomicU8, Ordering};
151
152         // Linux kernel prior to 4.11 or glibc prior to glibc 2.28 don't support `statx`
153         // We store the availability in global to avoid unnecessary syscalls.
154         // 0: Unknown
155         // 1: Not available
156         // 2: Available
157         static STATX_STATE: AtomicU8 = AtomicU8::new(0);
158         syscall! {
159             fn statx(
160                 fd: c_int,
161                 pathname: *const c_char,
162                 flags: c_int,
163                 mask: libc::c_uint,
164                 statxbuf: *mut libc::statx
165             ) -> c_int
166         }
167
168         match STATX_STATE.load(Ordering::Relaxed) {
169             0 => {
170                 // It is a trick to call `statx` with null pointers to check if the syscall
171                 // is available. According to the manual, it is expected to fail with EFAULT.
172                 // We do this mainly for performance, since it is nearly hundreds times
173                 // faster than a normal successful call.
174                 let err = cvt(statx(0, ptr::null(), 0, libc::STATX_ALL, ptr::null_mut()))
175                     .err()
176                     .and_then(|e| e.raw_os_error());
177                 // We don't check `err == Some(libc::ENOSYS)` because the syscall may be limited
178                 // and returns `EPERM`. Listing all possible errors seems not a good idea.
179                 // See: https://github.com/rust-lang/rust/issues/65662
180                 if err != Some(libc::EFAULT) {
181                     STATX_STATE.store(1, Ordering::Relaxed);
182                     return None;
183                 }
184                 STATX_STATE.store(2, Ordering::Relaxed);
185             }
186             1 => return None,
187             _ => {}
188         }
189
190         let mut buf: libc::statx = mem::zeroed();
191         if let Err(err) = cvt(statx(fd, path, flags, mask, &mut buf)) {
192             return Some(Err(err));
193         }
194
195         // We cannot fill `stat64` exhaustively because of private padding fields.
196         let mut stat: stat64 = mem::zeroed();
197         // `c_ulong` on gnu-mips, `dev_t` otherwise
198         stat.st_dev = libc::makedev(buf.stx_dev_major, buf.stx_dev_minor) as _;
199         stat.st_ino = buf.stx_ino as libc::ino64_t;
200         stat.st_nlink = buf.stx_nlink as libc::nlink_t;
201         stat.st_mode = buf.stx_mode as libc::mode_t;
202         stat.st_uid = buf.stx_uid as libc::uid_t;
203         stat.st_gid = buf.stx_gid as libc::gid_t;
204         stat.st_rdev = libc::makedev(buf.stx_rdev_major, buf.stx_rdev_minor) as _;
205         stat.st_size = buf.stx_size as off64_t;
206         stat.st_blksize = buf.stx_blksize as libc::blksize_t;
207         stat.st_blocks = buf.stx_blocks as libc::blkcnt64_t;
208         stat.st_atime = buf.stx_atime.tv_sec as libc::time_t;
209         // `i64` on gnu-x86_64-x32, `c_ulong` otherwise.
210         stat.st_atime_nsec = buf.stx_atime.tv_nsec as _;
211         stat.st_mtime = buf.stx_mtime.tv_sec as libc::time_t;
212         stat.st_mtime_nsec = buf.stx_mtime.tv_nsec as _;
213         stat.st_ctime = buf.stx_ctime.tv_sec as libc::time_t;
214         stat.st_ctime_nsec = buf.stx_ctime.tv_nsec as _;
215
216         let extra = StatxExtraFields {
217             stx_mask: buf.stx_mask,
218             stx_btime: buf.stx_btime,
219             // Store full times to avoid 32-bit `time_t` truncation.
220             #[cfg(target_pointer_width = "32")]
221             stx_atime: buf.stx_atime,
222             #[cfg(target_pointer_width = "32")]
223             stx_ctime: buf.stx_ctime,
224             #[cfg(target_pointer_width = "32")]
225             stx_mtime: buf.stx_mtime,
226         };
227
228         Some(Ok(FileAttr { stat, statx_extra_fields: Some(extra) }))
229     }
230
231 } else {
232     #[derive(Clone)]
233     pub struct FileAttr {
234         stat: stat64,
235     }
236 }}
237
238 // all DirEntry's will have a reference to this struct
239 struct InnerReadDir {
240     dirp: Dir,
241     root: PathBuf,
242 }
243
244 pub struct ReadDir {
245     inner: Arc<InnerReadDir>,
246     #[cfg(not(any(
247         target_os = "android",
248         target_os = "linux",
249         target_os = "solaris",
250         target_os = "illumos",
251         target_os = "fuchsia",
252         target_os = "redox",
253     )))]
254     end_of_stream: bool,
255 }
256
257 struct Dir(*mut libc::DIR);
258
259 unsafe impl Send for Dir {}
260 unsafe impl Sync for Dir {}
261
262 #[cfg(any(
263     target_os = "android",
264     target_os = "linux",
265     target_os = "solaris",
266     target_os = "illumos",
267     target_os = "fuchsia",
268     target_os = "redox"
269 ))]
270 pub struct DirEntry {
271     dir: Arc<InnerReadDir>,
272     entry: dirent64_min,
273     // We need to store an owned copy of the entry name on platforms that use
274     // readdir() (not readdir_r()), because a) struct dirent may use a flexible
275     // array to store the name, b) it lives only until the next readdir() call.
276     name: crate::ffi::CString,
277 }
278
279 // Define a minimal subset of fields we need from `dirent64`, especially since
280 // we're not using the immediate `d_name` on these targets. Keeping this as an
281 // `entry` field in `DirEntry` helps reduce the `cfg` boilerplate elsewhere.
282 #[cfg(any(
283     target_os = "android",
284     target_os = "linux",
285     target_os = "solaris",
286     target_os = "illumos",
287     target_os = "fuchsia",
288     target_os = "redox"
289 ))]
290 struct dirent64_min {
291     d_ino: u64,
292     #[cfg(not(any(target_os = "solaris", target_os = "illumos")))]
293     d_type: u8,
294 }
295
296 #[cfg(not(any(
297     target_os = "android",
298     target_os = "linux",
299     target_os = "solaris",
300     target_os = "illumos",
301     target_os = "fuchsia",
302     target_os = "redox"
303 )))]
304 pub struct DirEntry {
305     dir: Arc<InnerReadDir>,
306     // The full entry includes a fixed-length `d_name`.
307     entry: dirent64,
308 }
309
310 #[derive(Clone, Debug)]
311 pub struct OpenOptions {
312     // generic
313     read: bool,
314     write: bool,
315     append: bool,
316     truncate: bool,
317     create: bool,
318     create_new: bool,
319     // system-specific
320     custom_flags: i32,
321     mode: mode_t,
322 }
323
324 #[derive(Clone, PartialEq, Eq, Debug)]
325 pub struct FilePermissions {
326     mode: mode_t,
327 }
328
329 #[derive(Copy, Clone, Debug, Default)]
330 pub struct FileTimes {
331     accessed: Option<SystemTime>,
332     modified: Option<SystemTime>,
333 }
334
335 #[derive(Copy, Clone, Eq, Debug)]
336 pub struct FileType {
337     mode: mode_t,
338 }
339
340 impl PartialEq for FileType {
341     fn eq(&self, other: &Self) -> bool {
342         self.masked() == other.masked()
343     }
344 }
345
346 impl core::hash::Hash for FileType {
347     fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
348         self.masked().hash(state);
349     }
350 }
351
352 #[derive(Debug)]
353 pub struct DirBuilder {
354     mode: mode_t,
355 }
356
357 cfg_has_statx! {{
358     impl FileAttr {
359         fn from_stat64(stat: stat64) -> Self {
360             Self { stat, statx_extra_fields: None }
361         }
362
363         #[cfg(target_pointer_width = "32")]
364         pub fn stx_mtime(&self) -> Option<&libc::statx_timestamp> {
365             if let Some(ext) = &self.statx_extra_fields {
366                 if (ext.stx_mask & libc::STATX_MTIME) != 0 {
367                     return Some(&ext.stx_mtime);
368                 }
369             }
370             None
371         }
372
373         #[cfg(target_pointer_width = "32")]
374         pub fn stx_atime(&self) -> Option<&libc::statx_timestamp> {
375             if let Some(ext) = &self.statx_extra_fields {
376                 if (ext.stx_mask & libc::STATX_ATIME) != 0 {
377                     return Some(&ext.stx_atime);
378                 }
379             }
380             None
381         }
382
383         #[cfg(target_pointer_width = "32")]
384         pub fn stx_ctime(&self) -> Option<&libc::statx_timestamp> {
385             if let Some(ext) = &self.statx_extra_fields {
386                 if (ext.stx_mask & libc::STATX_CTIME) != 0 {
387                     return Some(&ext.stx_ctime);
388                 }
389             }
390             None
391         }
392     }
393 } else {
394     impl FileAttr {
395         fn from_stat64(stat: stat64) -> Self {
396             Self { stat }
397         }
398     }
399 }}
400
401 impl FileAttr {
402     pub fn size(&self) -> u64 {
403         self.stat.st_size as u64
404     }
405     pub fn perm(&self) -> FilePermissions {
406         FilePermissions { mode: (self.stat.st_mode as mode_t) }
407     }
408
409     pub fn file_type(&self) -> FileType {
410         FileType { mode: self.stat.st_mode as mode_t }
411     }
412 }
413
414 #[cfg(target_os = "netbsd")]
415 impl FileAttr {
416     pub fn modified(&self) -> io::Result<SystemTime> {
417         Ok(SystemTime::new(self.stat.st_mtime as i64, self.stat.st_mtimensec as i64))
418     }
419
420     pub fn accessed(&self) -> io::Result<SystemTime> {
421         Ok(SystemTime::new(self.stat.st_atime as i64, self.stat.st_atimensec as i64))
422     }
423
424     pub fn created(&self) -> io::Result<SystemTime> {
425         Ok(SystemTime::new(self.stat.st_birthtime as i64, self.stat.st_birthtimensec as i64))
426     }
427 }
428
429 #[cfg(not(target_os = "netbsd"))]
430 impl FileAttr {
431     #[cfg(not(any(target_os = "vxworks", target_os = "espidf", target_os = "horizon")))]
432     pub fn modified(&self) -> io::Result<SystemTime> {
433         #[cfg(target_pointer_width = "32")]
434         cfg_has_statx! {
435             if let Some(mtime) = self.stx_mtime() {
436                 return Ok(SystemTime::new(mtime.tv_sec, mtime.tv_nsec as i64));
437             }
438         }
439
440         Ok(SystemTime::new(self.stat.st_mtime as i64, self.stat.st_mtime_nsec as i64))
441     }
442
443     #[cfg(any(target_os = "vxworks", target_os = "espidf"))]
444     pub fn modified(&self) -> io::Result<SystemTime> {
445         Ok(SystemTime::new(self.stat.st_mtime as i64, 0))
446     }
447
448     #[cfg(target_os = "horizon")]
449     pub fn modified(&self) -> io::Result<SystemTime> {
450         Ok(SystemTime::from(self.stat.st_mtim))
451     }
452
453     #[cfg(not(any(target_os = "vxworks", target_os = "espidf", target_os = "horizon")))]
454     pub fn accessed(&self) -> io::Result<SystemTime> {
455         #[cfg(target_pointer_width = "32")]
456         cfg_has_statx! {
457             if let Some(atime) = self.stx_atime() {
458                 return Ok(SystemTime::new(atime.tv_sec, atime.tv_nsec as i64));
459             }
460         }
461
462         Ok(SystemTime::new(self.stat.st_atime as i64, self.stat.st_atime_nsec as i64))
463     }
464
465     #[cfg(any(target_os = "vxworks", target_os = "espidf"))]
466     pub fn accessed(&self) -> io::Result<SystemTime> {
467         Ok(SystemTime::new(self.stat.st_atime as i64, 0))
468     }
469
470     #[cfg(target_os = "horizon")]
471     pub fn accessed(&self) -> io::Result<SystemTime> {
472         Ok(SystemTime::from(self.stat.st_atim))
473     }
474
475     #[cfg(any(
476         target_os = "freebsd",
477         target_os = "openbsd",
478         target_os = "macos",
479         target_os = "ios",
480         target_os = "watchos",
481     ))]
482     pub fn created(&self) -> io::Result<SystemTime> {
483         Ok(SystemTime::new(self.stat.st_birthtime as i64, self.stat.st_birthtime_nsec as i64))
484     }
485
486     #[cfg(not(any(
487         target_os = "freebsd",
488         target_os = "openbsd",
489         target_os = "macos",
490         target_os = "ios",
491         target_os = "watchos",
492     )))]
493     pub fn created(&self) -> io::Result<SystemTime> {
494         cfg_has_statx! {
495             if let Some(ext) = &self.statx_extra_fields {
496                 return if (ext.stx_mask & libc::STATX_BTIME) != 0 {
497                     Ok(SystemTime::new(ext.stx_btime.tv_sec, ext.stx_btime.tv_nsec as i64))
498                 } else {
499                     Err(io::const_io_error!(
500                         io::ErrorKind::Uncategorized,
501                         "creation time is not available for the filesystem",
502                     ))
503                 };
504             }
505         }
506
507         Err(io::const_io_error!(
508             io::ErrorKind::Unsupported,
509             "creation time is not available on this platform \
510                             currently",
511         ))
512     }
513 }
514
515 impl AsInner<stat64> for FileAttr {
516     fn as_inner(&self) -> &stat64 {
517         &self.stat
518     }
519 }
520
521 impl FilePermissions {
522     pub fn readonly(&self) -> bool {
523         // check if any class (owner, group, others) has write permission
524         self.mode & 0o222 == 0
525     }
526
527     pub fn set_readonly(&mut self, readonly: bool) {
528         if readonly {
529             // remove write permission for all classes; equivalent to `chmod a-w <file>`
530             self.mode &= !0o222;
531         } else {
532             // add write permission for all classes; equivalent to `chmod a+w <file>`
533             self.mode |= 0o222;
534         }
535     }
536     pub fn mode(&self) -> u32 {
537         self.mode as u32
538     }
539 }
540
541 impl FileTimes {
542     pub fn set_accessed(&mut self, t: SystemTime) {
543         self.accessed = Some(t);
544     }
545
546     pub fn set_modified(&mut self, t: SystemTime) {
547         self.modified = Some(t);
548     }
549 }
550
551 impl FileType {
552     pub fn is_dir(&self) -> bool {
553         self.is(libc::S_IFDIR)
554     }
555     pub fn is_file(&self) -> bool {
556         self.is(libc::S_IFREG)
557     }
558     pub fn is_symlink(&self) -> bool {
559         self.is(libc::S_IFLNK)
560     }
561
562     pub fn is(&self, mode: mode_t) -> bool {
563         self.masked() == mode
564     }
565
566     fn masked(&self) -> mode_t {
567         self.mode & libc::S_IFMT
568     }
569 }
570
571 impl FromInner<u32> for FilePermissions {
572     fn from_inner(mode: u32) -> FilePermissions {
573         FilePermissions { mode: mode as mode_t }
574     }
575 }
576
577 impl fmt::Debug for ReadDir {
578     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
579         // This will only be called from std::fs::ReadDir, which will add a "ReadDir()" frame.
580         // Thus the result will be e g 'ReadDir("/home")'
581         fmt::Debug::fmt(&*self.inner.root, f)
582     }
583 }
584
585 impl Iterator for ReadDir {
586     type Item = io::Result<DirEntry>;
587
588     #[cfg(any(
589         target_os = "android",
590         target_os = "linux",
591         target_os = "solaris",
592         target_os = "fuchsia",
593         target_os = "redox",
594         target_os = "illumos"
595     ))]
596     fn next(&mut self) -> Option<io::Result<DirEntry>> {
597         unsafe {
598             loop {
599                 // As of POSIX.1-2017, readdir() is not required to be thread safe; only
600                 // readdir_r() is. However, readdir_r() cannot correctly handle platforms
601                 // with unlimited or variable NAME_MAX.  Many modern platforms guarantee
602                 // thread safety for readdir() as long an individual DIR* is not accessed
603                 // concurrently, which is sufficient for Rust.
604                 super::os::set_errno(0);
605                 let entry_ptr = readdir64(self.inner.dirp.0);
606                 if entry_ptr.is_null() {
607                     // null can mean either the end is reached or an error occurred.
608                     // So we had to clear errno beforehand to check for an error now.
609                     return match super::os::errno() {
610                         0 => None,
611                         e => Some(Err(Error::from_raw_os_error(e))),
612                     };
613                 }
614
615                 // The dirent64 struct is a weird imaginary thing that isn't ever supposed
616                 // to be worked with by value. Its trailing d_name field is declared
617                 // variously as [c_char; 256] or [c_char; 1] on different systems but
618                 // either way that size is meaningless; only the offset of d_name is
619                 // meaningful. The dirent64 pointers that libc returns from readdir64 are
620                 // allowed to point to allocations smaller _or_ LARGER than implied by the
621                 // definition of the struct.
622                 //
623                 // As such, we need to be even more careful with dirent64 than if its
624                 // contents were "simply" partially initialized data.
625                 //
626                 // Like for uninitialized contents, converting entry_ptr to `&dirent64`
627                 // would not be legal. However, unique to dirent64 is that we don't even
628                 // get to use `addr_of!((*entry_ptr).d_name)` because that operation
629                 // requires the full extent of *entry_ptr to be in bounds of the same
630                 // allocation, which is not necessarily the case here.
631                 //
632                 // Absent any other way to obtain a pointer to `(*entry_ptr).d_name`
633                 // legally in Rust analogously to how it would be done in C, we instead
634                 // need to make our own non-libc allocation that conforms to the weird
635                 // imaginary definition of dirent64, and use that for a field offset
636                 // computation.
637                 macro_rules! offset_ptr {
638                     ($entry_ptr:expr, $field:ident) => {{
639                         const OFFSET: isize = {
640                             let delusion = MaybeUninit::<dirent64>::uninit();
641                             let entry_ptr = delusion.as_ptr();
642                             unsafe {
643                                 ptr::addr_of!((*entry_ptr).$field)
644                                     .cast::<u8>()
645                                     .offset_from(entry_ptr.cast::<u8>())
646                             }
647                         };
648                         if true {
649                             // Cast to the same type determined by the else branch.
650                             $entry_ptr.byte_offset(OFFSET).cast::<_>()
651                         } else {
652                             #[allow(deref_nullptr)]
653                             {
654                                 ptr::addr_of!((*ptr::null::<dirent64>()).$field)
655                             }
656                         }
657                     }};
658                 }
659
660                 // d_name is guaranteed to be null-terminated.
661                 let name = CStr::from_ptr(offset_ptr!(entry_ptr, d_name).cast());
662                 let name_bytes = name.to_bytes();
663                 if name_bytes == b"." || name_bytes == b".." {
664                     continue;
665                 }
666
667                 let entry = dirent64_min {
668                     d_ino: *offset_ptr!(entry_ptr, d_ino) as u64,
669                     #[cfg(not(any(target_os = "solaris", target_os = "illumos")))]
670                     d_type: *offset_ptr!(entry_ptr, d_type) as u8,
671                 };
672
673                 return Some(Ok(DirEntry {
674                     entry,
675                     name: name.to_owned(),
676                     dir: Arc::clone(&self.inner),
677                 }));
678             }
679         }
680     }
681
682     #[cfg(not(any(
683         target_os = "android",
684         target_os = "linux",
685         target_os = "solaris",
686         target_os = "fuchsia",
687         target_os = "redox",
688         target_os = "illumos"
689     )))]
690     fn next(&mut self) -> Option<io::Result<DirEntry>> {
691         if self.end_of_stream {
692             return None;
693         }
694
695         unsafe {
696             let mut ret = DirEntry { entry: mem::zeroed(), dir: Arc::clone(&self.inner) };
697             let mut entry_ptr = ptr::null_mut();
698             loop {
699                 let err = readdir64_r(self.inner.dirp.0, &mut ret.entry, &mut entry_ptr);
700                 if err != 0 {
701                     if entry_ptr.is_null() {
702                         // We encountered an error (which will be returned in this iteration), but
703                         // we also reached the end of the directory stream. The `end_of_stream`
704                         // flag is enabled to make sure that we return `None` in the next iteration
705                         // (instead of looping forever)
706                         self.end_of_stream = true;
707                     }
708                     return Some(Err(Error::from_raw_os_error(err)));
709                 }
710                 if entry_ptr.is_null() {
711                     return None;
712                 }
713                 if ret.name_bytes() != b"." && ret.name_bytes() != b".." {
714                     return Some(Ok(ret));
715                 }
716             }
717         }
718     }
719 }
720
721 impl Drop for Dir {
722     fn drop(&mut self) {
723         let r = unsafe { libc::closedir(self.0) };
724         assert!(
725             r == 0 || crate::io::Error::last_os_error().kind() == crate::io::ErrorKind::Interrupted,
726             "unexpected error during closedir: {:?}",
727             crate::io::Error::last_os_error()
728         );
729     }
730 }
731
732 impl DirEntry {
733     pub fn path(&self) -> PathBuf {
734         self.dir.root.join(self.file_name_os_str())
735     }
736
737     pub fn file_name(&self) -> OsString {
738         self.file_name_os_str().to_os_string()
739     }
740
741     #[cfg(all(
742         any(target_os = "linux", target_os = "emscripten", target_os = "android"),
743         not(miri)
744     ))]
745     pub fn metadata(&self) -> io::Result<FileAttr> {
746         let fd = cvt(unsafe { dirfd(self.dir.dirp.0) })?;
747         let name = self.name_cstr().as_ptr();
748
749         cfg_has_statx! {
750             if let Some(ret) = unsafe { try_statx(
751                 fd,
752                 name,
753                 libc::AT_SYMLINK_NOFOLLOW | libc::AT_STATX_SYNC_AS_STAT,
754                 libc::STATX_ALL,
755             ) } {
756                 return ret;
757             }
758         }
759
760         let mut stat: stat64 = unsafe { mem::zeroed() };
761         cvt(unsafe { fstatat64(fd, name, &mut stat, libc::AT_SYMLINK_NOFOLLOW) })?;
762         Ok(FileAttr::from_stat64(stat))
763     }
764
765     #[cfg(any(
766         not(any(target_os = "linux", target_os = "emscripten", target_os = "android")),
767         miri
768     ))]
769     pub fn metadata(&self) -> io::Result<FileAttr> {
770         lstat(&self.path())
771     }
772
773     #[cfg(any(
774         target_os = "solaris",
775         target_os = "illumos",
776         target_os = "haiku",
777         target_os = "vxworks"
778     ))]
779     pub fn file_type(&self) -> io::Result<FileType> {
780         self.metadata().map(|m| m.file_type())
781     }
782
783     #[cfg(not(any(
784         target_os = "solaris",
785         target_os = "illumos",
786         target_os = "haiku",
787         target_os = "vxworks"
788     )))]
789     pub fn file_type(&self) -> io::Result<FileType> {
790         match self.entry.d_type {
791             libc::DT_CHR => Ok(FileType { mode: libc::S_IFCHR }),
792             libc::DT_FIFO => Ok(FileType { mode: libc::S_IFIFO }),
793             libc::DT_LNK => Ok(FileType { mode: libc::S_IFLNK }),
794             libc::DT_REG => Ok(FileType { mode: libc::S_IFREG }),
795             libc::DT_SOCK => Ok(FileType { mode: libc::S_IFSOCK }),
796             libc::DT_DIR => Ok(FileType { mode: libc::S_IFDIR }),
797             libc::DT_BLK => Ok(FileType { mode: libc::S_IFBLK }),
798             _ => self.metadata().map(|m| m.file_type()),
799         }
800     }
801
802     #[cfg(any(
803         target_os = "macos",
804         target_os = "ios",
805         target_os = "watchos",
806         target_os = "linux",
807         target_os = "emscripten",
808         target_os = "android",
809         target_os = "solaris",
810         target_os = "illumos",
811         target_os = "haiku",
812         target_os = "l4re",
813         target_os = "fuchsia",
814         target_os = "redox",
815         target_os = "vxworks",
816         target_os = "espidf",
817         target_os = "horizon"
818     ))]
819     pub fn ino(&self) -> u64 {
820         self.entry.d_ino as u64
821     }
822
823     #[cfg(any(
824         target_os = "freebsd",
825         target_os = "openbsd",
826         target_os = "netbsd",
827         target_os = "dragonfly"
828     ))]
829     pub fn ino(&self) -> u64 {
830         self.entry.d_fileno as u64
831     }
832
833     #[cfg(any(
834         target_os = "macos",
835         target_os = "ios",
836         target_os = "watchos",
837         target_os = "netbsd",
838         target_os = "openbsd",
839         target_os = "freebsd",
840         target_os = "dragonfly"
841     ))]
842     fn name_bytes(&self) -> &[u8] {
843         use crate::slice;
844         unsafe {
845             slice::from_raw_parts(
846                 self.entry.d_name.as_ptr() as *const u8,
847                 self.entry.d_namlen as usize,
848             )
849         }
850     }
851     #[cfg(not(any(
852         target_os = "macos",
853         target_os = "ios",
854         target_os = "watchos",
855         target_os = "netbsd",
856         target_os = "openbsd",
857         target_os = "freebsd",
858         target_os = "dragonfly"
859     )))]
860     fn name_bytes(&self) -> &[u8] {
861         self.name_cstr().to_bytes()
862     }
863
864     #[cfg(not(any(
865         target_os = "android",
866         target_os = "linux",
867         target_os = "solaris",
868         target_os = "illumos",
869         target_os = "fuchsia",
870         target_os = "redox"
871     )))]
872     fn name_cstr(&self) -> &CStr {
873         unsafe { CStr::from_ptr(self.entry.d_name.as_ptr()) }
874     }
875     #[cfg(any(
876         target_os = "android",
877         target_os = "linux",
878         target_os = "solaris",
879         target_os = "illumos",
880         target_os = "fuchsia",
881         target_os = "redox"
882     ))]
883     fn name_cstr(&self) -> &CStr {
884         &self.name
885     }
886
887     pub fn file_name_os_str(&self) -> &OsStr {
888         OsStr::from_bytes(self.name_bytes())
889     }
890 }
891
892 impl OpenOptions {
893     pub fn new() -> OpenOptions {
894         OpenOptions {
895             // generic
896             read: false,
897             write: false,
898             append: false,
899             truncate: false,
900             create: false,
901             create_new: false,
902             // system-specific
903             custom_flags: 0,
904             mode: 0o666,
905         }
906     }
907
908     pub fn read(&mut self, read: bool) {
909         self.read = read;
910     }
911     pub fn write(&mut self, write: bool) {
912         self.write = write;
913     }
914     pub fn append(&mut self, append: bool) {
915         self.append = append;
916     }
917     pub fn truncate(&mut self, truncate: bool) {
918         self.truncate = truncate;
919     }
920     pub fn create(&mut self, create: bool) {
921         self.create = create;
922     }
923     pub fn create_new(&mut self, create_new: bool) {
924         self.create_new = create_new;
925     }
926
927     pub fn custom_flags(&mut self, flags: i32) {
928         self.custom_flags = flags;
929     }
930     pub fn mode(&mut self, mode: u32) {
931         self.mode = mode as mode_t;
932     }
933
934     fn get_access_mode(&self) -> io::Result<c_int> {
935         match (self.read, self.write, self.append) {
936             (true, false, false) => Ok(libc::O_RDONLY),
937             (false, true, false) => Ok(libc::O_WRONLY),
938             (true, true, false) => Ok(libc::O_RDWR),
939             (false, _, true) => Ok(libc::O_WRONLY | libc::O_APPEND),
940             (true, _, true) => Ok(libc::O_RDWR | libc::O_APPEND),
941             (false, false, false) => Err(Error::from_raw_os_error(libc::EINVAL)),
942         }
943     }
944
945     fn get_creation_mode(&self) -> io::Result<c_int> {
946         match (self.write, self.append) {
947             (true, false) => {}
948             (false, false) => {
949                 if self.truncate || self.create || self.create_new {
950                     return Err(Error::from_raw_os_error(libc::EINVAL));
951                 }
952             }
953             (_, true) => {
954                 if self.truncate && !self.create_new {
955                     return Err(Error::from_raw_os_error(libc::EINVAL));
956                 }
957             }
958         }
959
960         Ok(match (self.create, self.truncate, self.create_new) {
961             (false, false, false) => 0,
962             (true, false, false) => libc::O_CREAT,
963             (false, true, false) => libc::O_TRUNC,
964             (true, true, false) => libc::O_CREAT | libc::O_TRUNC,
965             (_, _, true) => libc::O_CREAT | libc::O_EXCL,
966         })
967     }
968 }
969
970 impl File {
971     pub fn open(path: &Path, opts: &OpenOptions) -> io::Result<File> {
972         run_path_with_cstr(path, |path| File::open_c(path, opts))
973     }
974
975     pub fn open_c(path: &CStr, opts: &OpenOptions) -> io::Result<File> {
976         let flags = libc::O_CLOEXEC
977             | opts.get_access_mode()?
978             | opts.get_creation_mode()?
979             | (opts.custom_flags as c_int & !libc::O_ACCMODE);
980         // The third argument of `open64` is documented to have type `mode_t`. On
981         // some platforms (like macOS, where `open64` is actually `open`), `mode_t` is `u16`.
982         // However, since this is a variadic function, C integer promotion rules mean that on
983         // the ABI level, this still gets passed as `c_int` (aka `u32` on Unix platforms).
984         let fd = cvt_r(|| unsafe { open64(path.as_ptr(), flags, opts.mode as c_int) })?;
985         Ok(File(unsafe { FileDesc::from_raw_fd(fd) }))
986     }
987
988     pub fn file_attr(&self) -> io::Result<FileAttr> {
989         let fd = self.as_raw_fd();
990
991         cfg_has_statx! {
992             if let Some(ret) = unsafe { try_statx(
993                 fd,
994                 b"\0" as *const _ as *const c_char,
995                 libc::AT_EMPTY_PATH | libc::AT_STATX_SYNC_AS_STAT,
996                 libc::STATX_ALL,
997             ) } {
998                 return ret;
999             }
1000         }
1001
1002         let mut stat: stat64 = unsafe { mem::zeroed() };
1003         cvt(unsafe { fstat64(fd, &mut stat) })?;
1004         Ok(FileAttr::from_stat64(stat))
1005     }
1006
1007     pub fn fsync(&self) -> io::Result<()> {
1008         cvt_r(|| unsafe { os_fsync(self.as_raw_fd()) })?;
1009         return Ok(());
1010
1011         #[cfg(any(target_os = "macos", target_os = "ios", target_os = "watchos"))]
1012         unsafe fn os_fsync(fd: c_int) -> c_int {
1013             libc::fcntl(fd, libc::F_FULLFSYNC)
1014         }
1015         #[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "watchos")))]
1016         unsafe fn os_fsync(fd: c_int) -> c_int {
1017             libc::fsync(fd)
1018         }
1019     }
1020
1021     pub fn datasync(&self) -> io::Result<()> {
1022         cvt_r(|| unsafe { os_datasync(self.as_raw_fd()) })?;
1023         return Ok(());
1024
1025         #[cfg(any(target_os = "macos", target_os = "ios", target_os = "watchos"))]
1026         unsafe fn os_datasync(fd: c_int) -> c_int {
1027             libc::fcntl(fd, libc::F_FULLFSYNC)
1028         }
1029         #[cfg(any(
1030             target_os = "freebsd",
1031             target_os = "linux",
1032             target_os = "android",
1033             target_os = "netbsd",
1034             target_os = "openbsd"
1035         ))]
1036         unsafe fn os_datasync(fd: c_int) -> c_int {
1037             libc::fdatasync(fd)
1038         }
1039         #[cfg(not(any(
1040             target_os = "android",
1041             target_os = "freebsd",
1042             target_os = "ios",
1043             target_os = "linux",
1044             target_os = "macos",
1045             target_os = "netbsd",
1046             target_os = "openbsd",
1047             target_os = "watchos",
1048         )))]
1049         unsafe fn os_datasync(fd: c_int) -> c_int {
1050             libc::fsync(fd)
1051         }
1052     }
1053
1054     pub fn truncate(&self, size: u64) -> io::Result<()> {
1055         let size: off64_t =
1056             size.try_into().map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
1057         cvt_r(|| unsafe { ftruncate64(self.as_raw_fd(), size) }).map(drop)
1058     }
1059
1060     pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
1061         self.0.read(buf)
1062     }
1063
1064     pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
1065         self.0.read_vectored(bufs)
1066     }
1067
1068     #[inline]
1069     pub fn is_read_vectored(&self) -> bool {
1070         self.0.is_read_vectored()
1071     }
1072
1073     pub fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
1074         self.0.read_at(buf, offset)
1075     }
1076
1077     pub fn read_buf(&self, cursor: BorrowedCursor<'_>) -> io::Result<()> {
1078         self.0.read_buf(cursor)
1079     }
1080
1081     pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
1082         self.0.write(buf)
1083     }
1084
1085     pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
1086         self.0.write_vectored(bufs)
1087     }
1088
1089     #[inline]
1090     pub fn is_write_vectored(&self) -> bool {
1091         self.0.is_write_vectored()
1092     }
1093
1094     pub fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
1095         self.0.write_at(buf, offset)
1096     }
1097
1098     pub fn flush(&self) -> io::Result<()> {
1099         Ok(())
1100     }
1101
1102     pub fn seek(&self, pos: SeekFrom) -> io::Result<u64> {
1103         let (whence, pos) = match pos {
1104             // Casting to `i64` is fine, too large values will end up as
1105             // negative which will cause an error in `lseek64`.
1106             SeekFrom::Start(off) => (libc::SEEK_SET, off as i64),
1107             SeekFrom::End(off) => (libc::SEEK_END, off),
1108             SeekFrom::Current(off) => (libc::SEEK_CUR, off),
1109         };
1110         let n = cvt(unsafe { lseek64(self.as_raw_fd(), pos as off64_t, whence) })?;
1111         Ok(n as u64)
1112     }
1113
1114     pub fn duplicate(&self) -> io::Result<File> {
1115         self.0.duplicate().map(File)
1116     }
1117
1118     pub fn set_permissions(&self, perm: FilePermissions) -> io::Result<()> {
1119         cvt_r(|| unsafe { libc::fchmod(self.as_raw_fd(), perm.mode) })?;
1120         Ok(())
1121     }
1122
1123     pub fn set_times(&self, times: FileTimes) -> io::Result<()> {
1124         #[cfg(not(any(target_os = "redox", target_os = "espidf", target_os = "horizon")))]
1125         let to_timespec = |time: Option<SystemTime>| {
1126             match time {
1127                 Some(time) if let Some(ts) = time.t.to_timespec() => Ok(ts),
1128                 Some(time) if time > crate::sys::time::UNIX_EPOCH => Err(io::const_io_error!(io::ErrorKind::InvalidInput, "timestamp is too large to set as a file time")),
1129                 Some(_) => Err(io::const_io_error!(io::ErrorKind::InvalidInput, "timestamp is too small to set as a file time")),
1130                 None => Ok(libc::timespec { tv_sec: 0, tv_nsec: libc::UTIME_OMIT as _ }),
1131             }
1132         };
1133         #[cfg(not(any(target_os = "redox", target_os = "espidf", target_os = "horizon")))]
1134         let times = [to_timespec(times.accessed)?, to_timespec(times.modified)?];
1135         cfg_if::cfg_if! {
1136             if #[cfg(any(target_os = "redox", target_os = "espidf", target_os = "horizon"))] {
1137                 // Redox doesn't appear to support `UTIME_OMIT`.
1138                 // ESP-IDF and HorizonOS do not support `futimens` at all and the behavior for those OS is therefore
1139                 // the same as for Redox.
1140                 drop(times);
1141                 Err(io::const_io_error!(
1142                     io::ErrorKind::Unsupported,
1143                     "setting file times not supported",
1144                 ))
1145             } else if #[cfg(any(target_os = "android", target_os = "macos"))] {
1146                 // futimens requires macOS 10.13, and Android API level 19
1147                 cvt(unsafe {
1148                     weak!(fn futimens(c_int, *const libc::timespec) -> c_int);
1149                     match futimens.get() {
1150                         Some(futimens) => futimens(self.as_raw_fd(), times.as_ptr()),
1151                         #[cfg(target_os = "macos")]
1152                         None => {
1153                             fn ts_to_tv(ts: &libc::timespec) -> libc::timeval {
1154                                 libc::timeval {
1155                                     tv_sec: ts.tv_sec,
1156                                     tv_usec: (ts.tv_nsec / 1000) as _
1157                                 }
1158                             }
1159                             let timevals = [ts_to_tv(&times[0]), ts_to_tv(&times[1])];
1160                             libc::futimes(self.as_raw_fd(), timevals.as_ptr())
1161                         }
1162                         // futimes requires even newer Android.
1163                         #[cfg(target_os = "android")]
1164                         None => return Err(io::const_io_error!(
1165                             io::ErrorKind::Unsupported,
1166                             "setting file times requires Android API level >= 19",
1167                         )),
1168                     }
1169                 })?;
1170                 Ok(())
1171             } else {
1172                 cvt(unsafe { libc::futimens(self.as_raw_fd(), times.as_ptr()) })?;
1173                 Ok(())
1174             }
1175         }
1176     }
1177 }
1178
1179 impl DirBuilder {
1180     pub fn new() -> DirBuilder {
1181         DirBuilder { mode: 0o777 }
1182     }
1183
1184     pub fn mkdir(&self, p: &Path) -> io::Result<()> {
1185         run_path_with_cstr(p, |p| cvt(unsafe { libc::mkdir(p.as_ptr(), self.mode) }).map(|_| ()))
1186     }
1187
1188     pub fn set_mode(&mut self, mode: u32) {
1189         self.mode = mode as mode_t;
1190     }
1191 }
1192
1193 impl AsInner<FileDesc> for File {
1194     fn as_inner(&self) -> &FileDesc {
1195         &self.0
1196     }
1197 }
1198
1199 impl AsInnerMut<FileDesc> for File {
1200     fn as_inner_mut(&mut self) -> &mut FileDesc {
1201         &mut self.0
1202     }
1203 }
1204
1205 impl IntoInner<FileDesc> for File {
1206     fn into_inner(self) -> FileDesc {
1207         self.0
1208     }
1209 }
1210
1211 impl FromInner<FileDesc> for File {
1212     fn from_inner(file_desc: FileDesc) -> Self {
1213         Self(file_desc)
1214     }
1215 }
1216
1217 impl AsFd for File {
1218     fn as_fd(&self) -> BorrowedFd<'_> {
1219         self.0.as_fd()
1220     }
1221 }
1222
1223 impl AsRawFd for File {
1224     fn as_raw_fd(&self) -> RawFd {
1225         self.0.as_raw_fd()
1226     }
1227 }
1228
1229 impl IntoRawFd for File {
1230     fn into_raw_fd(self) -> RawFd {
1231         self.0.into_raw_fd()
1232     }
1233 }
1234
1235 impl FromRawFd for File {
1236     unsafe fn from_raw_fd(raw_fd: RawFd) -> Self {
1237         Self(FromRawFd::from_raw_fd(raw_fd))
1238     }
1239 }
1240
1241 impl fmt::Debug for File {
1242     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1243         #[cfg(any(
1244             target_os = "linux",
1245             target_os = "netbsd",
1246             target_os = "illumos",
1247             target_os = "solaris"
1248         ))]
1249         fn get_path(fd: c_int) -> Option<PathBuf> {
1250             let mut p = PathBuf::from("/proc/self/fd");
1251             p.push(&fd.to_string());
1252             readlink(&p).ok()
1253         }
1254
1255         #[cfg(target_os = "macos")]
1256         fn get_path(fd: c_int) -> Option<PathBuf> {
1257             // FIXME: The use of PATH_MAX is generally not encouraged, but it
1258             // is inevitable in this case because macOS defines `fcntl` with
1259             // `F_GETPATH` in terms of `MAXPATHLEN`, and there are no
1260             // alternatives. If a better method is invented, it should be used
1261             // instead.
1262             let mut buf = vec![0; libc::PATH_MAX as usize];
1263             let n = unsafe { libc::fcntl(fd, libc::F_GETPATH, buf.as_ptr()) };
1264             if n == -1 {
1265                 return None;
1266             }
1267             let l = buf.iter().position(|&c| c == 0).unwrap();
1268             buf.truncate(l as usize);
1269             buf.shrink_to_fit();
1270             Some(PathBuf::from(OsString::from_vec(buf)))
1271         }
1272
1273         #[cfg(all(target_os = "freebsd", target_arch = "x86_64"))]
1274         fn get_path(fd: c_int) -> Option<PathBuf> {
1275             let info = Box::<libc::kinfo_file>::new_zeroed();
1276             let mut info = unsafe { info.assume_init() };
1277             info.kf_structsize = mem::size_of::<libc::kinfo_file>() as libc::c_int;
1278             let n = unsafe { libc::fcntl(fd, libc::F_KINFO, &mut *info) };
1279             if n == -1 {
1280                 return None;
1281             }
1282             let buf = unsafe { CStr::from_ptr(info.kf_path.as_mut_ptr()).to_bytes().to_vec() };
1283             Some(PathBuf::from(OsString::from_vec(buf)))
1284         }
1285
1286         #[cfg(target_os = "vxworks")]
1287         fn get_path(fd: c_int) -> Option<PathBuf> {
1288             let mut buf = vec![0; libc::PATH_MAX as usize];
1289             let n = unsafe { libc::ioctl(fd, libc::FIOGETNAME, buf.as_ptr()) };
1290             if n == -1 {
1291                 return None;
1292             }
1293             let l = buf.iter().position(|&c| c == 0).unwrap();
1294             buf.truncate(l as usize);
1295             Some(PathBuf::from(OsString::from_vec(buf)))
1296         }
1297
1298         #[cfg(not(any(
1299             target_os = "linux",
1300             target_os = "macos",
1301             target_os = "vxworks",
1302             all(target_os = "freebsd", target_arch = "x86_64"),
1303             target_os = "netbsd",
1304             target_os = "illumos",
1305             target_os = "solaris"
1306         )))]
1307         fn get_path(_fd: c_int) -> Option<PathBuf> {
1308             // FIXME(#24570): implement this for other Unix platforms
1309             None
1310         }
1311
1312         #[cfg(any(
1313             target_os = "linux",
1314             target_os = "macos",
1315             target_os = "freebsd",
1316             target_os = "netbsd",
1317             target_os = "openbsd",
1318             target_os = "vxworks"
1319         ))]
1320         fn get_mode(fd: c_int) -> Option<(bool, bool)> {
1321             let mode = unsafe { libc::fcntl(fd, libc::F_GETFL) };
1322             if mode == -1 {
1323                 return None;
1324             }
1325             match mode & libc::O_ACCMODE {
1326                 libc::O_RDONLY => Some((true, false)),
1327                 libc::O_RDWR => Some((true, true)),
1328                 libc::O_WRONLY => Some((false, true)),
1329                 _ => None,
1330             }
1331         }
1332
1333         #[cfg(not(any(
1334             target_os = "linux",
1335             target_os = "macos",
1336             target_os = "freebsd",
1337             target_os = "netbsd",
1338             target_os = "openbsd",
1339             target_os = "vxworks"
1340         )))]
1341         fn get_mode(_fd: c_int) -> Option<(bool, bool)> {
1342             // FIXME(#24570): implement this for other Unix platforms
1343             None
1344         }
1345
1346         let fd = self.as_raw_fd();
1347         let mut b = f.debug_struct("File");
1348         b.field("fd", &fd);
1349         if let Some(path) = get_path(fd) {
1350             b.field("path", &path);
1351         }
1352         if let Some((read, write)) = get_mode(fd) {
1353             b.field("read", &read).field("write", &write);
1354         }
1355         b.finish()
1356     }
1357 }
1358
1359 pub fn readdir(path: &Path) -> io::Result<ReadDir> {
1360     let ptr = run_path_with_cstr(path, |p| unsafe { Ok(libc::opendir(p.as_ptr())) })?;
1361     if ptr.is_null() {
1362         Err(Error::last_os_error())
1363     } else {
1364         let root = path.to_path_buf();
1365         let inner = InnerReadDir { dirp: Dir(ptr), root };
1366         Ok(ReadDir {
1367             inner: Arc::new(inner),
1368             #[cfg(not(any(
1369                 target_os = "android",
1370                 target_os = "linux",
1371                 target_os = "solaris",
1372                 target_os = "illumos",
1373                 target_os = "fuchsia",
1374                 target_os = "redox",
1375             )))]
1376             end_of_stream: false,
1377         })
1378     }
1379 }
1380
1381 pub fn unlink(p: &Path) -> io::Result<()> {
1382     run_path_with_cstr(p, |p| cvt(unsafe { libc::unlink(p.as_ptr()) }).map(|_| ()))
1383 }
1384
1385 pub fn rename(old: &Path, new: &Path) -> io::Result<()> {
1386     run_path_with_cstr(old, |old| {
1387         run_path_with_cstr(new, |new| {
1388             cvt(unsafe { libc::rename(old.as_ptr(), new.as_ptr()) }).map(|_| ())
1389         })
1390     })
1391 }
1392
1393 pub fn set_perm(p: &Path, perm: FilePermissions) -> io::Result<()> {
1394     run_path_with_cstr(p, |p| cvt_r(|| unsafe { libc::chmod(p.as_ptr(), perm.mode) }).map(|_| ()))
1395 }
1396
1397 pub fn rmdir(p: &Path) -> io::Result<()> {
1398     run_path_with_cstr(p, |p| cvt(unsafe { libc::rmdir(p.as_ptr()) }).map(|_| ()))
1399 }
1400
1401 pub fn readlink(p: &Path) -> io::Result<PathBuf> {
1402     run_path_with_cstr(p, |c_path| {
1403         let p = c_path.as_ptr();
1404
1405         let mut buf = Vec::with_capacity(256);
1406
1407         loop {
1408             let buf_read =
1409                 cvt(unsafe { libc::readlink(p, buf.as_mut_ptr() as *mut _, buf.capacity()) })?
1410                     as usize;
1411
1412             unsafe {
1413                 buf.set_len(buf_read);
1414             }
1415
1416             if buf_read != buf.capacity() {
1417                 buf.shrink_to_fit();
1418
1419                 return Ok(PathBuf::from(OsString::from_vec(buf)));
1420             }
1421
1422             // Trigger the internal buffer resizing logic of `Vec` by requiring
1423             // more space than the current capacity. The length is guaranteed to be
1424             // the same as the capacity due to the if statement above.
1425             buf.reserve(1);
1426         }
1427     })
1428 }
1429
1430 pub fn symlink(original: &Path, link: &Path) -> io::Result<()> {
1431     run_path_with_cstr(original, |original| {
1432         run_path_with_cstr(link, |link| {
1433             cvt(unsafe { libc::symlink(original.as_ptr(), link.as_ptr()) }).map(|_| ())
1434         })
1435     })
1436 }
1437
1438 pub fn link(original: &Path, link: &Path) -> io::Result<()> {
1439     run_path_with_cstr(original, |original| {
1440         run_path_with_cstr(link, |link| {
1441             cfg_if::cfg_if! {
1442                 if #[cfg(any(target_os = "vxworks", target_os = "redox", target_os = "android", target_os = "espidf", target_os = "horizon"))] {
1443                     // VxWorks, Redox and ESP-IDF lack `linkat`, so use `link` instead. POSIX leaves
1444                     // it implementation-defined whether `link` follows symlinks, so rely on the
1445                     // `symlink_hard_link` test in library/std/src/fs/tests.rs to check the behavior.
1446                     // Android has `linkat` on newer versions, but we happen to know `link`
1447                     // always has the correct behavior, so it's here as well.
1448                     cvt(unsafe { libc::link(original.as_ptr(), link.as_ptr()) })?;
1449                 } else if #[cfg(target_os = "macos")] {
1450                     // On MacOS, older versions (<=10.9) lack support for linkat while newer
1451                     // versions have it. We want to use linkat if it is available, so we use weak!
1452                     // to check. `linkat` is preferable to `link` because it gives us a flag to
1453                     // specify how symlinks should be handled. We pass 0 as the flags argument,
1454                     // meaning it shouldn't follow symlinks.
1455                     weak!(fn linkat(c_int, *const c_char, c_int, *const c_char, c_int) -> c_int);
1456
1457                     if let Some(f) = linkat.get() {
1458                         cvt(unsafe { f(libc::AT_FDCWD, original.as_ptr(), libc::AT_FDCWD, link.as_ptr(), 0) })?;
1459                     } else {
1460                         cvt(unsafe { libc::link(original.as_ptr(), link.as_ptr()) })?;
1461                     };
1462                 } else {
1463                     // Where we can, use `linkat` instead of `link`; see the comment above
1464                     // this one for details on why.
1465                     cvt(unsafe { libc::linkat(libc::AT_FDCWD, original.as_ptr(), libc::AT_FDCWD, link.as_ptr(), 0) })?;
1466                 }
1467             }
1468             Ok(())
1469         })
1470     })
1471 }
1472
1473 pub fn stat(p: &Path) -> io::Result<FileAttr> {
1474     run_path_with_cstr(p, |p| {
1475         cfg_has_statx! {
1476             if let Some(ret) = unsafe { try_statx(
1477                 libc::AT_FDCWD,
1478                 p.as_ptr(),
1479                 libc::AT_STATX_SYNC_AS_STAT,
1480                 libc::STATX_ALL,
1481             ) } {
1482                 return ret;
1483             }
1484         }
1485
1486         let mut stat: stat64 = unsafe { mem::zeroed() };
1487         cvt(unsafe { stat64(p.as_ptr(), &mut stat) })?;
1488         Ok(FileAttr::from_stat64(stat))
1489     })
1490 }
1491
1492 pub fn lstat(p: &Path) -> io::Result<FileAttr> {
1493     run_path_with_cstr(p, |p| {
1494         cfg_has_statx! {
1495             if let Some(ret) = unsafe { try_statx(
1496                 libc::AT_FDCWD,
1497                 p.as_ptr(),
1498                 libc::AT_SYMLINK_NOFOLLOW | libc::AT_STATX_SYNC_AS_STAT,
1499                 libc::STATX_ALL,
1500             ) } {
1501                 return ret;
1502             }
1503         }
1504
1505         let mut stat: stat64 = unsafe { mem::zeroed() };
1506         cvt(unsafe { lstat64(p.as_ptr(), &mut stat) })?;
1507         Ok(FileAttr::from_stat64(stat))
1508     })
1509 }
1510
1511 pub fn canonicalize(p: &Path) -> io::Result<PathBuf> {
1512     let r = run_path_with_cstr(p, |path| unsafe {
1513         Ok(libc::realpath(path.as_ptr(), ptr::null_mut()))
1514     })?;
1515     if r.is_null() {
1516         return Err(io::Error::last_os_error());
1517     }
1518     Ok(PathBuf::from(OsString::from_vec(unsafe {
1519         let buf = CStr::from_ptr(r).to_bytes().to_vec();
1520         libc::free(r as *mut _);
1521         buf
1522     })))
1523 }
1524
1525 fn open_from(from: &Path) -> io::Result<(crate::fs::File, crate::fs::Metadata)> {
1526     use crate::fs::File;
1527     use crate::sys_common::fs::NOT_FILE_ERROR;
1528
1529     let reader = File::open(from)?;
1530     let metadata = reader.metadata()?;
1531     if !metadata.is_file() {
1532         return Err(NOT_FILE_ERROR);
1533     }
1534     Ok((reader, metadata))
1535 }
1536
1537 #[cfg(target_os = "espidf")]
1538 fn open_to_and_set_permissions(
1539     to: &Path,
1540     reader_metadata: crate::fs::Metadata,
1541 ) -> io::Result<(crate::fs::File, crate::fs::Metadata)> {
1542     use crate::fs::OpenOptions;
1543     let writer = OpenOptions::new().open(to)?;
1544     let writer_metadata = writer.metadata()?;
1545     Ok((writer, writer_metadata))
1546 }
1547
1548 #[cfg(not(target_os = "espidf"))]
1549 fn open_to_and_set_permissions(
1550     to: &Path,
1551     reader_metadata: crate::fs::Metadata,
1552 ) -> io::Result<(crate::fs::File, crate::fs::Metadata)> {
1553     use crate::fs::OpenOptions;
1554     use crate::os::unix::fs::{OpenOptionsExt, PermissionsExt};
1555
1556     let perm = reader_metadata.permissions();
1557     let writer = OpenOptions::new()
1558         // create the file with the correct mode right away
1559         .mode(perm.mode())
1560         .write(true)
1561         .create(true)
1562         .truncate(true)
1563         .open(to)?;
1564     let writer_metadata = writer.metadata()?;
1565     if writer_metadata.is_file() {
1566         // Set the correct file permissions, in case the file already existed.
1567         // Don't set the permissions on already existing non-files like
1568         // pipes/FIFOs or device nodes.
1569         writer.set_permissions(perm)?;
1570     }
1571     Ok((writer, writer_metadata))
1572 }
1573
1574 #[cfg(not(any(
1575     target_os = "linux",
1576     target_os = "android",
1577     target_os = "macos",
1578     target_os = "ios",
1579     target_os = "watchos",
1580 )))]
1581 pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
1582     let (mut reader, reader_metadata) = open_from(from)?;
1583     let (mut writer, _) = open_to_and_set_permissions(to, reader_metadata)?;
1584
1585     io::copy(&mut reader, &mut writer)
1586 }
1587
1588 #[cfg(any(target_os = "linux", target_os = "android"))]
1589 pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
1590     let (mut reader, reader_metadata) = open_from(from)?;
1591     let max_len = u64::MAX;
1592     let (mut writer, _) = open_to_and_set_permissions(to, reader_metadata)?;
1593
1594     use super::kernel_copy::{copy_regular_files, CopyResult};
1595
1596     match copy_regular_files(reader.as_raw_fd(), writer.as_raw_fd(), max_len) {
1597         CopyResult::Ended(bytes) => Ok(bytes),
1598         CopyResult::Error(e, _) => Err(e),
1599         CopyResult::Fallback(written) => match io::copy::generic_copy(&mut reader, &mut writer) {
1600             Ok(bytes) => Ok(bytes + written),
1601             Err(e) => Err(e),
1602         },
1603     }
1604 }
1605
1606 #[cfg(any(target_os = "macos", target_os = "ios", target_os = "watchos"))]
1607 pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
1608     use crate::sync::atomic::{AtomicBool, Ordering};
1609
1610     const COPYFILE_ACL: u32 = 1 << 0;
1611     const COPYFILE_STAT: u32 = 1 << 1;
1612     const COPYFILE_XATTR: u32 = 1 << 2;
1613     const COPYFILE_DATA: u32 = 1 << 3;
1614
1615     const COPYFILE_SECURITY: u32 = COPYFILE_STAT | COPYFILE_ACL;
1616     const COPYFILE_METADATA: u32 = COPYFILE_SECURITY | COPYFILE_XATTR;
1617     const COPYFILE_ALL: u32 = COPYFILE_METADATA | COPYFILE_DATA;
1618
1619     const COPYFILE_STATE_COPIED: u32 = 8;
1620
1621     #[allow(non_camel_case_types)]
1622     type copyfile_state_t = *mut libc::c_void;
1623     #[allow(non_camel_case_types)]
1624     type copyfile_flags_t = u32;
1625
1626     extern "C" {
1627         fn fcopyfile(
1628             from: libc::c_int,
1629             to: libc::c_int,
1630             state: copyfile_state_t,
1631             flags: copyfile_flags_t,
1632         ) -> libc::c_int;
1633         fn copyfile_state_alloc() -> copyfile_state_t;
1634         fn copyfile_state_free(state: copyfile_state_t) -> libc::c_int;
1635         fn copyfile_state_get(
1636             state: copyfile_state_t,
1637             flag: u32,
1638             dst: *mut libc::c_void,
1639         ) -> libc::c_int;
1640     }
1641
1642     struct FreeOnDrop(copyfile_state_t);
1643     impl Drop for FreeOnDrop {
1644         fn drop(&mut self) {
1645             // The code below ensures that `FreeOnDrop` is never a null pointer
1646             unsafe {
1647                 // `copyfile_state_free` returns -1 if the `to` or `from` files
1648                 // cannot be closed. However, this is not considered this an
1649                 // error.
1650                 copyfile_state_free(self.0);
1651             }
1652         }
1653     }
1654
1655     // MacOS prior to 10.12 don't support `fclonefileat`
1656     // We store the availability in a global to avoid unnecessary syscalls
1657     static HAS_FCLONEFILEAT: AtomicBool = AtomicBool::new(true);
1658     syscall! {
1659         fn fclonefileat(
1660             srcfd: libc::c_int,
1661             dst_dirfd: libc::c_int,
1662             dst: *const c_char,
1663             flags: libc::c_int
1664         ) -> libc::c_int
1665     }
1666
1667     let (reader, reader_metadata) = open_from(from)?;
1668
1669     // Opportunistically attempt to create a copy-on-write clone of `from`
1670     // using `fclonefileat`.
1671     if HAS_FCLONEFILEAT.load(Ordering::Relaxed) {
1672         let clonefile_result = run_path_with_cstr(to, |to| {
1673             cvt(unsafe { fclonefileat(reader.as_raw_fd(), libc::AT_FDCWD, to.as_ptr(), 0) })
1674         });
1675         match clonefile_result {
1676             Ok(_) => return Ok(reader_metadata.len()),
1677             Err(err) => match err.raw_os_error() {
1678                 // `fclonefileat` will fail on non-APFS volumes, if the
1679                 // destination already exists, or if the source and destination
1680                 // are on different devices. In all these cases `fcopyfile`
1681                 // should succeed.
1682                 Some(libc::ENOTSUP) | Some(libc::EEXIST) | Some(libc::EXDEV) => (),
1683                 Some(libc::ENOSYS) => HAS_FCLONEFILEAT.store(false, Ordering::Relaxed),
1684                 _ => return Err(err),
1685             },
1686         }
1687     }
1688
1689     // Fall back to using `fcopyfile` if `fclonefileat` does not succeed.
1690     let (writer, writer_metadata) = open_to_and_set_permissions(to, reader_metadata)?;
1691
1692     // We ensure that `FreeOnDrop` never contains a null pointer so it is
1693     // always safe to call `copyfile_state_free`
1694     let state = unsafe {
1695         let state = copyfile_state_alloc();
1696         if state.is_null() {
1697             return Err(crate::io::Error::last_os_error());
1698         }
1699         FreeOnDrop(state)
1700     };
1701
1702     let flags = if writer_metadata.is_file() { COPYFILE_ALL } else { COPYFILE_DATA };
1703
1704     cvt(unsafe { fcopyfile(reader.as_raw_fd(), writer.as_raw_fd(), state.0, flags) })?;
1705
1706     let mut bytes_copied: libc::off_t = 0;
1707     cvt(unsafe {
1708         copyfile_state_get(
1709             state.0,
1710             COPYFILE_STATE_COPIED,
1711             &mut bytes_copied as *mut libc::off_t as *mut libc::c_void,
1712         )
1713     })?;
1714     Ok(bytes_copied as u64)
1715 }
1716
1717 pub fn chown(path: &Path, uid: u32, gid: u32) -> io::Result<()> {
1718     run_path_with_cstr(path, |path| {
1719         cvt(unsafe { libc::chown(path.as_ptr(), uid as libc::uid_t, gid as libc::gid_t) })
1720             .map(|_| ())
1721     })
1722 }
1723
1724 pub fn fchown(fd: c_int, uid: u32, gid: u32) -> io::Result<()> {
1725     cvt(unsafe { libc::fchown(fd, uid as libc::uid_t, gid as libc::gid_t) })?;
1726     Ok(())
1727 }
1728
1729 pub fn lchown(path: &Path, uid: u32, gid: u32) -> io::Result<()> {
1730     run_path_with_cstr(path, |path| {
1731         cvt(unsafe { libc::lchown(path.as_ptr(), uid as libc::uid_t, gid as libc::gid_t) })
1732             .map(|_| ())
1733     })
1734 }
1735
1736 #[cfg(not(any(target_os = "fuchsia", target_os = "vxworks")))]
1737 pub fn chroot(dir: &Path) -> io::Result<()> {
1738     run_path_with_cstr(dir, |dir| cvt(unsafe { libc::chroot(dir.as_ptr()) }).map(|_| ()))
1739 }
1740
1741 pub use remove_dir_impl::remove_dir_all;
1742
1743 // Fallback for REDOX, ESP-ID, Horizon, and Miri
1744 #[cfg(any(target_os = "redox", target_os = "espidf", target_os = "horizon", miri))]
1745 mod remove_dir_impl {
1746     pub use crate::sys_common::fs::remove_dir_all;
1747 }
1748
1749 // Modern implementation using openat(), unlinkat() and fdopendir()
1750 #[cfg(not(any(target_os = "redox", target_os = "espidf", target_os = "horizon", miri)))]
1751 mod remove_dir_impl {
1752     use super::{lstat, Dir, DirEntry, InnerReadDir, ReadDir};
1753     use crate::ffi::CStr;
1754     use crate::io;
1755     use crate::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd};
1756     use crate::os::unix::prelude::{OwnedFd, RawFd};
1757     use crate::path::{Path, PathBuf};
1758     use crate::sync::Arc;
1759     use crate::sys::common::small_c_string::run_path_with_cstr;
1760     use crate::sys::{cvt, cvt_r};
1761
1762     #[cfg(not(any(
1763         target_os = "linux",
1764         all(target_os = "macos", not(target_arch = "aarch64"))
1765     )))]
1766     use libc::{fdopendir, openat, unlinkat};
1767     #[cfg(target_os = "linux")]
1768     use libc::{fdopendir, openat64 as openat, unlinkat};
1769     #[cfg(all(target_os = "macos", not(target_arch = "aarch64")))]
1770     use macos_weak::{fdopendir, openat, unlinkat};
1771
1772     #[cfg(all(target_os = "macos", not(target_arch = "aarch64")))]
1773     mod macos_weak {
1774         use crate::sys::weak::weak;
1775         use libc::{c_char, c_int, DIR};
1776
1777         fn get_openat_fn() -> Option<unsafe extern "C" fn(c_int, *const c_char, c_int) -> c_int> {
1778             weak!(fn openat(c_int, *const c_char, c_int) -> c_int);
1779             openat.get()
1780         }
1781
1782         pub fn has_openat() -> bool {
1783             get_openat_fn().is_some()
1784         }
1785
1786         pub unsafe fn openat(dirfd: c_int, pathname: *const c_char, flags: c_int) -> c_int {
1787             get_openat_fn().map(|openat| openat(dirfd, pathname, flags)).unwrap_or_else(|| {
1788                 crate::sys::unix::os::set_errno(libc::ENOSYS);
1789                 -1
1790             })
1791         }
1792
1793         pub unsafe fn fdopendir(fd: c_int) -> *mut DIR {
1794             #[cfg(all(target_os = "macos", target_arch = "x86"))]
1795             weak!(fn fdopendir(c_int) -> *mut DIR, "fdopendir$INODE64$UNIX2003");
1796             #[cfg(all(target_os = "macos", target_arch = "x86_64"))]
1797             weak!(fn fdopendir(c_int) -> *mut DIR, "fdopendir$INODE64");
1798             fdopendir.get().map(|fdopendir| fdopendir(fd)).unwrap_or_else(|| {
1799                 crate::sys::unix::os::set_errno(libc::ENOSYS);
1800                 crate::ptr::null_mut()
1801             })
1802         }
1803
1804         pub unsafe fn unlinkat(dirfd: c_int, pathname: *const c_char, flags: c_int) -> c_int {
1805             weak!(fn unlinkat(c_int, *const c_char, c_int) -> c_int);
1806             unlinkat.get().map(|unlinkat| unlinkat(dirfd, pathname, flags)).unwrap_or_else(|| {
1807                 crate::sys::unix::os::set_errno(libc::ENOSYS);
1808                 -1
1809             })
1810         }
1811     }
1812
1813     pub fn openat_nofollow_dironly(parent_fd: Option<RawFd>, p: &CStr) -> io::Result<OwnedFd> {
1814         let fd = cvt_r(|| unsafe {
1815             openat(
1816                 parent_fd.unwrap_or(libc::AT_FDCWD),
1817                 p.as_ptr(),
1818                 libc::O_CLOEXEC | libc::O_RDONLY | libc::O_NOFOLLOW | libc::O_DIRECTORY,
1819             )
1820         })?;
1821         Ok(unsafe { OwnedFd::from_raw_fd(fd) })
1822     }
1823
1824     fn fdreaddir(dir_fd: OwnedFd) -> io::Result<(ReadDir, RawFd)> {
1825         let ptr = unsafe { fdopendir(dir_fd.as_raw_fd()) };
1826         if ptr.is_null() {
1827             return Err(io::Error::last_os_error());
1828         }
1829         let dirp = Dir(ptr);
1830         // file descriptor is automatically closed by libc::closedir() now, so give up ownership
1831         let new_parent_fd = dir_fd.into_raw_fd();
1832         // a valid root is not needed because we do not call any functions involving the full path
1833         // of the DirEntrys.
1834         let dummy_root = PathBuf::new();
1835         Ok((
1836             ReadDir {
1837                 inner: Arc::new(InnerReadDir { dirp, root: dummy_root }),
1838                 #[cfg(not(any(
1839                     target_os = "android",
1840                     target_os = "linux",
1841                     target_os = "solaris",
1842                     target_os = "illumos",
1843                     target_os = "fuchsia",
1844                     target_os = "redox",
1845                 )))]
1846                 end_of_stream: false,
1847             },
1848             new_parent_fd,
1849         ))
1850     }
1851
1852     #[cfg(any(
1853         target_os = "solaris",
1854         target_os = "illumos",
1855         target_os = "haiku",
1856         target_os = "vxworks",
1857     ))]
1858     fn is_dir(_ent: &DirEntry) -> Option<bool> {
1859         None
1860     }
1861
1862     #[cfg(not(any(
1863         target_os = "solaris",
1864         target_os = "illumos",
1865         target_os = "haiku",
1866         target_os = "vxworks",
1867     )))]
1868     fn is_dir(ent: &DirEntry) -> Option<bool> {
1869         match ent.entry.d_type {
1870             libc::DT_UNKNOWN => None,
1871             libc::DT_DIR => Some(true),
1872             _ => Some(false),
1873         }
1874     }
1875
1876     fn remove_dir_all_recursive(parent_fd: Option<RawFd>, path: &CStr) -> io::Result<()> {
1877         // try opening as directory
1878         let fd = match openat_nofollow_dironly(parent_fd, &path) {
1879             Err(err) if matches!(err.raw_os_error(), Some(libc::ENOTDIR | libc::ELOOP)) => {
1880                 // not a directory - don't traverse further
1881                 // (for symlinks, older Linux kernels may return ELOOP instead of ENOTDIR)
1882                 return match parent_fd {
1883                     // unlink...
1884                     Some(parent_fd) => {
1885                         cvt(unsafe { unlinkat(parent_fd, path.as_ptr(), 0) }).map(drop)
1886                     }
1887                     // ...unless this was supposed to be the deletion root directory
1888                     None => Err(err),
1889                 };
1890             }
1891             result => result?,
1892         };
1893
1894         // open the directory passing ownership of the fd
1895         let (dir, fd) = fdreaddir(fd)?;
1896         for child in dir {
1897             let child = child?;
1898             let child_name = child.name_cstr();
1899             match is_dir(&child) {
1900                 Some(true) => {
1901                     remove_dir_all_recursive(Some(fd), child_name)?;
1902                 }
1903                 Some(false) => {
1904                     cvt(unsafe { unlinkat(fd, child_name.as_ptr(), 0) })?;
1905                 }
1906                 None => {
1907                     // POSIX specifies that calling unlink()/unlinkat(..., 0) on a directory can succeed
1908                     // if the process has the appropriate privileges. This however can causing orphaned
1909                     // directories requiring an fsck e.g. on Solaris and Illumos. So we try recursing
1910                     // into it first instead of trying to unlink() it.
1911                     remove_dir_all_recursive(Some(fd), child_name)?;
1912                 }
1913             }
1914         }
1915
1916         // unlink the directory after removing its contents
1917         cvt(unsafe {
1918             unlinkat(parent_fd.unwrap_or(libc::AT_FDCWD), path.as_ptr(), libc::AT_REMOVEDIR)
1919         })?;
1920         Ok(())
1921     }
1922
1923     fn remove_dir_all_modern(p: &Path) -> io::Result<()> {
1924         // We cannot just call remove_dir_all_recursive() here because that would not delete a passed
1925         // symlink. No need to worry about races, because remove_dir_all_recursive() does not recurse
1926         // into symlinks.
1927         let attr = lstat(p)?;
1928         if attr.file_type().is_symlink() {
1929             crate::fs::remove_file(p)
1930         } else {
1931             run_path_with_cstr(p, |p| remove_dir_all_recursive(None, &p))
1932         }
1933     }
1934
1935     #[cfg(not(all(target_os = "macos", not(target_arch = "aarch64"))))]
1936     pub fn remove_dir_all(p: &Path) -> io::Result<()> {
1937         remove_dir_all_modern(p)
1938     }
1939
1940     #[cfg(all(target_os = "macos", not(target_arch = "aarch64")))]
1941     pub fn remove_dir_all(p: &Path) -> io::Result<()> {
1942         if macos_weak::has_openat() {
1943             // openat() is available with macOS 10.10+, just like unlinkat() and fdopendir()
1944             remove_dir_all_modern(p)
1945         } else {
1946             // fall back to classic implementation
1947             crate::sys_common::fs::remove_dir_all(p)
1948         }
1949     }
1950 }