1 // miri has some special hacks here that make things unused.
2 #![cfg_attr(miri, allow(unused))]
4 use crate::os
::unix
::prelude
::*;
6 use crate::ffi
::{CStr, OsStr, OsString}
;
8 use crate::io
::{self, BorrowedCursor, Error, IoSlice, IoSliceMut, SeekFrom}
;
11 target_os
= "android",
13 target_os
= "solaris",
14 target_os
= "fuchsia",
16 target_os
= "illumos",
19 use crate::mem
::MaybeUninit
;
20 use crate::os
::unix
::io
::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd}
;
21 use crate::path
::{Path, PathBuf}
;
24 use crate::sys
::common
::small_c_string
::run_path_with_cstr
;
25 use crate::sys
::fd
::FileDesc
;
26 use crate::sys
::time
::SystemTime
;
27 use crate::sys
::{cvt, cvt_r}
;
28 use crate::sys_common
::{AsInner, AsInnerMut, FromInner, IntoInner}
;
31 all(target_os
= "linux", target_env
= "gnu"),
34 target_os
= "watchos",
36 use crate::sys
::weak
::syscall
;
37 #[cfg(any(target_os = "android", target_os = "macos", target_os = "solaris"))]
38 use crate::sys
::weak
::weak
;
40 use libc
::{c_int, mode_t}
;
45 target_os
= "watchos",
46 target_os
= "solaris",
47 all(target_os
= "linux", target_env
= "gnu")
50 #[cfg(any(target_os = "linux", target_os = "emscripten", target_os = "android"))]
52 #[cfg(any(target_os = "linux", target_os = "emscripten"))]
55 target_os
= "android",
56 target_os
= "solaris",
57 target_os
= "fuchsia",
59 target_os
= "illumos",
62 use libc
::readdir
as readdir64
;
63 #[cfg(target_os = "linux")]
65 #[cfg(any(target_os = "emscripten", target_os = "l4re"))]
66 use libc
::readdir64_r
;
68 target_os
= "android",
70 target_os
= "emscripten",
71 target_os
= "solaris",
72 target_os
= "illumos",
74 target_os
= "fuchsia",
78 use libc
::readdir_r
as readdir64_r
;
79 #[cfg(target_os = "android")]
81 dirent
as dirent64
, fstat
as fstat64
, fstatat
as fstatat64
, ftruncate64
, lseek64
,
82 lstat
as lstat64
, off64_t
, open
as open64
, stat
as stat64
,
86 target_os
= "emscripten",
91 dirent
as dirent64
, fstat
as fstat64
, ftruncate
as ftruncate64
, lseek
as lseek64
,
92 lstat
as lstat64
, off_t
as off64_t
, open
as open64
, stat
as stat64
,
94 #[cfg(any(target_os = "linux", target_os = "emscripten", target_os = "l4re"))]
95 use libc
::{dirent64, fstat64, ftruncate64, lseek64, lstat64, off64_t, open64, stat64}
;
97 pub use crate::sys_common
::fs
::try_exists
;
99 pub struct File(FileDesc
);
101 // FIXME: This should be available on Linux with all `target_env`.
102 // But currently only glibc exposes `statx` fn and structs.
103 // We don't want to import unverified raw C structs here directly.
104 // https://github.com/rust-lang/rust/pull/67774
105 macro_rules
! cfg_has_statx
{
106 ({ $($then_tt:tt)* }
else { $($else_tt:tt)* }
) => {
108 if #[cfg(all(target_os = "linux", target_env = "gnu"))] {
115 ($
($block_inner
:tt
)*) => {
116 #[cfg(all(target_os = "linux", target_env = "gnu"))]
125 pub struct FileAttr
{
127 statx_extra_fields
: Option
<StatxExtraFields
>,
131 struct StatxExtraFields
{
132 // This is needed to check if btime is supported by the filesystem.
134 stx_btime
: libc
::statx_timestamp
,
135 // With statx, we can overcome 32-bit `time_t` too.
136 #[cfg(target_pointer_width = "32")]
137 stx_atime
: libc
::statx_timestamp
,
138 #[cfg(target_pointer_width = "32")]
139 stx_ctime
: libc
::statx_timestamp
,
140 #[cfg(target_pointer_width = "32")]
141 stx_mtime
: libc
::statx_timestamp
,
145 // We prefer `statx` on Linux if available, which contains file creation time,
146 // as well as 64-bit timestamps of all kinds.
147 // Default `stat64` contains no creation time and may have 32-bit `time_t`.
153 ) -> Option
<io
::Result
<FileAttr
>> {
154 use crate::sync
::atomic
::{AtomicU8, Ordering}
;
156 // Linux kernel prior to 4.11 or glibc prior to glibc 2.28 don't support `statx`.
157 // We check for it on first failure and remember availability to avoid having to
160 enum STATX_STATE{ Unknown = 0, Present, Unavailable }
161 static STATX_SAVED_STATE
: AtomicU8
= AtomicU8
::new(STATX_STATE
::Unknown
as u8);
166 pathname
: *const c_char
,
169 statxbuf
: *mut libc
::statx
173 if STATX_SAVED_STATE
.load(Ordering
::Relaxed
) == STATX_STATE
::Unavailable
as u8 {
177 let mut buf
: libc
::statx
= mem
::zeroed();
178 if let Err(err
) = cvt(statx(fd
, path
, flags
, mask
, &mut buf
)) {
179 if STATX_SAVED_STATE
.load(Ordering
::Relaxed
) == STATX_STATE
::Present
as u8 {
180 return Some(Err(err
));
183 // Availability not checked yet.
185 // First try the cheap way.
186 if err
.raw_os_error() == Some(libc
::ENOSYS
) {
187 STATX_SAVED_STATE
.store(STATX_STATE
::Unavailable
as u8, Ordering
::Relaxed
);
191 // Error other than `ENOSYS` is not a good enough indicator -- it is
192 // known that `EPERM` can be returned as a result of using seccomp to
193 // block the syscall.
194 // Availability is checked by performing a call which expects `EFAULT`
195 // if the syscall is usable.
196 // See: https://github.com/rust-lang/rust/issues/65662
197 // FIXME this can probably just do the call if `EPERM` was received, but
198 // previous iteration of the code checked it for all errors and for now
200 // FIXME what about transient conditions like `ENOMEM`?
201 let err2
= cvt(statx(0, ptr
::null(), 0, libc
::STATX_ALL
, ptr
::null_mut()))
203 .and_then(|e
| e
.raw_os_error());
204 if err2
== Some(libc
::EFAULT
) {
205 STATX_SAVED_STATE
.store(STATX_STATE
::Present
as u8, Ordering
::Relaxed
);
206 return Some(Err(err
));
208 STATX_SAVED_STATE
.store(STATX_STATE
::Unavailable
as u8, Ordering
::Relaxed
);
213 // We cannot fill `stat64` exhaustively because of private padding fields.
214 let mut stat
: stat64
= mem
::zeroed();
215 // `c_ulong` on gnu-mips, `dev_t` otherwise
216 stat
.st_dev
= libc
::makedev(buf
.stx_dev_major
, buf
.stx_dev_minor
) as _
;
217 stat
.st_ino
= buf
.stx_ino
as libc
::ino64_t
;
218 stat
.st_nlink
= buf
.stx_nlink
as libc
::nlink_t
;
219 stat
.st_mode
= buf
.stx_mode
as libc
::mode_t
;
220 stat
.st_uid
= buf
.stx_uid
as libc
::uid_t
;
221 stat
.st_gid
= buf
.stx_gid
as libc
::gid_t
;
222 stat
.st_rdev
= libc
::makedev(buf
.stx_rdev_major
, buf
.stx_rdev_minor
) as _
;
223 stat
.st_size
= buf
.stx_size
as off64_t
;
224 stat
.st_blksize
= buf
.stx_blksize
as libc
::blksize_t
;
225 stat
.st_blocks
= buf
.stx_blocks
as libc
::blkcnt64_t
;
226 stat
.st_atime
= buf
.stx_atime
.tv_sec
as libc
::time_t
;
227 // `i64` on gnu-x86_64-x32, `c_ulong` otherwise.
228 stat
.st_atime_nsec
= buf
.stx_atime
.tv_nsec
as _
;
229 stat
.st_mtime
= buf
.stx_mtime
.tv_sec
as libc
::time_t
;
230 stat
.st_mtime_nsec
= buf
.stx_mtime
.tv_nsec
as _
;
231 stat
.st_ctime
= buf
.stx_ctime
.tv_sec
as libc
::time_t
;
232 stat
.st_ctime_nsec
= buf
.stx_ctime
.tv_nsec
as _
;
234 let extra
= StatxExtraFields
{
235 stx_mask
: buf
.stx_mask
,
236 stx_btime
: buf
.stx_btime
,
237 // Store full times to avoid 32-bit `time_t` truncation.
238 #[cfg(target_pointer_width = "32")]
239 stx_atime
: buf
.stx_atime
,
240 #[cfg(target_pointer_width = "32")]
241 stx_ctime
: buf
.stx_ctime
,
242 #[cfg(target_pointer_width = "32")]
243 stx_mtime
: buf
.stx_mtime
,
246 Some(Ok(FileAttr { stat, statx_extra_fields: Some(extra) }
))
251 pub struct FileAttr
{
256 // all DirEntry's will have a reference to this struct
257 struct InnerReadDir
{
263 inner
: Arc
<InnerReadDir
>,
268 fn new(inner
: InnerReadDir
) -> Self {
269 Self { inner: Arc::new(inner), end_of_stream: false }
273 struct Dir(*mut libc
::DIR
);
275 unsafe impl Send
for Dir {}
276 unsafe impl Sync
for Dir {}
279 target_os
= "android",
281 target_os
= "solaris",
282 target_os
= "illumos",
283 target_os
= "fuchsia",
287 pub struct DirEntry
{
288 dir
: Arc
<InnerReadDir
>,
290 // We need to store an owned copy of the entry name on platforms that use
291 // readdir() (not readdir_r()), because a) struct dirent may use a flexible
292 // array to store the name, b) it lives only until the next readdir() call.
293 name
: crate::ffi
::CString
,
296 // Define a minimal subset of fields we need from `dirent64`, especially since
297 // we're not using the immediate `d_name` on these targets. Keeping this as an
298 // `entry` field in `DirEntry` helps reduce the `cfg` boilerplate elsewhere.
300 target_os
= "android",
302 target_os
= "solaris",
303 target_os
= "illumos",
304 target_os
= "fuchsia",
308 struct dirent64_min
{
310 #[cfg(not(any(target_os = "solaris", target_os = "illumos", target_os = "nto")))]
315 target_os
= "android",
317 target_os
= "solaris",
318 target_os
= "illumos",
319 target_os
= "fuchsia",
323 pub struct DirEntry
{
324 dir
: Arc
<InnerReadDir
>,
325 // The full entry includes a fixed-length `d_name`.
329 #[derive(Clone, Debug)]
330 pub struct OpenOptions
{
343 #[derive(Clone, PartialEq, Eq, Debug)]
344 pub struct FilePermissions
{
348 #[derive(Copy, Clone, Debug, Default)]
349 pub struct FileTimes
{
350 accessed
: Option
<SystemTime
>,
351 modified
: Option
<SystemTime
>,
354 #[derive(Copy, Clone, Eq, Debug)]
355 pub struct FileType
{
359 impl PartialEq
for FileType
{
360 fn eq(&self, other
: &Self) -> bool
{
361 self.masked() == other
.masked()
365 impl core
::hash
::Hash
for FileType
{
366 fn hash
<H
: core
::hash
::Hasher
>(&self, state
: &mut H
) {
367 self.masked().hash(state
);
372 pub struct DirBuilder
{
378 fn from_stat64(stat
: stat64
) -> Self {
379 Self { stat, statx_extra_fields: None }
382 #[cfg(target_pointer_width = "32")]
383 pub fn stx_mtime(&self) -> Option
<&libc
::statx_timestamp
> {
384 if let Some(ext
) = &self.statx_extra_fields
{
385 if (ext
.stx_mask
& libc
::STATX_MTIME
) != 0 {
386 return Some(&ext
.stx_mtime
);
392 #[cfg(target_pointer_width = "32")]
393 pub fn stx_atime(&self) -> Option
<&libc
::statx_timestamp
> {
394 if let Some(ext
) = &self.statx_extra_fields
{
395 if (ext
.stx_mask
& libc
::STATX_ATIME
) != 0 {
396 return Some(&ext
.stx_atime
);
402 #[cfg(target_pointer_width = "32")]
403 pub fn stx_ctime(&self) -> Option
<&libc
::statx_timestamp
> {
404 if let Some(ext
) = &self.statx_extra_fields
{
405 if (ext
.stx_mask
& libc
::STATX_CTIME
) != 0 {
406 return Some(&ext
.stx_ctime
);
414 fn from_stat64(stat
: stat64
) -> Self {
421 pub fn size(&self) -> u64 {
422 self.stat
.st_size
as u64
424 pub fn perm(&self) -> FilePermissions
{
425 FilePermissions { mode: (self.stat.st_mode as mode_t) }
428 pub fn file_type(&self) -> FileType
{
429 FileType { mode: self.stat.st_mode as mode_t }
433 #[cfg(target_os = "netbsd")]
435 pub fn modified(&self) -> io
::Result
<SystemTime
> {
436 Ok(SystemTime
::new(self.stat
.st_mtime
as i64, self.stat
.st_mtimensec
as i64))
439 pub fn accessed(&self) -> io
::Result
<SystemTime
> {
440 Ok(SystemTime
::new(self.stat
.st_atime
as i64, self.stat
.st_atimensec
as i64))
443 pub fn created(&self) -> io
::Result
<SystemTime
> {
444 Ok(SystemTime
::new(self.stat
.st_birthtime
as i64, self.stat
.st_birthtimensec
as i64))
448 #[cfg(not(any(target_os = "netbsd", target_os = "nto")))]
450 #[cfg(not(any(target_os = "vxworks", target_os = "espidf", target_os = "horizon")))]
451 pub fn modified(&self) -> io
::Result
<SystemTime
> {
452 #[cfg(target_pointer_width = "32")]
454 if let Some(mtime
) = self.stx_mtime() {
455 return Ok(SystemTime
::new(mtime
.tv_sec
, mtime
.tv_nsec
as i64));
459 Ok(SystemTime
::new(self.stat
.st_mtime
as i64, self.stat
.st_mtime_nsec
as i64))
462 #[cfg(any(target_os = "vxworks", target_os = "espidf"))]
463 pub fn modified(&self) -> io
::Result
<SystemTime
> {
464 Ok(SystemTime
::new(self.stat
.st_mtime
as i64, 0))
467 #[cfg(target_os = "horizon")]
468 pub fn modified(&self) -> io
::Result
<SystemTime
> {
469 Ok(SystemTime
::from(self.stat
.st_mtim
))
472 #[cfg(not(any(target_os = "vxworks", target_os = "espidf", target_os = "horizon")))]
473 pub fn accessed(&self) -> io
::Result
<SystemTime
> {
474 #[cfg(target_pointer_width = "32")]
476 if let Some(atime
) = self.stx_atime() {
477 return Ok(SystemTime
::new(atime
.tv_sec
, atime
.tv_nsec
as i64));
481 Ok(SystemTime
::new(self.stat
.st_atime
as i64, self.stat
.st_atime_nsec
as i64))
484 #[cfg(any(target_os = "vxworks", target_os = "espidf"))]
485 pub fn accessed(&self) -> io
::Result
<SystemTime
> {
486 Ok(SystemTime
::new(self.stat
.st_atime
as i64, 0))
489 #[cfg(target_os = "horizon")]
490 pub fn accessed(&self) -> io
::Result
<SystemTime
> {
491 Ok(SystemTime
::from(self.stat
.st_atim
))
495 target_os
= "freebsd",
496 target_os
= "openbsd",
499 target_os
= "watchos",
501 pub fn created(&self) -> io
::Result
<SystemTime
> {
502 Ok(SystemTime
::new(self.stat
.st_birthtime
as i64, self.stat
.st_birthtime_nsec
as i64))
506 target_os
= "freebsd",
507 target_os
= "openbsd",
510 target_os
= "watchos",
512 pub fn created(&self) -> io
::Result
<SystemTime
> {
514 if let Some(ext
) = &self.statx_extra_fields
{
515 return if (ext
.stx_mask
& libc
::STATX_BTIME
) != 0 {
516 Ok(SystemTime
::new(ext
.stx_btime
.tv_sec
, ext
.stx_btime
.tv_nsec
as i64))
518 Err(io
::const_io_error
!(
519 io
::ErrorKind
::Uncategorized
,
520 "creation time is not available for the filesystem",
526 Err(io
::const_io_error
!(
527 io
::ErrorKind
::Unsupported
,
528 "creation time is not available on this platform \
534 #[cfg(target_os = "nto")]
536 pub fn modified(&self) -> io
::Result
<SystemTime
> {
537 Ok(SystemTime
::new(self.stat
.st_mtim
.tv_sec
, self.stat
.st_mtim
.tv_nsec
))
540 pub fn accessed(&self) -> io
::Result
<SystemTime
> {
541 Ok(SystemTime
::new(self.stat
.st_atim
.tv_sec
, self.stat
.st_atim
.tv_nsec
))
544 pub fn created(&self) -> io
::Result
<SystemTime
> {
545 Ok(SystemTime
::new(self.stat
.st_ctim
.tv_sec
, self.stat
.st_ctim
.tv_nsec
))
549 impl AsInner
<stat64
> for FileAttr
{
550 fn as_inner(&self) -> &stat64
{
555 impl FilePermissions
{
556 pub fn readonly(&self) -> bool
{
557 // check if any class (owner, group, others) has write permission
558 self.mode
& 0o222 == 0
561 pub fn set_readonly(&mut self, readonly
: bool
) {
563 // remove write permission for all classes; equivalent to `chmod a-w <file>`
566 // add write permission for all classes; equivalent to `chmod a+w <file>`
570 pub fn mode(&self) -> u32 {
576 pub fn set_accessed(&mut self, t
: SystemTime
) {
577 self.accessed
= Some(t
);
580 pub fn set_modified(&mut self, t
: SystemTime
) {
581 self.modified
= Some(t
);
586 pub fn is_dir(&self) -> bool
{
587 self.is(libc
::S_IFDIR
)
589 pub fn is_file(&self) -> bool
{
590 self.is(libc
::S_IFREG
)
592 pub fn is_symlink(&self) -> bool
{
593 self.is(libc
::S_IFLNK
)
596 pub fn is(&self, mode
: mode_t
) -> bool
{
597 self.masked() == mode
600 fn masked(&self) -> mode_t
{
601 self.mode
& libc
::S_IFMT
605 impl FromInner
<u32> for FilePermissions
{
606 fn from_inner(mode
: u32) -> FilePermissions
{
607 FilePermissions { mode: mode as mode_t }
611 impl fmt
::Debug
for ReadDir
{
612 fn fmt(&self, f
: &mut fmt
::Formatter
<'_
>) -> fmt
::Result
{
613 // This will only be called from std::fs::ReadDir, which will add a "ReadDir()" frame.
614 // Thus the result will be e g 'ReadDir("/home")'
615 fmt
::Debug
::fmt(&*self.inner
.root
, f
)
619 impl Iterator
for ReadDir
{
620 type Item
= io
::Result
<DirEntry
>;
623 target_os
= "android",
625 target_os
= "solaris",
626 target_os
= "fuchsia",
628 target_os
= "illumos",
631 fn next(&mut self) -> Option
<io
::Result
<DirEntry
>> {
632 if self.end_of_stream
{
638 // As of POSIX.1-2017, readdir() is not required to be thread safe; only
639 // readdir_r() is. However, readdir_r() cannot correctly handle platforms
640 // with unlimited or variable NAME_MAX. Many modern platforms guarantee
641 // thread safety for readdir() as long an individual DIR* is not accessed
642 // concurrently, which is sufficient for Rust.
643 super::os
::set_errno(0);
644 let entry_ptr
= readdir64(self.inner
.dirp
.0);
645 if entry_ptr
.is_null() {
646 // We either encountered an error, or reached the end. Either way,
647 // the next call to next() should return None.
648 self.end_of_stream
= true;
650 // To distinguish between errors and end-of-directory, we had to clear
651 // errno beforehand to check for an error now.
652 return match super::os
::errno() {
654 e
=> Some(Err(Error
::from_raw_os_error(e
))),
658 // The dirent64 struct is a weird imaginary thing that isn't ever supposed
659 // to be worked with by value. Its trailing d_name field is declared
660 // variously as [c_char; 256] or [c_char; 1] on different systems but
661 // either way that size is meaningless; only the offset of d_name is
662 // meaningful. The dirent64 pointers that libc returns from readdir64 are
663 // allowed to point to allocations smaller _or_ LARGER than implied by the
664 // definition of the struct.
666 // As such, we need to be even more careful with dirent64 than if its
667 // contents were "simply" partially initialized data.
669 // Like for uninitialized contents, converting entry_ptr to `&dirent64`
670 // would not be legal. However, unique to dirent64 is that we don't even
671 // get to use `addr_of!((*entry_ptr).d_name)` because that operation
672 // requires the full extent of *entry_ptr to be in bounds of the same
673 // allocation, which is not necessarily the case here.
675 // Absent any other way to obtain a pointer to `(*entry_ptr).d_name`
676 // legally in Rust analogously to how it would be done in C, we instead
677 // need to make our own non-libc allocation that conforms to the weird
678 // imaginary definition of dirent64, and use that for a field offset
680 macro_rules
! offset_ptr
{
681 ($entry_ptr
:expr
, $field
:ident
) => {{
682 const OFFSET
: isize = {
683 let delusion
= MaybeUninit
::<dirent64
>::uninit();
684 let entry_ptr
= delusion
.as_ptr();
686 ptr
::addr_of
!((*entry_ptr
).$field
)
688 .offset_from(entry_ptr
.cast
::<u8>())
692 // Cast to the same type determined by the else branch.
693 $entry_ptr
.byte_offset(OFFSET
).cast
::<_
>()
695 #[allow(deref_nullptr)]
697 ptr
::addr_of
!((*ptr
::null
::<dirent64
>()).$field
)
703 // d_name is guaranteed to be null-terminated.
704 let name
= CStr
::from_ptr(offset_ptr
!(entry_ptr
, d_name
).cast());
705 let name_bytes
= name
.to_bytes();
706 if name_bytes
== b
"." || name_bytes
== b
".." {
710 let entry
= dirent64_min
{
711 d_ino
: *offset_ptr
!(entry_ptr
, d_ino
) as u64,
713 target_os
= "solaris",
714 target_os
= "illumos",
717 d_type
: *offset_ptr
!(entry_ptr
, d_type
) as u8,
720 return Some(Ok(DirEntry
{
722 name
: name
.to_owned(),
723 dir
: Arc
::clone(&self.inner
),
730 target_os
= "android",
732 target_os
= "solaris",
733 target_os
= "fuchsia",
735 target_os
= "illumos",
738 fn next(&mut self) -> Option
<io
::Result
<DirEntry
>> {
739 if self.end_of_stream
{
744 let mut ret
= DirEntry { entry: mem::zeroed(), dir: Arc::clone(&self.inner) }
;
745 let mut entry_ptr
= ptr
::null_mut();
747 let err
= readdir64_r(self.inner
.dirp
.0, &mut ret
.entry
, &mut entry_ptr
);
749 if entry_ptr
.is_null() {
750 // We encountered an error (which will be returned in this iteration), but
751 // we also reached the end of the directory stream. The `end_of_stream`
752 // flag is enabled to make sure that we return `None` in the next iteration
753 // (instead of looping forever)
754 self.end_of_stream
= true;
756 return Some(Err(Error
::from_raw_os_error(err
)));
758 if entry_ptr
.is_null() {
761 if ret
.name_bytes() != b
"." && ret
.name_bytes() != b
".." {
762 return Some(Ok(ret
));
771 let r
= unsafe { libc::closedir(self.0) }
;
773 r
== 0 || crate::io
::Error
::last_os_error().kind() == crate::io
::ErrorKind
::Interrupted
,
774 "unexpected error during closedir: {:?}",
775 crate::io
::Error
::last_os_error()
781 pub fn path(&self) -> PathBuf
{
782 self.dir
.root
.join(self.file_name_os_str())
785 pub fn file_name(&self) -> OsString
{
786 self.file_name_os_str().to_os_string()
790 any(target_os
= "linux", target_os
= "emscripten", target_os
= "android"),
793 pub fn metadata(&self) -> io
::Result
<FileAttr
> {
794 let fd
= cvt(unsafe { dirfd(self.dir.dirp.0) }
)?
;
795 let name
= self.name_cstr().as_ptr();
798 if let Some(ret
) = unsafe { try_statx(
801 libc
::AT_SYMLINK_NOFOLLOW
| libc
::AT_STATX_SYNC_AS_STAT
,
808 let mut stat
: stat64
= unsafe { mem::zeroed() }
;
809 cvt(unsafe { fstatat64(fd, name, &mut stat, libc::AT_SYMLINK_NOFOLLOW) }
)?
;
810 Ok(FileAttr
::from_stat64(stat
))
814 not(any(target_os
= "linux", target_os
= "emscripten", target_os
= "android")),
817 pub fn metadata(&self) -> io
::Result
<FileAttr
> {
822 target_os
= "solaris",
823 target_os
= "illumos",
825 target_os
= "vxworks",
828 pub fn file_type(&self) -> io
::Result
<FileType
> {
829 self.metadata().map(|m
| m
.file_type())
833 target_os
= "solaris",
834 target_os
= "illumos",
836 target_os
= "vxworks",
839 pub fn file_type(&self) -> io
::Result
<FileType
> {
840 match self.entry
.d_type
{
841 libc
::DT_CHR
=> Ok(FileType { mode: libc::S_IFCHR }
),
842 libc
::DT_FIFO
=> Ok(FileType { mode: libc::S_IFIFO }
),
843 libc
::DT_LNK
=> Ok(FileType { mode: libc::S_IFLNK }
),
844 libc
::DT_REG
=> Ok(FileType { mode: libc::S_IFREG }
),
845 libc
::DT_SOCK
=> Ok(FileType { mode: libc::S_IFSOCK }
),
846 libc
::DT_DIR
=> Ok(FileType { mode: libc::S_IFDIR }
),
847 libc
::DT_BLK
=> Ok(FileType { mode: libc::S_IFBLK }
),
848 _
=> self.metadata().map(|m
| m
.file_type()),
855 target_os
= "watchos",
857 target_os
= "emscripten",
858 target_os
= "android",
859 target_os
= "solaris",
860 target_os
= "illumos",
863 target_os
= "fuchsia",
865 target_os
= "vxworks",
866 target_os
= "espidf",
867 target_os
= "horizon",
870 pub fn ino(&self) -> u64 {
871 self.entry
.d_ino
as u64
875 target_os
= "freebsd",
876 target_os
= "openbsd",
877 target_os
= "netbsd",
878 target_os
= "dragonfly"
880 pub fn ino(&self) -> u64 {
881 self.entry
.d_fileno
as u64
887 target_os
= "watchos",
888 target_os
= "netbsd",
889 target_os
= "openbsd",
890 target_os
= "freebsd",
891 target_os
= "dragonfly"
893 fn name_bytes(&self) -> &[u8] {
896 slice
::from_raw_parts(
897 self.entry
.d_name
.as_ptr() as *const u8,
898 self.entry
.d_namlen
as usize,
905 target_os
= "watchos",
906 target_os
= "netbsd",
907 target_os
= "openbsd",
908 target_os
= "freebsd",
909 target_os
= "dragonfly"
911 fn name_bytes(&self) -> &[u8] {
912 self.name_cstr().to_bytes()
916 target_os
= "android",
918 target_os
= "solaris",
919 target_os
= "illumos",
920 target_os
= "fuchsia",
924 fn name_cstr(&self) -> &CStr
{
925 unsafe { CStr::from_ptr(self.entry.d_name.as_ptr()) }
928 target_os
= "android",
930 target_os
= "solaris",
931 target_os
= "illumos",
932 target_os
= "fuchsia",
936 fn name_cstr(&self) -> &CStr
{
940 pub fn file_name_os_str(&self) -> &OsStr
{
941 OsStr
::from_bytes(self.name_bytes())
946 pub fn new() -> OpenOptions
{
961 pub fn read(&mut self, read
: bool
) {
964 pub fn write(&mut self, write
: bool
) {
967 pub fn append(&mut self, append
: bool
) {
968 self.append
= append
;
970 pub fn truncate(&mut self, truncate
: bool
) {
971 self.truncate
= truncate
;
973 pub fn create(&mut self, create
: bool
) {
974 self.create
= create
;
976 pub fn create_new(&mut self, create_new
: bool
) {
977 self.create_new
= create_new
;
980 pub fn custom_flags(&mut self, flags
: i32) {
981 self.custom_flags
= flags
;
983 pub fn mode(&mut self, mode
: u32) {
984 self.mode
= mode
as mode_t
;
987 fn get_access_mode(&self) -> io
::Result
<c_int
> {
988 match (self.read
, self.write
, self.append
) {
989 (true, false, false) => Ok(libc
::O_RDONLY
),
990 (false, true, false) => Ok(libc
::O_WRONLY
),
991 (true, true, false) => Ok(libc
::O_RDWR
),
992 (false, _
, true) => Ok(libc
::O_WRONLY
| libc
::O_APPEND
),
993 (true, _
, true) => Ok(libc
::O_RDWR
| libc
::O_APPEND
),
994 (false, false, false) => Err(Error
::from_raw_os_error(libc
::EINVAL
)),
998 fn get_creation_mode(&self) -> io
::Result
<c_int
> {
999 match (self.write
, self.append
) {
1002 if self.truncate
|| self.create
|| self.create_new
{
1003 return Err(Error
::from_raw_os_error(libc
::EINVAL
));
1007 if self.truncate
&& !self.create_new
{
1008 return Err(Error
::from_raw_os_error(libc
::EINVAL
));
1013 Ok(match (self.create
, self.truncate
, self.create_new
) {
1014 (false, false, false) => 0,
1015 (true, false, false) => libc
::O_CREAT
,
1016 (false, true, false) => libc
::O_TRUNC
,
1017 (true, true, false) => libc
::O_CREAT
| libc
::O_TRUNC
,
1018 (_
, _
, true) => libc
::O_CREAT
| libc
::O_EXCL
,
1024 pub fn open(path
: &Path
, opts
: &OpenOptions
) -> io
::Result
<File
> {
1025 run_path_with_cstr(path
, |path
| File
::open_c(path
, opts
))
1028 pub fn open_c(path
: &CStr
, opts
: &OpenOptions
) -> io
::Result
<File
> {
1029 let flags
= libc
::O_CLOEXEC
1030 | opts
.get_access_mode()?
1031 | opts
.get_creation_mode()?
1032 | (opts
.custom_flags
as c_int
& !libc
::O_ACCMODE
);
1033 // The third argument of `open64` is documented to have type `mode_t`. On
1034 // some platforms (like macOS, where `open64` is actually `open`), `mode_t` is `u16`.
1035 // However, since this is a variadic function, C integer promotion rules mean that on
1036 // the ABI level, this still gets passed as `c_int` (aka `u32` on Unix platforms).
1037 let fd
= cvt_r(|| unsafe { open64(path.as_ptr(), flags, opts.mode as c_int) }
)?
;
1038 Ok(File(unsafe { FileDesc::from_raw_fd(fd) }
))
1041 pub fn file_attr(&self) -> io
::Result
<FileAttr
> {
1042 let fd
= self.as_raw_fd();
1045 if let Some(ret
) = unsafe { try_statx(
1047 b
"\0" as *const _
as *const c_char
,
1048 libc
::AT_EMPTY_PATH
| libc
::AT_STATX_SYNC_AS_STAT
,
1055 let mut stat
: stat64
= unsafe { mem::zeroed() }
;
1056 cvt(unsafe { fstat64(fd, &mut stat) }
)?
;
1057 Ok(FileAttr
::from_stat64(stat
))
1060 pub fn fsync(&self) -> io
::Result
<()> {
1061 cvt_r(|| unsafe { os_fsync(self.as_raw_fd()) }
)?
;
1064 #[cfg(any(target_os = "macos", target_os = "ios", target_os = "watchos"))]
1065 unsafe fn os_fsync(fd
: c_int
) -> c_int
{
1066 libc
::fcntl(fd
, libc
::F_FULLFSYNC
)
1068 #[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "watchos")))]
1069 unsafe fn os_fsync(fd
: c_int
) -> c_int
{
1074 pub fn datasync(&self) -> io
::Result
<()> {
1075 cvt_r(|| unsafe { os_datasync(self.as_raw_fd()) }
)?
;
1078 #[cfg(any(target_os = "macos", target_os = "ios", target_os = "watchos"))]
1079 unsafe fn os_datasync(fd
: c_int
) -> c_int
{
1080 libc
::fcntl(fd
, libc
::F_FULLFSYNC
)
1083 target_os
= "freebsd",
1084 target_os
= "linux",
1085 target_os
= "android",
1086 target_os
= "netbsd",
1087 target_os
= "openbsd",
1090 unsafe fn os_datasync(fd
: c_int
) -> c_int
{
1094 target_os
= "android",
1095 target_os
= "freebsd",
1097 target_os
= "linux",
1098 target_os
= "macos",
1099 target_os
= "netbsd",
1100 target_os
= "openbsd",
1101 target_os
= "watchos",
1104 unsafe fn os_datasync(fd
: c_int
) -> c_int
{
1109 pub fn truncate(&self, size
: u64) -> io
::Result
<()> {
1111 size
.try_into().map_err(|e
| io
::Error
::new(io
::ErrorKind
::InvalidInput
, e
))?
;
1112 cvt_r(|| unsafe { ftruncate64(self.as_raw_fd(), size) }
).map(drop
)
1115 pub fn read(&self, buf
: &mut [u8]) -> io
::Result
<usize> {
1119 pub fn read_vectored(&self, bufs
: &mut [IoSliceMut
<'_
>]) -> io
::Result
<usize> {
1120 self.0.read_vectored(bufs
)
1124 pub fn is_read_vectored(&self) -> bool
{
1125 self.0.is_read_vectored
()
1128 pub fn read_at(&self, buf
: &mut [u8], offset
: u64) -> io
::Result
<usize> {
1129 self.0.read_at(buf
, offset
)
1132 pub fn read_buf(&self, cursor
: BorrowedCursor
<'_
>) -> io
::Result
<()> {
1133 self.0.read_buf(cursor
)
1136 pub fn read_vectored_at(&self, bufs
: &mut [IoSliceMut
<'_
>], offset
: u64) -> io
::Result
<usize> {
1137 self.0.read_vectored_at(bufs
, offset
)
1140 pub fn write(&self, buf
: &[u8]) -> io
::Result
<usize> {
1144 pub fn write_vectored(&self, bufs
: &[IoSlice
<'_
>]) -> io
::Result
<usize> {
1145 self.0.write_vectored(bufs
)
1149 pub fn is_write_vectored(&self) -> bool
{
1150 self.0.is_write_vectored
()
1153 pub fn write_at(&self, buf
: &[u8], offset
: u64) -> io
::Result
<usize> {
1154 self.0.write_at(buf
, offset
)
1157 pub fn write_vectored_at(&self, bufs
: &[IoSlice
<'_
>], offset
: u64) -> io
::Result
<usize> {
1158 self.0.write_vectored_at(bufs
, offset
)
1161 pub fn flush(&self) -> io
::Result
<()> {
1165 pub fn seek(&self, pos
: SeekFrom
) -> io
::Result
<u64> {
1166 let (whence
, pos
) = match pos
{
1167 // Casting to `i64` is fine, too large values will end up as
1168 // negative which will cause an error in `lseek64`.
1169 SeekFrom
::Start(off
) => (libc
::SEEK_SET
, off
as i64),
1170 SeekFrom
::End(off
) => (libc
::SEEK_END
, off
),
1171 SeekFrom
::Current(off
) => (libc
::SEEK_CUR
, off
),
1173 let n
= cvt(unsafe { lseek64(self.as_raw_fd(), pos as off64_t, whence) }
)?
;
1177 pub fn duplicate(&self) -> io
::Result
<File
> {
1178 self.0.duplicate().map(File
)
1181 pub fn set_permissions(&self, perm
: FilePermissions
) -> io
::Result
<()> {
1182 cvt_r(|| unsafe { libc::fchmod(self.as_raw_fd(), perm.mode) }
)?
;
1186 pub fn set_times(&self, times
: FileTimes
) -> io
::Result
<()> {
1187 #[cfg(not(any(target_os = "redox", target_os = "espidf", target_os = "horizon")))]
1188 let to_timespec
= |time
: Option
<SystemTime
>| {
1190 Some(time
) if let Some(ts
) = time
.t
.to_timespec() => Ok(ts
),
1191 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")),
1192 Some(_
) => Err(io
::const_io_error
!(io
::ErrorKind
::InvalidInput
, "timestamp is too small to set as a file time")),
1193 None
=> Ok(libc
::timespec { tv_sec: 0, tv_nsec: libc::UTIME_OMIT as _ }
),
1196 #[cfg(not(any(target_os = "redox", target_os = "espidf", target_os = "horizon")))]
1197 let times
= [to_timespec(times
.accessed
)?
, to_timespec(times
.modified
)?
];
1199 if #[cfg(any(target_os = "redox", target_os = "espidf", target_os = "horizon"))] {
1200 // Redox doesn't appear to support `UTIME_OMIT`.
1201 // ESP-IDF and HorizonOS do not support `futimens` at all and the behavior for those OS is therefore
1202 // the same as for Redox.
1204 Err(io
::const_io_error
!(
1205 io
::ErrorKind
::Unsupported
,
1206 "setting file times not supported",
1208 } else if #[cfg(any(target_os = "android", target_os = "macos"))] {
1209 // futimens requires macOS 10.13, and Android API level 19
1211 weak
!(fn futimens(c_int
, *const libc
::timespec
) -> c_int
);
1212 match futimens
.get() {
1213 Some(futimens
) => futimens(self.as_raw_fd(), times
.as_ptr()),
1214 #[cfg(target_os = "macos")]
1216 fn ts_to_tv(ts
: &libc
::timespec
) -> libc
::timeval
{
1219 tv_usec
: (ts
.tv_nsec
/ 1000) as _
1222 let timevals
= [ts_to_tv(×
[0]), ts_to_tv(×
[1])];
1223 libc
::futimes(self.as_raw_fd(), timevals
.as_ptr())
1225 // futimes requires even newer Android.
1226 #[cfg(target_os = "android")]
1227 None
=> return Err(io
::const_io_error
!(
1228 io
::ErrorKind
::Unsupported
,
1229 "setting file times requires Android API level >= 19",
1235 cvt(unsafe { libc::futimens(self.as_raw_fd(), times.as_ptr()) }
)?
;
1243 pub fn new() -> DirBuilder
{
1244 DirBuilder { mode: 0o777 }
1247 pub fn mkdir(&self, p
: &Path
) -> io
::Result
<()> {
1248 run_path_with_cstr(p
, |p
| cvt(unsafe { libc::mkdir(p.as_ptr(), self.mode) }
).map(|_
| ()))
1251 pub fn set_mode(&mut self, mode
: u32) {
1252 self.mode
= mode
as mode_t
;
1256 impl AsInner
<FileDesc
> for File
{
1257 fn as_inner(&self) -> &FileDesc
{
1262 impl AsInnerMut
<FileDesc
> for File
{
1263 fn as_inner_mut(&mut self) -> &mut FileDesc
{
1268 impl IntoInner
<FileDesc
> for File
{
1269 fn into_inner(self) -> FileDesc
{
1274 impl FromInner
<FileDesc
> for File
{
1275 fn from_inner(file_desc
: FileDesc
) -> Self {
1280 impl AsFd
for File
{
1281 fn as_fd(&self) -> BorrowedFd
<'_
> {
1286 impl AsRawFd
for File
{
1287 fn as_raw_fd(&self) -> RawFd
{
1292 impl IntoRawFd
for File
{
1293 fn into_raw_fd(self) -> RawFd
{
1294 self.0.into_raw_fd
()
1298 impl FromRawFd
for File
{
1299 unsafe fn from_raw_fd(raw_fd
: RawFd
) -> Self {
1300 Self(FromRawFd
::from_raw_fd(raw_fd
))
1304 impl fmt
::Debug
for File
{
1305 fn fmt(&self, f
: &mut fmt
::Formatter
<'_
>) -> fmt
::Result
{
1307 target_os
= "linux",
1308 target_os
= "netbsd",
1309 target_os
= "illumos",
1310 target_os
= "solaris"
1312 fn get_path(fd
: c_int
) -> Option
<PathBuf
> {
1313 let mut p
= PathBuf
::from("/proc/self/fd");
1314 p
.push(&fd
.to_string());
1318 #[cfg(target_os = "macos")]
1319 fn get_path(fd
: c_int
) -> Option
<PathBuf
> {
1320 // FIXME: The use of PATH_MAX is generally not encouraged, but it
1321 // is inevitable in this case because macOS defines `fcntl` with
1322 // `F_GETPATH` in terms of `MAXPATHLEN`, and there are no
1323 // alternatives. If a better method is invented, it should be used
1325 let mut buf
= vec
![0; libc
::PATH_MAX
as usize];
1326 let n
= unsafe { libc::fcntl(fd, libc::F_GETPATH, buf.as_ptr()) }
;
1330 let l
= buf
.iter().position(|&c
| c
== 0).unwrap();
1331 buf
.truncate(l
as usize);
1332 buf
.shrink_to_fit();
1333 Some(PathBuf
::from(OsString
::from_vec(buf
)))
1336 #[cfg(all(target_os = "freebsd", target_arch = "x86_64"))]
1337 fn get_path(fd
: c_int
) -> Option
<PathBuf
> {
1338 let info
= Box
::<libc
::kinfo_file
>::new_zeroed();
1339 let mut info
= unsafe { info.assume_init() }
;
1340 info
.kf_structsize
= mem
::size_of
::<libc
::kinfo_file
>() as libc
::c_int
;
1341 let n
= unsafe { libc::fcntl(fd, libc::F_KINFO, &mut *info) }
;
1345 let buf
= unsafe { CStr::from_ptr(info.kf_path.as_mut_ptr()).to_bytes().to_vec() }
;
1346 Some(PathBuf
::from(OsString
::from_vec(buf
)))
1349 #[cfg(target_os = "vxworks")]
1350 fn get_path(fd
: c_int
) -> Option
<PathBuf
> {
1351 let mut buf
= vec
![0; libc
::PATH_MAX
as usize];
1352 let n
= unsafe { libc::ioctl(fd, libc::FIOGETNAME, buf.as_ptr()) }
;
1356 let l
= buf
.iter().position(|&c
| c
== 0).unwrap();
1357 buf
.truncate(l
as usize);
1358 Some(PathBuf
::from(OsString
::from_vec(buf
)))
1362 target_os
= "linux",
1363 target_os
= "macos",
1364 target_os
= "vxworks",
1365 all(target_os
= "freebsd", target_arch
= "x86_64"),
1366 target_os
= "netbsd",
1367 target_os
= "illumos",
1368 target_os
= "solaris"
1370 fn get_path(_fd
: c_int
) -> Option
<PathBuf
> {
1371 // FIXME(#24570): implement this for other Unix platforms
1376 target_os
= "linux",
1377 target_os
= "macos",
1378 target_os
= "freebsd",
1379 target_os
= "netbsd",
1380 target_os
= "openbsd",
1381 target_os
= "vxworks"
1383 fn get_mode(fd
: c_int
) -> Option
<(bool
, bool
)> {
1384 let mode
= unsafe { libc::fcntl(fd, libc::F_GETFL) }
;
1388 match mode
& libc
::O_ACCMODE
{
1389 libc
::O_RDONLY
=> Some((true, false)),
1390 libc
::O_RDWR
=> Some((true, true)),
1391 libc
::O_WRONLY
=> Some((false, true)),
1397 target_os
= "linux",
1398 target_os
= "macos",
1399 target_os
= "freebsd",
1400 target_os
= "netbsd",
1401 target_os
= "openbsd",
1402 target_os
= "vxworks"
1404 fn get_mode(_fd
: c_int
) -> Option
<(bool
, bool
)> {
1405 // FIXME(#24570): implement this for other Unix platforms
1409 let fd
= self.as_raw_fd();
1410 let mut b
= f
.debug_struct("File");
1412 if let Some(path
) = get_path(fd
) {
1413 b
.field("path", &path
);
1415 if let Some((read
, write
)) = get_mode(fd
) {
1416 b
.field("read", &read
).field("write", &write
);
1422 pub fn readdir(path
: &Path
) -> io
::Result
<ReadDir
> {
1423 let ptr
= run_path_with_cstr(path
, |p
| unsafe { Ok(libc::opendir(p.as_ptr())) }
)?
;
1425 Err(Error
::last_os_error())
1427 let root
= path
.to_path_buf();
1428 let inner
= InnerReadDir { dirp: Dir(ptr), root }
;
1429 Ok(ReadDir
::new(inner
))
1433 pub fn unlink(p
: &Path
) -> io
::Result
<()> {
1434 run_path_with_cstr(p
, |p
| cvt(unsafe { libc::unlink(p.as_ptr()) }
).map(|_
| ()))
1437 pub fn rename(old
: &Path
, new
: &Path
) -> io
::Result
<()> {
1438 run_path_with_cstr(old
, |old
| {
1439 run_path_with_cstr(new
, |new
| {
1440 cvt(unsafe { libc::rename(old.as_ptr(), new.as_ptr()) }
).map(|_
| ())
1445 pub fn set_perm(p
: &Path
, perm
: FilePermissions
) -> io
::Result
<()> {
1446 run_path_with_cstr(p
, |p
| cvt_r(|| unsafe { libc::chmod(p.as_ptr(), perm.mode) }
).map(|_
| ()))
1449 pub fn rmdir(p
: &Path
) -> io
::Result
<()> {
1450 run_path_with_cstr(p
, |p
| cvt(unsafe { libc::rmdir(p.as_ptr()) }
).map(|_
| ()))
1453 pub fn readlink(p
: &Path
) -> io
::Result
<PathBuf
> {
1454 run_path_with_cstr(p
, |c_path
| {
1455 let p
= c_path
.as_ptr();
1457 let mut buf
= Vec
::with_capacity(256);
1461 cvt(unsafe { libc::readlink(p, buf.as_mut_ptr() as *mut _, buf.capacity()) }
)?
1465 buf
.set_len(buf_read
);
1468 if buf_read
!= buf
.capacity() {
1469 buf
.shrink_to_fit();
1471 return Ok(PathBuf
::from(OsString
::from_vec(buf
)));
1474 // Trigger the internal buffer resizing logic of `Vec` by requiring
1475 // more space than the current capacity. The length is guaranteed to be
1476 // the same as the capacity due to the if statement above.
1482 pub fn symlink(original
: &Path
, link
: &Path
) -> io
::Result
<()> {
1483 run_path_with_cstr(original
, |original
| {
1484 run_path_with_cstr(link
, |link
| {
1485 cvt(unsafe { libc::symlink(original.as_ptr(), link.as_ptr()) }
).map(|_
| ())
1490 pub fn link(original
: &Path
, link
: &Path
) -> io
::Result
<()> {
1491 run_path_with_cstr(original
, |original
| {
1492 run_path_with_cstr(link
, |link
| {
1494 if #[cfg(any(target_os = "vxworks", target_os = "redox", target_os = "android", target_os = "espidf", target_os = "horizon"))] {
1495 // VxWorks, Redox and ESP-IDF lack `linkat`, so use `link` instead. POSIX leaves
1496 // it implementation-defined whether `link` follows symlinks, so rely on the
1497 // `symlink_hard_link` test in library/std/src/fs/tests.rs to check the behavior.
1498 // Android has `linkat` on newer versions, but we happen to know `link`
1499 // always has the correct behavior, so it's here as well.
1500 cvt(unsafe { libc::link(original.as_ptr(), link.as_ptr()) }
)?
;
1501 } else if #[cfg(any(target_os = "macos", target_os = "solaris"))] {
1502 // MacOS (<=10.9) and Solaris 10 lack support for linkat while newer
1503 // versions have it. We want to use linkat if it is available, so we use weak!
1504 // to check. `linkat` is preferable to `link` because it gives us a flag to
1505 // specify how symlinks should be handled. We pass 0 as the flags argument,
1506 // meaning it shouldn't follow symlinks.
1507 weak
!(fn linkat(c_int
, *const c_char
, c_int
, *const c_char
, c_int
) -> c_int
);
1509 if let Some(f
) = linkat
.get() {
1510 cvt(unsafe { f(libc::AT_FDCWD, original.as_ptr(), libc::AT_FDCWD, link.as_ptr(), 0) }
)?
;
1512 cvt(unsafe { libc::link(original.as_ptr(), link.as_ptr()) }
)?
;
1515 // Where we can, use `linkat` instead of `link`; see the comment above
1516 // this one for details on why.
1517 cvt(unsafe { libc::linkat(libc::AT_FDCWD, original.as_ptr(), libc::AT_FDCWD, link.as_ptr(), 0) }
)?
;
1525 pub fn stat(p
: &Path
) -> io
::Result
<FileAttr
> {
1526 run_path_with_cstr(p
, |p
| {
1528 if let Some(ret
) = unsafe { try_statx(
1531 libc
::AT_STATX_SYNC_AS_STAT
,
1538 let mut stat
: stat64
= unsafe { mem::zeroed() }
;
1539 cvt(unsafe { stat64(p.as_ptr(), &mut stat) }
)?
;
1540 Ok(FileAttr
::from_stat64(stat
))
1544 pub fn lstat(p
: &Path
) -> io
::Result
<FileAttr
> {
1545 run_path_with_cstr(p
, |p
| {
1547 if let Some(ret
) = unsafe { try_statx(
1550 libc
::AT_SYMLINK_NOFOLLOW
| libc
::AT_STATX_SYNC_AS_STAT
,
1557 let mut stat
: stat64
= unsafe { mem::zeroed() }
;
1558 cvt(unsafe { lstat64(p.as_ptr(), &mut stat) }
)?
;
1559 Ok(FileAttr
::from_stat64(stat
))
1563 pub fn canonicalize(p
: &Path
) -> io
::Result
<PathBuf
> {
1564 let r
= run_path_with_cstr(p
, |path
| unsafe {
1565 Ok(libc
::realpath(path
.as_ptr(), ptr
::null_mut()))
1568 return Err(io
::Error
::last_os_error());
1570 Ok(PathBuf
::from(OsString
::from_vec(unsafe {
1571 let buf
= CStr
::from_ptr(r
).to_bytes().to_vec();
1572 libc
::free(r
as *mut _
);
1577 fn open_from(from
: &Path
) -> io
::Result
<(crate::fs
::File
, crate::fs
::Metadata
)> {
1578 use crate::fs
::File
;
1579 use crate::sys_common
::fs
::NOT_FILE_ERROR
;
1581 let reader
= File
::open(from
)?
;
1582 let metadata
= reader
.metadata()?
;
1583 if !metadata
.is_file() {
1584 return Err(NOT_FILE_ERROR
);
1586 Ok((reader
, metadata
))
1589 #[cfg(target_os = "espidf")]
1590 fn open_to_and_set_permissions(
1592 reader_metadata
: crate::fs
::Metadata
,
1593 ) -> io
::Result
<(crate::fs
::File
, crate::fs
::Metadata
)> {
1594 use crate::fs
::OpenOptions
;
1595 let writer
= OpenOptions
::new().open(to
)?
;
1596 let writer_metadata
= writer
.metadata()?
;
1597 Ok((writer
, writer_metadata
))
1600 #[cfg(not(target_os = "espidf"))]
1601 fn open_to_and_set_permissions(
1603 reader_metadata
: crate::fs
::Metadata
,
1604 ) -> io
::Result
<(crate::fs
::File
, crate::fs
::Metadata
)> {
1605 use crate::fs
::OpenOptions
;
1606 use crate::os
::unix
::fs
::{OpenOptionsExt, PermissionsExt}
;
1608 let perm
= reader_metadata
.permissions();
1609 let writer
= OpenOptions
::new()
1610 // create the file with the correct mode right away
1616 let writer_metadata
= writer
.metadata()?
;
1617 if writer_metadata
.is_file() {
1618 // Set the correct file permissions, in case the file already existed.
1619 // Don't set the permissions on already existing non-files like
1620 // pipes/FIFOs or device nodes.
1621 writer
.set_permissions(perm
)?
;
1623 Ok((writer
, writer_metadata
))
1627 target_os
= "linux",
1628 target_os
= "android",
1629 target_os
= "macos",
1631 target_os
= "watchos",
1633 pub fn copy(from
: &Path
, to
: &Path
) -> io
::Result
<u64> {
1634 let (mut reader
, reader_metadata
) = open_from(from
)?
;
1635 let (mut writer
, _
) = open_to_and_set_permissions(to
, reader_metadata
)?
;
1637 io
::copy(&mut reader
, &mut writer
)
1640 #[cfg(any(target_os = "linux", target_os = "android"))]
1641 pub fn copy(from
: &Path
, to
: &Path
) -> io
::Result
<u64> {
1642 let (mut reader
, reader_metadata
) = open_from(from
)?
;
1643 let max_len
= u64::MAX
;
1644 let (mut writer
, _
) = open_to_and_set_permissions(to
, reader_metadata
)?
;
1646 use super::kernel_copy
::{copy_regular_files, CopyResult}
;
1648 match copy_regular_files(reader
.as_raw_fd(), writer
.as_raw_fd(), max_len
) {
1649 CopyResult
::Ended(bytes
) => Ok(bytes
),
1650 CopyResult
::Error(e
, _
) => Err(e
),
1651 CopyResult
::Fallback(written
) => match io
::copy
::generic_copy(&mut reader
, &mut writer
) {
1652 Ok(bytes
) => Ok(bytes
+ written
),
1658 #[cfg(any(target_os = "macos", target_os = "ios", target_os = "watchos"))]
1659 pub fn copy(from
: &Path
, to
: &Path
) -> io
::Result
<u64> {
1660 use crate::sync
::atomic
::{AtomicBool, Ordering}
;
1662 const COPYFILE_ACL
: u32 = 1 << 0;
1663 const COPYFILE_STAT
: u32 = 1 << 1;
1664 const COPYFILE_XATTR
: u32 = 1 << 2;
1665 const COPYFILE_DATA
: u32 = 1 << 3;
1667 const COPYFILE_SECURITY
: u32 = COPYFILE_STAT
| COPYFILE_ACL
;
1668 const COPYFILE_METADATA
: u32 = COPYFILE_SECURITY
| COPYFILE_XATTR
;
1669 const COPYFILE_ALL
: u32 = COPYFILE_METADATA
| COPYFILE_DATA
;
1671 const COPYFILE_STATE_COPIED
: u32 = 8;
1673 #[allow(non_camel_case_types)]
1674 type copyfile_state_t
= *mut libc
::c_void
;
1675 #[allow(non_camel_case_types)]
1676 type copyfile_flags_t
= u32;
1682 state
: copyfile_state_t
,
1683 flags
: copyfile_flags_t
,
1685 fn copyfile_state_alloc() -> copyfile_state_t
;
1686 fn copyfile_state_free(state
: copyfile_state_t
) -> libc
::c_int
;
1687 fn copyfile_state_get(
1688 state
: copyfile_state_t
,
1690 dst
: *mut libc
::c_void
,
1694 struct FreeOnDrop(copyfile_state_t
);
1695 impl Drop
for FreeOnDrop
{
1696 fn drop(&mut self) {
1697 // The code below ensures that `FreeOnDrop` is never a null pointer
1699 // `copyfile_state_free` returns -1 if the `to` or `from` files
1700 // cannot be closed. However, this is not considered this an
1702 copyfile_state_free(self.0);
1707 // MacOS prior to 10.12 don't support `fclonefileat`
1708 // We store the availability in a global to avoid unnecessary syscalls
1709 static HAS_FCLONEFILEAT
: AtomicBool
= AtomicBool
::new(true);
1713 dst_dirfd
: libc
::c_int
,
1719 let (reader
, reader_metadata
) = open_from(from
)?
;
1721 // Opportunistically attempt to create a copy-on-write clone of `from`
1722 // using `fclonefileat`.
1723 if HAS_FCLONEFILEAT
.load(Ordering
::Relaxed
) {
1724 let clonefile_result
= run_path_with_cstr(to
, |to
| {
1725 cvt(unsafe { fclonefileat(reader.as_raw_fd(), libc::AT_FDCWD, to.as_ptr(), 0) }
)
1727 match clonefile_result
{
1728 Ok(_
) => return Ok(reader_metadata
.len()),
1729 Err(err
) => match err
.raw_os_error() {
1730 // `fclonefileat` will fail on non-APFS volumes, if the
1731 // destination already exists, or if the source and destination
1732 // are on different devices. In all these cases `fcopyfile`
1734 Some(libc
::ENOTSUP
) | Some(libc
::EEXIST
) | Some(libc
::EXDEV
) => (),
1735 Some(libc
::ENOSYS
) => HAS_FCLONEFILEAT
.store(false, Ordering
::Relaxed
),
1736 _
=> return Err(err
),
1741 // Fall back to using `fcopyfile` if `fclonefileat` does not succeed.
1742 let (writer
, writer_metadata
) = open_to_and_set_permissions(to
, reader_metadata
)?
;
1744 // We ensure that `FreeOnDrop` never contains a null pointer so it is
1745 // always safe to call `copyfile_state_free`
1746 let state
= unsafe {
1747 let state
= copyfile_state_alloc();
1748 if state
.is_null() {
1749 return Err(crate::io
::Error
::last_os_error());
1754 let flags
= if writer_metadata
.is_file() { COPYFILE_ALL }
else { COPYFILE_DATA }
;
1756 cvt(unsafe { fcopyfile(reader.as_raw_fd(), writer.as_raw_fd(), state.0, flags) }
)?
;
1758 let mut bytes_copied
: libc
::off_t
= 0;
1762 COPYFILE_STATE_COPIED
,
1763 &mut bytes_copied
as *mut libc
::off_t
as *mut libc
::c_void
,
1766 Ok(bytes_copied
as u64)
1769 pub fn chown(path
: &Path
, uid
: u32, gid
: u32) -> io
::Result
<()> {
1770 run_path_with_cstr(path
, |path
| {
1771 cvt(unsafe { libc::chown(path.as_ptr(), uid as libc::uid_t, gid as libc::gid_t) }
)
1776 pub fn fchown(fd
: c_int
, uid
: u32, gid
: u32) -> io
::Result
<()> {
1777 cvt(unsafe { libc::fchown(fd, uid as libc::uid_t, gid as libc::gid_t) }
)?
;
1781 pub fn lchown(path
: &Path
, uid
: u32, gid
: u32) -> io
::Result
<()> {
1782 run_path_with_cstr(path
, |path
| {
1783 cvt(unsafe { libc::lchown(path.as_ptr(), uid as libc::uid_t, gid as libc::gid_t) }
)
1788 #[cfg(not(any(target_os = "fuchsia", target_os = "vxworks")))]
1789 pub fn chroot(dir
: &Path
) -> io
::Result
<()> {
1790 run_path_with_cstr(dir
, |dir
| cvt(unsafe { libc::chroot(dir.as_ptr()) }
).map(|_
| ()))
1793 pub use remove_dir_impl
::remove_dir_all
;
1795 // Fallback for REDOX, ESP-ID, Horizon, and Miri
1797 target_os
= "redox",
1798 target_os
= "espidf",
1799 target_os
= "horizon",
1803 mod remove_dir_impl
{
1804 pub use crate::sys_common
::fs
::remove_dir_all
;
1807 // Modern implementation using openat(), unlinkat() and fdopendir()
1809 target_os
= "redox",
1810 target_os
= "espidf",
1811 target_os
= "horizon",
1815 mod remove_dir_impl
{
1816 use super::{lstat, Dir, DirEntry, InnerReadDir, ReadDir}
;
1817 use crate::ffi
::CStr
;
1819 use crate::os
::unix
::io
::{AsRawFd, FromRawFd, IntoRawFd}
;
1820 use crate::os
::unix
::prelude
::{OwnedFd, RawFd}
;
1821 use crate::path
::{Path, PathBuf}
;
1822 use crate::sys
::common
::small_c_string
::run_path_with_cstr
;
1823 use crate::sys
::{cvt, cvt_r}
;
1826 all(target_os
= "linux", target_env
= "gnu"),
1827 all(target_os
= "macos", not(target_arch
= "aarch64"))
1829 use libc
::{fdopendir, openat, unlinkat}
;
1830 #[cfg(all(target_os = "linux", target_env = "gnu"))]
1831 use libc
::{fdopendir, openat64 as openat, unlinkat}
;
1832 #[cfg(all(target_os = "macos", not(target_arch = "aarch64")))]
1833 use macos_weak
::{fdopendir, openat, unlinkat}
;
1835 #[cfg(all(target_os = "macos", not(target_arch = "aarch64")))]
1837 use crate::sys
::weak
::weak
;
1838 use libc
::{c_char, c_int, DIR}
;
1840 fn get_openat_fn() -> Option
<unsafe extern "C" fn(c_int
, *const c_char
, c_int
) -> c_int
> {
1841 weak
!(fn openat(c_int
, *const c_char
, c_int
) -> c_int
);
1845 pub fn has_openat() -> bool
{
1846 get_openat_fn().is_some()
1849 pub unsafe fn openat(dirfd
: c_int
, pathname
: *const c_char
, flags
: c_int
) -> c_int
{
1850 get_openat_fn().map(|openat
| openat(dirfd
, pathname
, flags
)).unwrap_or_else(|| {
1851 crate::sys
::unix
::os
::set_errno(libc
::ENOSYS
);
1856 pub unsafe fn fdopendir(fd
: c_int
) -> *mut DIR
{
1857 #[cfg(all(target_os = "macos", target_arch = "x86"))]
1858 weak
!(fn fdopendir(c_int
) -> *mut DIR
, "fdopendir$INODE64$UNIX2003");
1859 #[cfg(all(target_os = "macos", target_arch = "x86_64"))]
1860 weak
!(fn fdopendir(c_int
) -> *mut DIR
, "fdopendir$INODE64");
1861 fdopendir
.get().map(|fdopendir
| fdopendir(fd
)).unwrap_or_else(|| {
1862 crate::sys
::unix
::os
::set_errno(libc
::ENOSYS
);
1863 crate::ptr
::null_mut()
1867 pub unsafe fn unlinkat(dirfd
: c_int
, pathname
: *const c_char
, flags
: c_int
) -> c_int
{
1868 weak
!(fn unlinkat(c_int
, *const c_char
, c_int
) -> c_int
);
1869 unlinkat
.get().map(|unlinkat
| unlinkat(dirfd
, pathname
, flags
)).unwrap_or_else(|| {
1870 crate::sys
::unix
::os
::set_errno(libc
::ENOSYS
);
1876 pub fn openat_nofollow_dironly(parent_fd
: Option
<RawFd
>, p
: &CStr
) -> io
::Result
<OwnedFd
> {
1877 let fd
= cvt_r(|| unsafe {
1879 parent_fd
.unwrap_or(libc
::AT_FDCWD
),
1881 libc
::O_CLOEXEC
| libc
::O_RDONLY
| libc
::O_NOFOLLOW
| libc
::O_DIRECTORY
,
1884 Ok(unsafe { OwnedFd::from_raw_fd(fd) }
)
1887 fn fdreaddir(dir_fd
: OwnedFd
) -> io
::Result
<(ReadDir
, RawFd
)> {
1888 let ptr
= unsafe { fdopendir(dir_fd.as_raw_fd()) }
;
1890 return Err(io
::Error
::last_os_error());
1892 let dirp
= Dir(ptr
);
1893 // file descriptor is automatically closed by libc::closedir() now, so give up ownership
1894 let new_parent_fd
= dir_fd
.into_raw_fd();
1895 // a valid root is not needed because we do not call any functions involving the full path
1896 // of the `DirEntry`s.
1897 let dummy_root
= PathBuf
::new();
1898 let inner
= InnerReadDir { dirp, root: dummy_root }
;
1899 Ok((ReadDir
::new(inner
), new_parent_fd
))
1903 target_os
= "solaris",
1904 target_os
= "illumos",
1905 target_os
= "haiku",
1906 target_os
= "vxworks",
1908 fn is_dir(_ent
: &DirEntry
) -> Option
<bool
> {
1913 target_os
= "solaris",
1914 target_os
= "illumos",
1915 target_os
= "haiku",
1916 target_os
= "vxworks",
1918 fn is_dir(ent
: &DirEntry
) -> Option
<bool
> {
1919 match ent
.entry
.d_type
{
1920 libc
::DT_UNKNOWN
=> None
,
1921 libc
::DT_DIR
=> Some(true),
1926 fn remove_dir_all_recursive(parent_fd
: Option
<RawFd
>, path
: &CStr
) -> io
::Result
<()> {
1927 // try opening as directory
1928 let fd
= match openat_nofollow_dironly(parent_fd
, &path
) {
1929 Err(err
) if matches
!(err
.raw_os_error(), Some(libc
::ENOTDIR
| libc
::ELOOP
)) => {
1930 // not a directory - don't traverse further
1931 // (for symlinks, older Linux kernels may return ELOOP instead of ENOTDIR)
1932 return match parent_fd
{
1934 Some(parent_fd
) => {
1935 cvt(unsafe { unlinkat(parent_fd, path.as_ptr(), 0) }
).map(drop
)
1937 // ...unless this was supposed to be the deletion root directory
1944 // open the directory passing ownership of the fd
1945 let (dir
, fd
) = fdreaddir(fd
)?
;
1948 let child_name
= child
.name_cstr();
1949 match is_dir(&child
) {
1951 remove_dir_all_recursive(Some(fd
), child_name
)?
;
1954 cvt(unsafe { unlinkat(fd, child_name.as_ptr(), 0) }
)?
;
1957 // POSIX specifies that calling unlink()/unlinkat(..., 0) on a directory can succeed
1958 // if the process has the appropriate privileges. This however can causing orphaned
1959 // directories requiring an fsck e.g. on Solaris and Illumos. So we try recursing
1960 // into it first instead of trying to unlink() it.
1961 remove_dir_all_recursive(Some(fd
), child_name
)?
;
1966 // unlink the directory after removing its contents
1968 unlinkat(parent_fd
.unwrap_or(libc
::AT_FDCWD
), path
.as_ptr(), libc
::AT_REMOVEDIR
)
1973 fn remove_dir_all_modern(p
: &Path
) -> io
::Result
<()> {
1974 // We cannot just call remove_dir_all_recursive() here because that would not delete a passed
1975 // symlink. No need to worry about races, because remove_dir_all_recursive() does not recurse
1977 let attr
= lstat(p
)?
;
1978 if attr
.file_type().is_symlink() {
1979 crate::fs
::remove_file(p
)
1981 run_path_with_cstr(p
, |p
| remove_dir_all_recursive(None
, &p
))
1985 #[cfg(not(all(target_os = "macos", not(target_arch = "aarch64"))))]
1986 pub fn remove_dir_all(p
: &Path
) -> io
::Result
<()> {
1987 remove_dir_all_modern(p
)
1990 #[cfg(all(target_os = "macos", not(target_arch = "aarch64")))]
1991 pub fn remove_dir_all(p
: &Path
) -> io
::Result
<()> {
1992 if macos_weak
::has_openat() {
1993 // openat() is available with macOS 10.10+, just like unlinkat() and fdopendir()
1994 remove_dir_all_modern(p
)
1996 // fall back to classic implementation
1997 crate::sys_common
::fs
::remove_dir_all(p
)