1 use crate::os::unix::prelude::*;
3 use crate::ffi::{CStr, CString, OsStr, OsString};
5 use crate::io::{self, Error, ErrorKind, IoSlice, IoSliceMut, SeekFrom};
7 use crate::path::{Path, PathBuf};
10 use crate::sys::fd::FileDesc;
11 use crate::sys::time::SystemTime;
12 use crate::sys::{cvt, cvt_r};
13 use crate::sys_common::{AsInner, FromInner};
15 use libc::{c_int, mode_t};
17 #[cfg(any(target_os = "linux", target_os = "emscripten", target_os = "android"))]
19 #[cfg(any(target_os = "linux", target_os = "emscripten"))]
23 target_os = "emscripten",
24 target_os = "solaris",
25 target_os = "illumos",
27 target_os = "fuchsia",
30 use libc::readdir_r as readdir64_r;
31 #[cfg(target_os = "android")]
33 dirent as dirent64, fstat as fstat64, fstatat as fstatat64, lseek64, lstat as lstat64,
34 open as open64, stat as stat64,
38 target_os = "emscripten",
43 dirent as dirent64, fstat as fstat64, ftruncate as ftruncate64, lseek as lseek64,
44 lstat as lstat64, off_t as off64_t, open as open64, stat as stat64,
46 #[cfg(any(target_os = "linux", target_os = "emscripten", target_os = "l4re"))]
48 dirent64, fstat64, ftruncate64, lseek64, lstat64, off64_t, open64, readdir64_r, stat64,
51 pub use crate::sys_common::fs::remove_dir_all;
53 pub struct File(FileDesc);
55 // FIXME: This should be available on Linux with all `target_env`.
56 // But currently only glibc exposes `statx` fn and structs.
57 // We don't want to import unverified raw C structs here directly.
58 // https://github.com/rust-lang/rust/pull/67774
59 macro_rules! cfg_has_statx {
60 ({ $($then_tt:tt)* } else { $($else_tt:tt)* }) => {
62 if #[cfg(all(target_os = "linux", target_env = "gnu"))] {
69 ($($block_inner:tt)*) => {
70 #[cfg(all(target_os = "linux", target_env = "gnu"))]
81 statx_extra_fields: Option<StatxExtraFields>,
85 struct StatxExtraFields {
86 // This is needed to check if btime is supported by the filesystem.
88 stx_btime: libc::statx_timestamp,
91 // We prefer `statx` on Linux if available, which contains file creation time.
92 // Default `stat64` contains no creation time.
95 path: *const libc::c_char,
98 ) -> Option<io::Result<FileAttr>> {
99 use crate::sync::atomic::{AtomicU8, Ordering};
101 // Linux kernel prior to 4.11 or glibc prior to glibc 2.28 don't support `statx`
102 // We store the availability in global to avoid unnecessary syscalls.
106 static STATX_STATE: AtomicU8 = AtomicU8::new(0);
110 pathname: *const libc::c_char,
113 statxbuf: *mut libc::statx
117 match STATX_STATE.load(Ordering::Relaxed) {
119 // It is a trick to call `statx` with NULL pointers to check if the syscall
120 // is available. According to the manual, it is expected to fail with EFAULT.
121 // We do this mainly for performance, since it is nearly hundreds times
122 // faster than a normal successful call.
123 let err = cvt(statx(0, ptr::null(), 0, libc::STATX_ALL, ptr::null_mut()))
125 .and_then(|e| e.raw_os_error());
126 // We don't check `err == Some(libc::ENOSYS)` because the syscall may be limited
127 // and returns `EPERM`. Listing all possible errors seems not a good idea.
128 // See: https://github.com/rust-lang/rust/issues/65662
129 if err != Some(libc::EFAULT) {
130 STATX_STATE.store(1, Ordering::Relaxed);
133 STATX_STATE.store(2, Ordering::Relaxed);
139 let mut buf: libc::statx = mem::zeroed();
140 if let Err(err) = cvt(statx(fd, path, flags, mask, &mut buf)) {
141 return Some(Err(err));
144 // We cannot fill `stat64` exhaustively because of private padding fields.
145 let mut stat: stat64 = mem::zeroed();
146 // `c_ulong` on gnu-mips, `dev_t` otherwise
147 stat.st_dev = libc::makedev(buf.stx_dev_major, buf.stx_dev_minor) as _;
148 stat.st_ino = buf.stx_ino as libc::ino64_t;
149 stat.st_nlink = buf.stx_nlink as libc::nlink_t;
150 stat.st_mode = buf.stx_mode as libc::mode_t;
151 stat.st_uid = buf.stx_uid as libc::uid_t;
152 stat.st_gid = buf.stx_gid as libc::gid_t;
153 stat.st_rdev = libc::makedev(buf.stx_rdev_major, buf.stx_rdev_minor) as _;
154 stat.st_size = buf.stx_size as off64_t;
155 stat.st_blksize = buf.stx_blksize as libc::blksize_t;
156 stat.st_blocks = buf.stx_blocks as libc::blkcnt64_t;
157 stat.st_atime = buf.stx_atime.tv_sec as libc::time_t;
158 // `i64` on gnu-x86_64-x32, `c_ulong` otherwise.
159 stat.st_atime_nsec = buf.stx_atime.tv_nsec as _;
160 stat.st_mtime = buf.stx_mtime.tv_sec as libc::time_t;
161 stat.st_mtime_nsec = buf.stx_mtime.tv_nsec as _;
162 stat.st_ctime = buf.stx_ctime.tv_sec as libc::time_t;
163 stat.st_ctime_nsec = buf.stx_ctime.tv_nsec as _;
165 let extra = StatxExtraFields {
166 stx_mask: buf.stx_mask,
167 stx_btime: buf.stx_btime,
170 Some(Ok(FileAttr { stat, statx_extra_fields: Some(extra) }))
175 pub struct FileAttr {
180 // all DirEntry's will have a reference to this struct
181 struct InnerReadDir {
187 inner: Arc<InnerReadDir>,
189 target_os = "solaris",
190 target_os = "illumos",
191 target_os = "fuchsia",
197 struct Dir(*mut libc::DIR);
199 unsafe impl Send for Dir {}
200 unsafe impl Sync for Dir {}
202 pub struct DirEntry {
204 dir: Arc<InnerReadDir>,
205 // We need to store an owned copy of the entry name
206 // on Solaris and Fuchsia because a) it uses a zero-length
207 // array to store the name, b) its lifetime between readdir
208 // calls is not guaranteed.
210 target_os = "solaris",
211 target_os = "illumos",
212 target_os = "fuchsia",
218 #[derive(Clone, Debug)]
219 pub struct OpenOptions {
232 #[derive(Clone, PartialEq, Eq, Debug)]
233 pub struct FilePermissions {
237 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
238 pub struct FileType {
243 pub struct DirBuilder {
249 fn from_stat64(stat: stat64) -> Self {
250 Self { stat, statx_extra_fields: None }
255 fn from_stat64(stat: stat64) -> Self {
262 pub fn size(&self) -> u64 {
263 self.stat.st_size as u64
265 pub fn perm(&self) -> FilePermissions {
266 FilePermissions { mode: (self.stat.st_mode as mode_t) }
269 pub fn file_type(&self) -> FileType {
270 FileType { mode: self.stat.st_mode as mode_t }
274 #[cfg(target_os = "netbsd")]
276 pub fn modified(&self) -> io::Result<SystemTime> {
277 Ok(SystemTime::from(libc::timespec {
278 tv_sec: self.stat.st_mtime as libc::time_t,
279 tv_nsec: self.stat.st_mtimensec as libc::c_long,
283 pub fn accessed(&self) -> io::Result<SystemTime> {
284 Ok(SystemTime::from(libc::timespec {
285 tv_sec: self.stat.st_atime as libc::time_t,
286 tv_nsec: self.stat.st_atimensec as libc::c_long,
290 pub fn created(&self) -> io::Result<SystemTime> {
291 Ok(SystemTime::from(libc::timespec {
292 tv_sec: self.stat.st_birthtime as libc::time_t,
293 tv_nsec: self.stat.st_birthtimensec as libc::c_long,
298 #[cfg(not(target_os = "netbsd"))]
300 #[cfg(not(target_os = "vxworks"))]
301 pub fn modified(&self) -> io::Result<SystemTime> {
302 Ok(SystemTime::from(libc::timespec {
303 tv_sec: self.stat.st_mtime as libc::time_t,
304 tv_nsec: self.stat.st_mtime_nsec as _,
308 #[cfg(target_os = "vxworks")]
309 pub fn modified(&self) -> io::Result<SystemTime> {
310 Ok(SystemTime::from(libc::timespec {
311 tv_sec: self.stat.st_mtime as libc::time_t,
316 #[cfg(not(target_os = "vxworks"))]
317 pub fn accessed(&self) -> io::Result<SystemTime> {
318 Ok(SystemTime::from(libc::timespec {
319 tv_sec: self.stat.st_atime as libc::time_t,
320 tv_nsec: self.stat.st_atime_nsec as _,
324 #[cfg(target_os = "vxworks")]
325 pub fn accessed(&self) -> io::Result<SystemTime> {
326 Ok(SystemTime::from(libc::timespec {
327 tv_sec: self.stat.st_atime as libc::time_t,
333 target_os = "freebsd",
334 target_os = "openbsd",
338 pub fn created(&self) -> io::Result<SystemTime> {
339 Ok(SystemTime::from(libc::timespec {
340 tv_sec: self.stat.st_birthtime as libc::time_t,
341 tv_nsec: self.stat.st_birthtime_nsec as libc::c_long,
346 target_os = "freebsd",
347 target_os = "openbsd",
351 pub fn created(&self) -> io::Result<SystemTime> {
353 if let Some(ext) = &self.statx_extra_fields {
354 return if (ext.stx_mask & libc::STATX_BTIME) != 0 {
355 Ok(SystemTime::from(libc::timespec {
356 tv_sec: ext.stx_btime.tv_sec as libc::time_t,
357 tv_nsec: ext.stx_btime.tv_nsec as _,
361 io::ErrorKind::Other,
362 "creation time is not available for the filesystem",
369 io::ErrorKind::Other,
370 "creation time is not available on this platform \
376 impl AsInner<stat64> for FileAttr {
377 fn as_inner(&self) -> &stat64 {
382 impl FilePermissions {
383 pub fn readonly(&self) -> bool {
384 // check if any class (owner, group, others) has write permission
385 self.mode & 0o222 == 0
388 pub fn set_readonly(&mut self, readonly: bool) {
390 // remove write permission for all classes; equivalent to `chmod a-w <file>`
393 // add write permission for all classes; equivalent to `chmod a+w <file>`
397 pub fn mode(&self) -> u32 {
403 pub fn is_dir(&self) -> bool {
404 self.is(libc::S_IFDIR)
406 pub fn is_file(&self) -> bool {
407 self.is(libc::S_IFREG)
409 pub fn is_symlink(&self) -> bool {
410 self.is(libc::S_IFLNK)
413 pub fn is(&self, mode: mode_t) -> bool {
414 self.mode & libc::S_IFMT == mode
418 impl FromInner<u32> for FilePermissions {
419 fn from_inner(mode: u32) -> FilePermissions {
420 FilePermissions { mode: mode as mode_t }
424 impl fmt::Debug for ReadDir {
425 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
426 // This will only be called from std::fs::ReadDir, which will add a "ReadDir()" frame.
427 // Thus the result will be e g 'ReadDir("/home")'
428 fmt::Debug::fmt(&*self.inner.root, f)
432 impl Iterator for ReadDir {
433 type Item = io::Result<DirEntry>;
436 target_os = "solaris",
437 target_os = "fuchsia",
439 target_os = "illumos"
441 fn next(&mut self) -> Option<io::Result<DirEntry>> {
446 // Although readdir_r(3) would be a correct function to use here because
447 // of the thread safety, on Illumos and Fuchsia the readdir(3C) function
448 // is safe to use in threaded applications and it is generally preferred
449 // over the readdir_r(3C) function.
450 super::os::set_errno(0);
451 let entry_ptr = libc::readdir(self.inner.dirp.0);
452 if entry_ptr.is_null() {
453 // NULL can mean either the end is reached or an error occurred.
454 // So we had to clear errno beforehand to check for an error now.
455 return match super::os::errno() {
457 e => Some(Err(Error::from_raw_os_error(e))),
461 let name = (*entry_ptr).d_name.as_ptr();
462 let namelen = libc::strlen(name) as usize;
466 name: slice::from_raw_parts(name as *const u8, namelen as usize)
469 dir: Arc::clone(&self.inner),
471 if ret.name_bytes() != b"." && ret.name_bytes() != b".." {
472 return Some(Ok(ret));
479 target_os = "solaris",
480 target_os = "fuchsia",
482 target_os = "illumos"
484 fn next(&mut self) -> Option<io::Result<DirEntry>> {
485 if self.end_of_stream {
490 let mut ret = DirEntry { entry: mem::zeroed(), dir: Arc::clone(&self.inner) };
491 let mut entry_ptr = ptr::null_mut();
493 if readdir64_r(self.inner.dirp.0, &mut ret.entry, &mut entry_ptr) != 0 {
494 if entry_ptr.is_null() {
495 // We encountered an error (which will be returned in this iteration), but
496 // we also reached the end of the directory stream. The `end_of_stream`
497 // flag is enabled to make sure that we return `None` in the next iteration
498 // (instead of looping forever)
499 self.end_of_stream = true;
501 return Some(Err(Error::last_os_error()));
503 if entry_ptr.is_null() {
506 if ret.name_bytes() != b"." && ret.name_bytes() != b".." {
507 return Some(Ok(ret));
516 let r = unsafe { libc::closedir(self.0) };
517 debug_assert_eq!(r, 0);
522 pub fn path(&self) -> PathBuf {
523 self.dir.root.join(OsStr::from_bytes(self.name_bytes()))
526 pub fn file_name(&self) -> OsString {
527 OsStr::from_bytes(self.name_bytes()).to_os_string()
530 #[cfg(any(target_os = "linux", target_os = "emscripten", target_os = "android"))]
531 pub fn metadata(&self) -> io::Result<FileAttr> {
532 let fd = cvt(unsafe { dirfd(self.dir.dirp.0) })?;
533 let name = self.entry.d_name.as_ptr();
536 if let Some(ret) = unsafe { try_statx(
539 libc::AT_SYMLINK_NOFOLLOW | libc::AT_STATX_SYNC_AS_STAT,
546 let mut stat: stat64 = unsafe { mem::zeroed() };
547 cvt(unsafe { fstatat64(fd, name, &mut stat, libc::AT_SYMLINK_NOFOLLOW) })?;
548 Ok(FileAttr::from_stat64(stat))
551 #[cfg(not(any(target_os = "linux", target_os = "emscripten", target_os = "android")))]
552 pub fn metadata(&self) -> io::Result<FileAttr> {
557 target_os = "solaris",
558 target_os = "illumos",
560 target_os = "vxworks"
562 pub fn file_type(&self) -> io::Result<FileType> {
563 lstat(&self.path()).map(|m| m.file_type())
567 target_os = "solaris",
568 target_os = "illumos",
570 target_os = "vxworks"
572 pub fn file_type(&self) -> io::Result<FileType> {
573 match self.entry.d_type {
574 libc::DT_CHR => Ok(FileType { mode: libc::S_IFCHR }),
575 libc::DT_FIFO => Ok(FileType { mode: libc::S_IFIFO }),
576 libc::DT_LNK => Ok(FileType { mode: libc::S_IFLNK }),
577 libc::DT_REG => Ok(FileType { mode: libc::S_IFREG }),
578 libc::DT_SOCK => Ok(FileType { mode: libc::S_IFSOCK }),
579 libc::DT_DIR => Ok(FileType { mode: libc::S_IFDIR }),
580 libc::DT_BLK => Ok(FileType { mode: libc::S_IFBLK }),
581 _ => lstat(&self.path()).map(|m| m.file_type()),
589 target_os = "emscripten",
590 target_os = "android",
591 target_os = "solaris",
592 target_os = "illumos",
595 target_os = "fuchsia",
597 target_os = "vxworks"
599 pub fn ino(&self) -> u64 {
600 self.entry.d_ino as u64
604 target_os = "freebsd",
605 target_os = "openbsd",
606 target_os = "netbsd",
607 target_os = "dragonfly"
609 pub fn ino(&self) -> u64 {
610 self.entry.d_fileno as u64
616 target_os = "netbsd",
617 target_os = "openbsd",
618 target_os = "freebsd",
619 target_os = "dragonfly"
621 fn name_bytes(&self) -> &[u8] {
624 slice::from_raw_parts(
625 self.entry.d_name.as_ptr() as *const u8,
626 self.entry.d_namlen as usize,
631 target_os = "android",
633 target_os = "emscripten",
636 target_os = "vxworks"
638 fn name_bytes(&self) -> &[u8] {
639 unsafe { CStr::from_ptr(self.entry.d_name.as_ptr()).to_bytes() }
642 target_os = "solaris",
643 target_os = "illumos",
644 target_os = "fuchsia",
647 fn name_bytes(&self) -> &[u8] {
653 pub fn new() -> OpenOptions {
668 pub fn read(&mut self, read: bool) {
671 pub fn write(&mut self, write: bool) {
674 pub fn append(&mut self, append: bool) {
675 self.append = append;
677 pub fn truncate(&mut self, truncate: bool) {
678 self.truncate = truncate;
680 pub fn create(&mut self, create: bool) {
681 self.create = create;
683 pub fn create_new(&mut self, create_new: bool) {
684 self.create_new = create_new;
687 pub fn custom_flags(&mut self, flags: i32) {
688 self.custom_flags = flags;
690 pub fn mode(&mut self, mode: u32) {
691 self.mode = mode as mode_t;
694 fn get_access_mode(&self) -> io::Result<c_int> {
695 match (self.read, self.write, self.append) {
696 (true, false, false) => Ok(libc::O_RDONLY),
697 (false, true, false) => Ok(libc::O_WRONLY),
698 (true, true, false) => Ok(libc::O_RDWR),
699 (false, _, true) => Ok(libc::O_WRONLY | libc::O_APPEND),
700 (true, _, true) => Ok(libc::O_RDWR | libc::O_APPEND),
701 (false, false, false) => Err(Error::from_raw_os_error(libc::EINVAL)),
705 fn get_creation_mode(&self) -> io::Result<c_int> {
706 match (self.write, self.append) {
709 if self.truncate || self.create || self.create_new {
710 return Err(Error::from_raw_os_error(libc::EINVAL));
714 if self.truncate && !self.create_new {
715 return Err(Error::from_raw_os_error(libc::EINVAL));
720 Ok(match (self.create, self.truncate, self.create_new) {
721 (false, false, false) => 0,
722 (true, false, false) => libc::O_CREAT,
723 (false, true, false) => libc::O_TRUNC,
724 (true, true, false) => libc::O_CREAT | libc::O_TRUNC,
725 (_, _, true) => libc::O_CREAT | libc::O_EXCL,
731 pub fn open(path: &Path, opts: &OpenOptions) -> io::Result<File> {
732 let path = cstr(path)?;
733 File::open_c(&path, opts)
736 pub fn open_c(path: &CStr, opts: &OpenOptions) -> io::Result<File> {
737 let flags = libc::O_CLOEXEC
738 | opts.get_access_mode()?
739 | opts.get_creation_mode()?
740 | (opts.custom_flags as c_int & !libc::O_ACCMODE);
741 // The third argument of `open64` is documented to have type `mode_t`. On
742 // some platforms (like macOS, where `open64` is actually `open`), `mode_t` is `u16`.
743 // However, since this is a variadic function, C integer promotion rules mean that on
744 // the ABI level, this still gets passed as `c_int` (aka `u32` on Unix platforms).
745 let fd = cvt_r(|| unsafe { open64(path.as_ptr(), flags, opts.mode as c_int) })?;
746 Ok(File(FileDesc::new(fd)))
749 pub fn file_attr(&self) -> io::Result<FileAttr> {
750 let fd = self.0.raw();
753 if let Some(ret) = unsafe { try_statx(
755 b"\0" as *const _ as *const libc::c_char,
756 libc::AT_EMPTY_PATH | libc::AT_STATX_SYNC_AS_STAT,
763 let mut stat: stat64 = unsafe { mem::zeroed() };
764 cvt(unsafe { fstat64(fd, &mut stat) })?;
765 Ok(FileAttr::from_stat64(stat))
768 pub fn fsync(&self) -> io::Result<()> {
769 cvt_r(|| unsafe { os_fsync(self.0.raw()) })?;
772 #[cfg(any(target_os = "macos", target_os = "ios"))]
773 unsafe fn os_fsync(fd: c_int) -> c_int {
774 libc::fcntl(fd, libc::F_FULLFSYNC)
776 #[cfg(not(any(target_os = "macos", target_os = "ios")))]
777 unsafe fn os_fsync(fd: c_int) -> c_int {
782 pub fn datasync(&self) -> io::Result<()> {
783 cvt_r(|| unsafe { os_datasync(self.0.raw()) })?;
786 #[cfg(any(target_os = "macos", target_os = "ios"))]
787 unsafe fn os_datasync(fd: c_int) -> c_int {
788 libc::fcntl(fd, libc::F_FULLFSYNC)
791 target_os = "freebsd",
793 target_os = "android",
794 target_os = "netbsd",
795 target_os = "openbsd"
797 unsafe fn os_datasync(fd: c_int) -> c_int {
801 target_os = "android",
802 target_os = "freebsd",
806 target_os = "netbsd",
807 target_os = "openbsd"
809 unsafe fn os_datasync(fd: c_int) -> c_int {
814 pub fn truncate(&self, size: u64) -> io::Result<()> {
815 #[cfg(target_os = "android")]
816 return crate::sys::android::ftruncate64(self.0.raw(), size);
818 #[cfg(not(target_os = "android"))]
820 use crate::convert::TryInto;
822 size.try_into().map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
823 cvt_r(|| unsafe { ftruncate64(self.0.raw(), size) }).map(drop)
827 pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
831 pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
832 self.0.read_vectored(bufs)
836 pub fn is_read_vectored(&self) -> bool {
837 self.0.is_read_vectored()
840 pub fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
841 self.0.read_at(buf, offset)
844 pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
848 pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
849 self.0.write_vectored(bufs)
853 pub fn is_write_vectored(&self) -> bool {
854 self.0.is_write_vectored()
857 pub fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
858 self.0.write_at(buf, offset)
861 pub fn flush(&self) -> io::Result<()> {
865 pub fn seek(&self, pos: SeekFrom) -> io::Result<u64> {
866 let (whence, pos) = match pos {
867 // Casting to `i64` is fine, too large values will end up as
868 // negative which will cause an error in `lseek64`.
869 SeekFrom::Start(off) => (libc::SEEK_SET, off as i64),
870 SeekFrom::End(off) => (libc::SEEK_END, off),
871 SeekFrom::Current(off) => (libc::SEEK_CUR, off),
873 let n = cvt(unsafe { lseek64(self.0.raw(), pos, whence) })?;
877 pub fn duplicate(&self) -> io::Result<File> {
878 self.0.duplicate().map(File)
881 pub fn fd(&self) -> &FileDesc {
885 pub fn into_fd(self) -> FileDesc {
889 pub fn set_permissions(&self, perm: FilePermissions) -> io::Result<()> {
890 cvt_r(|| unsafe { libc::fchmod(self.0.raw(), perm.mode) })?;
896 pub fn new() -> DirBuilder {
897 DirBuilder { mode: 0o777 }
900 pub fn mkdir(&self, p: &Path) -> io::Result<()> {
902 cvt(unsafe { libc::mkdir(p.as_ptr(), self.mode) })?;
906 pub fn set_mode(&mut self, mode: u32) {
907 self.mode = mode as mode_t;
911 fn cstr(path: &Path) -> io::Result<CString> {
912 Ok(CString::new(path.as_os_str().as_bytes())?)
915 impl FromInner<c_int> for File {
916 fn from_inner(fd: c_int) -> File {
917 File(FileDesc::new(fd))
921 impl fmt::Debug for File {
922 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
923 #[cfg(target_os = "linux")]
924 fn get_path(fd: c_int) -> Option<PathBuf> {
925 let mut p = PathBuf::from("/proc/self/fd");
926 p.push(&fd.to_string());
930 #[cfg(target_os = "macos")]
931 fn get_path(fd: c_int) -> Option<PathBuf> {
932 // FIXME: The use of PATH_MAX is generally not encouraged, but it
933 // is inevitable in this case because macOS defines `fcntl` with
934 // `F_GETPATH` in terms of `MAXPATHLEN`, and there are no
935 // alternatives. If a better method is invented, it should be used
937 let mut buf = vec![0; libc::PATH_MAX as usize];
938 let n = unsafe { libc::fcntl(fd, libc::F_GETPATH, buf.as_ptr()) };
942 let l = buf.iter().position(|&c| c == 0).unwrap();
943 buf.truncate(l as usize);
945 Some(PathBuf::from(OsString::from_vec(buf)))
948 #[cfg(target_os = "vxworks")]
949 fn get_path(fd: c_int) -> Option<PathBuf> {
950 let mut buf = vec![0; libc::PATH_MAX as usize];
951 let n = unsafe { libc::ioctl(fd, libc::FIOGETNAME, buf.as_ptr()) };
955 let l = buf.iter().position(|&c| c == 0).unwrap();
956 buf.truncate(l as usize);
957 Some(PathBuf::from(OsString::from_vec(buf)))
960 #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "vxworks")))]
961 fn get_path(_fd: c_int) -> Option<PathBuf> {
962 // FIXME(#24570): implement this for other Unix platforms
966 #[cfg(any(target_os = "linux", target_os = "macos", target_os = "vxworks"))]
967 fn get_mode(fd: c_int) -> Option<(bool, bool)> {
968 let mode = unsafe { libc::fcntl(fd, libc::F_GETFL) };
972 match mode & libc::O_ACCMODE {
973 libc::O_RDONLY => Some((true, false)),
974 libc::O_RDWR => Some((true, true)),
975 libc::O_WRONLY => Some((false, true)),
980 #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "vxworks")))]
981 fn get_mode(_fd: c_int) -> Option<(bool, bool)> {
982 // FIXME(#24570): implement this for other Unix platforms
986 let fd = self.0.raw();
987 let mut b = f.debug_struct("File");
989 if let Some(path) = get_path(fd) {
990 b.field("path", &path);
992 if let Some((read, write)) = get_mode(fd) {
993 b.field("read", &read).field("write", &write);
999 pub fn readdir(p: &Path) -> io::Result<ReadDir> {
1000 let root = p.to_path_buf();
1003 let ptr = libc::opendir(p.as_ptr());
1005 Err(Error::last_os_error())
1007 let inner = InnerReadDir { dirp: Dir(ptr), root };
1009 inner: Arc::new(inner),
1011 target_os = "solaris",
1012 target_os = "illumos",
1013 target_os = "fuchsia",
1014 target_os = "redox",
1016 end_of_stream: false,
1022 pub fn unlink(p: &Path) -> io::Result<()> {
1024 cvt(unsafe { libc::unlink(p.as_ptr()) })?;
1028 pub fn rename(old: &Path, new: &Path) -> io::Result<()> {
1029 let old = cstr(old)?;
1030 let new = cstr(new)?;
1031 cvt(unsafe { libc::rename(old.as_ptr(), new.as_ptr()) })?;
1035 pub fn set_perm(p: &Path, perm: FilePermissions) -> io::Result<()> {
1037 cvt_r(|| unsafe { libc::chmod(p.as_ptr(), perm.mode) })?;
1041 pub fn rmdir(p: &Path) -> io::Result<()> {
1043 cvt(unsafe { libc::rmdir(p.as_ptr()) })?;
1047 pub fn readlink(p: &Path) -> io::Result<PathBuf> {
1048 let c_path = cstr(p)?;
1049 let p = c_path.as_ptr();
1051 let mut buf = Vec::with_capacity(256);
1055 cvt(unsafe { libc::readlink(p, buf.as_mut_ptr() as *mut _, buf.capacity()) })? as usize;
1058 buf.set_len(buf_read);
1061 if buf_read != buf.capacity() {
1062 buf.shrink_to_fit();
1064 return Ok(PathBuf::from(OsString::from_vec(buf)));
1067 // Trigger the internal buffer resizing logic of `Vec` by requiring
1068 // more space than the current capacity. The length is guaranteed to be
1069 // the same as the capacity due to the if statement above.
1074 pub fn symlink(src: &Path, dst: &Path) -> io::Result<()> {
1075 let src = cstr(src)?;
1076 let dst = cstr(dst)?;
1077 cvt(unsafe { libc::symlink(src.as_ptr(), dst.as_ptr()) })?;
1081 pub fn link(src: &Path, dst: &Path) -> io::Result<()> {
1082 let src = cstr(src)?;
1083 let dst = cstr(dst)?;
1085 if #[cfg(any(target_os = "vxworks", target_os = "redox", target_os = "android"))] {
1086 // VxWorks, Redox, and old versions of Android lack `linkat`, so use
1087 // `link` instead. POSIX leaves it implementation-defined whether
1088 // `link` follows symlinks, so rely on the `symlink_hard_link` test
1089 // in library/std/src/fs/tests.rs to check the behavior.
1090 cvt(unsafe { libc::link(src.as_ptr(), dst.as_ptr()) })?;
1092 // Use `linkat` with `AT_FDCWD` instead of `link` as `linkat` gives
1093 // us a flag to specify how symlinks should be handled. Pass 0 as
1094 // the flags argument, meaning don't follow symlinks.
1095 cvt(unsafe { libc::linkat(libc::AT_FDCWD, src.as_ptr(), libc::AT_FDCWD, dst.as_ptr(), 0) })?;
1101 pub fn stat(p: &Path) -> io::Result<FileAttr> {
1105 if let Some(ret) = unsafe { try_statx(
1108 libc::AT_STATX_SYNC_AS_STAT,
1115 let mut stat: stat64 = unsafe { mem::zeroed() };
1116 cvt(unsafe { stat64(p.as_ptr(), &mut stat) })?;
1117 Ok(FileAttr::from_stat64(stat))
1120 pub fn lstat(p: &Path) -> io::Result<FileAttr> {
1124 if let Some(ret) = unsafe { try_statx(
1127 libc::AT_SYMLINK_NOFOLLOW | libc::AT_STATX_SYNC_AS_STAT,
1134 let mut stat: stat64 = unsafe { mem::zeroed() };
1135 cvt(unsafe { lstat64(p.as_ptr(), &mut stat) })?;
1136 Ok(FileAttr::from_stat64(stat))
1139 pub fn canonicalize(p: &Path) -> io::Result<PathBuf> {
1140 let path = CString::new(p.as_os_str().as_bytes())?;
1143 let r = libc::realpath(path.as_ptr(), ptr::null_mut());
1145 return Err(io::Error::last_os_error());
1147 buf = CStr::from_ptr(r).to_bytes().to_vec();
1148 libc::free(r as *mut _);
1150 Ok(PathBuf::from(OsString::from_vec(buf)))
1153 fn open_from(from: &Path) -> io::Result<(crate::fs::File, crate::fs::Metadata)> {
1154 use crate::fs::File;
1156 let reader = File::open(from)?;
1157 let metadata = reader.metadata()?;
1158 if !metadata.is_file() {
1159 return Err(Error::new(
1160 ErrorKind::InvalidInput,
1161 "the source path is not an existing regular file",
1164 Ok((reader, metadata))
1167 fn open_to_and_set_permissions(
1169 reader_metadata: crate::fs::Metadata,
1170 ) -> io::Result<(crate::fs::File, crate::fs::Metadata)> {
1171 use crate::fs::OpenOptions;
1172 use crate::os::unix::fs::{OpenOptionsExt, PermissionsExt};
1174 let perm = reader_metadata.permissions();
1175 let writer = OpenOptions::new()
1176 // create the file with the correct mode right away
1182 let writer_metadata = writer.metadata()?;
1183 if writer_metadata.is_file() {
1184 // Set the correct file permissions, in case the file already existed.
1185 // Don't set the permissions on already existing non-files like
1186 // pipes/FIFOs or device nodes.
1187 writer.set_permissions(perm)?;
1189 Ok((writer, writer_metadata))
1193 target_os = "linux",
1194 target_os = "android",
1195 target_os = "macos",
1198 pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
1199 let (mut reader, reader_metadata) = open_from(from)?;
1200 let (mut writer, _) = open_to_and_set_permissions(to, reader_metadata)?;
1202 io::copy(&mut reader, &mut writer)
1205 #[cfg(any(target_os = "linux", target_os = "android"))]
1206 pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
1207 let (mut reader, reader_metadata) = open_from(from)?;
1208 let max_len = u64::MAX;
1209 let (mut writer, _) = open_to_and_set_permissions(to, reader_metadata)?;
1211 use super::kernel_copy::{copy_regular_files, CopyResult};
1213 match copy_regular_files(reader.as_raw_fd(), writer.as_raw_fd(), max_len) {
1214 CopyResult::Ended(result) => result,
1215 CopyResult::Fallback(written) => match io::copy::generic_copy(&mut reader, &mut writer) {
1216 Ok(bytes) => Ok(bytes + written),
1222 #[cfg(any(target_os = "macos", target_os = "ios"))]
1223 pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
1224 use crate::sync::atomic::{AtomicBool, Ordering};
1226 const COPYFILE_ACL: u32 = 1 << 0;
1227 const COPYFILE_STAT: u32 = 1 << 1;
1228 const COPYFILE_XATTR: u32 = 1 << 2;
1229 const COPYFILE_DATA: u32 = 1 << 3;
1231 const COPYFILE_SECURITY: u32 = COPYFILE_STAT | COPYFILE_ACL;
1232 const COPYFILE_METADATA: u32 = COPYFILE_SECURITY | COPYFILE_XATTR;
1233 const COPYFILE_ALL: u32 = COPYFILE_METADATA | COPYFILE_DATA;
1235 const COPYFILE_STATE_COPIED: u32 = 8;
1237 #[allow(non_camel_case_types)]
1238 type copyfile_state_t = *mut libc::c_void;
1239 #[allow(non_camel_case_types)]
1240 type copyfile_flags_t = u32;
1246 state: copyfile_state_t,
1247 flags: copyfile_flags_t,
1249 fn copyfile_state_alloc() -> copyfile_state_t;
1250 fn copyfile_state_free(state: copyfile_state_t) -> libc::c_int;
1251 fn copyfile_state_get(
1252 state: copyfile_state_t,
1254 dst: *mut libc::c_void,
1258 struct FreeOnDrop(copyfile_state_t);
1259 impl Drop for FreeOnDrop {
1260 fn drop(&mut self) {
1261 // The code below ensures that `FreeOnDrop` is never a null pointer
1263 // `copyfile_state_free` returns -1 if the `to` or `from` files
1264 // cannot be closed. However, this is not considered this an
1266 copyfile_state_free(self.0);
1271 // MacOS prior to 10.12 don't support `fclonefileat`
1272 // We store the availability in a global to avoid unnecessary syscalls
1273 static HAS_FCLONEFILEAT: AtomicBool = AtomicBool::new(true);
1277 dst_dirfd: libc::c_int,
1278 dst: *const libc::c_char,
1283 let (reader, reader_metadata) = open_from(from)?;
1285 // Opportunistically attempt to create a copy-on-write clone of `from`
1286 // using `fclonefileat`.
1287 if HAS_FCLONEFILEAT.load(Ordering::Relaxed) {
1289 let clonefile_result =
1290 cvt(unsafe { fclonefileat(reader.as_raw_fd(), libc::AT_FDCWD, to.as_ptr(), 0) });
1291 match clonefile_result {
1292 Ok(_) => return Ok(reader_metadata.len()),
1293 Err(err) => match err.raw_os_error() {
1294 // `fclonefileat` will fail on non-APFS volumes, if the
1295 // destination already exists, or if the source and destination
1296 // are on different devices. In all these cases `fcopyfile`
1298 Some(libc::ENOTSUP) | Some(libc::EEXIST) | Some(libc::EXDEV) => (),
1299 Some(libc::ENOSYS) => HAS_FCLONEFILEAT.store(false, Ordering::Relaxed),
1300 _ => return Err(err),
1305 // Fall back to using `fcopyfile` if `fclonefileat` does not succeed.
1306 let (writer, writer_metadata) = open_to_and_set_permissions(to, reader_metadata)?;
1308 // We ensure that `FreeOnDrop` never contains a null pointer so it is
1309 // always safe to call `copyfile_state_free`
1310 let state = unsafe {
1311 let state = copyfile_state_alloc();
1312 if state.is_null() {
1313 return Err(crate::io::Error::last_os_error());
1318 let flags = if writer_metadata.is_file() { COPYFILE_ALL } else { COPYFILE_DATA };
1320 cvt(unsafe { fcopyfile(reader.as_raw_fd(), writer.as_raw_fd(), state.0, flags) })?;
1322 let mut bytes_copied: libc::off_t = 0;
1326 COPYFILE_STATE_COPIED,
1327 &mut bytes_copied as *mut libc::off_t as *mut libc::c_void,
1330 Ok(bytes_copied as u64)