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