From 698f8c2f01ea549d77d7dc3338a12e04c11057b9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 17 Apr 2024 14:02:58 +0200 Subject: Adding upstream version 1.64.0+dfsg1. Signed-off-by: Daniel Baumann --- library/std/src/sys/unix/fs.rs | 1878 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1878 insertions(+) create mode 100644 library/std/src/sys/unix/fs.rs (limited to 'library/std/src/sys/unix/fs.rs') diff --git a/library/std/src/sys/unix/fs.rs b/library/std/src/sys/unix/fs.rs new file mode 100644 index 000000000..b5cc8038c --- /dev/null +++ b/library/std/src/sys/unix/fs.rs @@ -0,0 +1,1878 @@ +use crate::os::unix::prelude::*; + +use crate::ffi::{CStr, CString, OsStr, OsString}; +use crate::fmt; +use crate::io::{self, Error, IoSlice, IoSliceMut, ReadBuf, SeekFrom}; +use crate::mem; +use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd}; +use crate::path::{Path, PathBuf}; +use crate::ptr; +use crate::sync::Arc; +use crate::sys::fd::FileDesc; +use crate::sys::time::SystemTime; +use crate::sys::{cvt, cvt_r}; +use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner}; + +#[cfg(any( + all(target_os = "linux", target_env = "gnu"), + target_os = "macos", + target_os = "ios", + target_os = "watchos", +))] +use crate::sys::weak::syscall; +#[cfg(any(target_os = "android", target_os = "macos"))] +use crate::sys::weak::weak; + +use libc::{c_int, mode_t}; + +#[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "watchos", + all(target_os = "linux", target_env = "gnu") +))] +use libc::c_char; +#[cfg(any(target_os = "linux", target_os = "emscripten", target_os = "android"))] +use libc::dirfd; +#[cfg(any(target_os = "linux", target_os = "emscripten"))] +use libc::fstatat64; +#[cfg(any( + target_os = "android", + target_os = "solaris", + target_os = "fuchsia", + target_os = "redox", + target_os = "illumos" +))] +use libc::readdir as readdir64; +#[cfg(target_os = "linux")] +use libc::readdir64; +#[cfg(any(target_os = "emscripten", target_os = "l4re"))] +use libc::readdir64_r; +#[cfg(not(any( + target_os = "android", + target_os = "linux", + target_os = "emscripten", + target_os = "solaris", + target_os = "illumos", + target_os = "l4re", + target_os = "fuchsia", + target_os = "redox" +)))] +use libc::readdir_r as readdir64_r; +#[cfg(target_os = "android")] +use libc::{ + dirent as dirent64, fstat as fstat64, fstatat as fstatat64, ftruncate64, lseek64, + lstat as lstat64, off64_t, open as open64, stat as stat64, +}; +#[cfg(not(any( + target_os = "linux", + target_os = "emscripten", + target_os = "l4re", + target_os = "android" +)))] +use libc::{ + dirent as dirent64, fstat as fstat64, ftruncate as ftruncate64, lseek as lseek64, + lstat as lstat64, off_t as off64_t, open as open64, stat as stat64, +}; +#[cfg(any(target_os = "linux", target_os = "emscripten", target_os = "l4re"))] +use libc::{dirent64, fstat64, ftruncate64, lseek64, lstat64, off64_t, open64, stat64}; + +pub use crate::sys_common::fs::try_exists; + +pub struct File(FileDesc); + +// FIXME: This should be available on Linux with all `target_env`. +// But currently only glibc exposes `statx` fn and structs. +// We don't want to import unverified raw C structs here directly. +// https://github.com/rust-lang/rust/pull/67774 +macro_rules! cfg_has_statx { + ({ $($then_tt:tt)* } else { $($else_tt:tt)* }) => { + cfg_if::cfg_if! { + if #[cfg(all(target_os = "linux", target_env = "gnu"))] { + $($then_tt)* + } else { + $($else_tt)* + } + } + }; + ($($block_inner:tt)*) => { + #[cfg(all(target_os = "linux", target_env = "gnu"))] + { + $($block_inner)* + } + }; +} + +cfg_has_statx! {{ + #[derive(Clone)] + pub struct FileAttr { + stat: stat64, + statx_extra_fields: Option, + } + + #[derive(Clone)] + struct StatxExtraFields { + // This is needed to check if btime is supported by the filesystem. + stx_mask: u32, + stx_btime: libc::statx_timestamp, + // With statx, we can overcome 32-bit `time_t` too. + #[cfg(target_pointer_width = "32")] + stx_atime: libc::statx_timestamp, + #[cfg(target_pointer_width = "32")] + stx_ctime: libc::statx_timestamp, + #[cfg(target_pointer_width = "32")] + stx_mtime: libc::statx_timestamp, + + } + + // We prefer `statx` on Linux if available, which contains file creation time, + // as well as 64-bit timestamps of all kinds. + // Default `stat64` contains no creation time and may have 32-bit `time_t`. + unsafe fn try_statx( + fd: c_int, + path: *const c_char, + flags: i32, + mask: u32, + ) -> Option> { + use crate::sync::atomic::{AtomicU8, Ordering}; + + // Linux kernel prior to 4.11 or glibc prior to glibc 2.28 don't support `statx` + // We store the availability in global to avoid unnecessary syscalls. + // 0: Unknown + // 1: Not available + // 2: Available + static STATX_STATE: AtomicU8 = AtomicU8::new(0); + syscall! { + fn statx( + fd: c_int, + pathname: *const c_char, + flags: c_int, + mask: libc::c_uint, + statxbuf: *mut libc::statx + ) -> c_int + } + + match STATX_STATE.load(Ordering::Relaxed) { + 0 => { + // It is a trick to call `statx` with null pointers to check if the syscall + // is available. According to the manual, it is expected to fail with EFAULT. + // We do this mainly for performance, since it is nearly hundreds times + // faster than a normal successful call. + let err = cvt(statx(0, ptr::null(), 0, libc::STATX_ALL, ptr::null_mut())) + .err() + .and_then(|e| e.raw_os_error()); + // We don't check `err == Some(libc::ENOSYS)` because the syscall may be limited + // and returns `EPERM`. Listing all possible errors seems not a good idea. + // See: https://github.com/rust-lang/rust/issues/65662 + if err != Some(libc::EFAULT) { + STATX_STATE.store(1, Ordering::Relaxed); + return None; + } + STATX_STATE.store(2, Ordering::Relaxed); + } + 1 => return None, + _ => {} + } + + let mut buf: libc::statx = mem::zeroed(); + if let Err(err) = cvt(statx(fd, path, flags, mask, &mut buf)) { + return Some(Err(err)); + } + + // We cannot fill `stat64` exhaustively because of private padding fields. + let mut stat: stat64 = mem::zeroed(); + // `c_ulong` on gnu-mips, `dev_t` otherwise + stat.st_dev = libc::makedev(buf.stx_dev_major, buf.stx_dev_minor) as _; + stat.st_ino = buf.stx_ino as libc::ino64_t; + stat.st_nlink = buf.stx_nlink as libc::nlink_t; + stat.st_mode = buf.stx_mode as libc::mode_t; + stat.st_uid = buf.stx_uid as libc::uid_t; + stat.st_gid = buf.stx_gid as libc::gid_t; + stat.st_rdev = libc::makedev(buf.stx_rdev_major, buf.stx_rdev_minor) as _; + stat.st_size = buf.stx_size as off64_t; + stat.st_blksize = buf.stx_blksize as libc::blksize_t; + stat.st_blocks = buf.stx_blocks as libc::blkcnt64_t; + stat.st_atime = buf.stx_atime.tv_sec as libc::time_t; + // `i64` on gnu-x86_64-x32, `c_ulong` otherwise. + stat.st_atime_nsec = buf.stx_atime.tv_nsec as _; + stat.st_mtime = buf.stx_mtime.tv_sec as libc::time_t; + stat.st_mtime_nsec = buf.stx_mtime.tv_nsec as _; + stat.st_ctime = buf.stx_ctime.tv_sec as libc::time_t; + stat.st_ctime_nsec = buf.stx_ctime.tv_nsec as _; + + let extra = StatxExtraFields { + stx_mask: buf.stx_mask, + stx_btime: buf.stx_btime, + // Store full times to avoid 32-bit `time_t` truncation. + #[cfg(target_pointer_width = "32")] + stx_atime: buf.stx_atime, + #[cfg(target_pointer_width = "32")] + stx_ctime: buf.stx_ctime, + #[cfg(target_pointer_width = "32")] + stx_mtime: buf.stx_mtime, + }; + + Some(Ok(FileAttr { stat, statx_extra_fields: Some(extra) })) + } + +} else { + #[derive(Clone)] + pub struct FileAttr { + stat: stat64, + } +}} + +// all DirEntry's will have a reference to this struct +struct InnerReadDir { + dirp: Dir, + root: PathBuf, +} + +pub struct ReadDir { + inner: Arc, + #[cfg(not(any( + target_os = "android", + target_os = "linux", + target_os = "solaris", + target_os = "illumos", + target_os = "fuchsia", + target_os = "redox", + )))] + end_of_stream: bool, +} + +struct Dir(*mut libc::DIR); + +unsafe impl Send for Dir {} +unsafe impl Sync for Dir {} + +#[cfg(any( + target_os = "android", + target_os = "linux", + target_os = "solaris", + target_os = "illumos", + target_os = "fuchsia", + target_os = "redox" +))] +pub struct DirEntry { + dir: Arc, + entry: dirent64_min, + // We need to store an owned copy of the entry name on platforms that use + // readdir() (not readdir_r()), because a) struct dirent may use a flexible + // array to store the name, b) it lives only until the next readdir() call. + name: CString, +} + +// Define a minimal subset of fields we need from `dirent64`, especially since +// we're not using the immediate `d_name` on these targets. Keeping this as an +// `entry` field in `DirEntry` helps reduce the `cfg` boilerplate elsewhere. +#[cfg(any( + target_os = "android", + target_os = "linux", + target_os = "solaris", + target_os = "illumos", + target_os = "fuchsia", + target_os = "redox" +))] +struct dirent64_min { + d_ino: u64, + #[cfg(not(any(target_os = "solaris", target_os = "illumos")))] + d_type: u8, +} + +#[cfg(not(any( + target_os = "android", + target_os = "linux", + target_os = "solaris", + target_os = "illumos", + target_os = "fuchsia", + target_os = "redox" +)))] +pub struct DirEntry { + dir: Arc, + // The full entry includes a fixed-length `d_name`. + entry: dirent64, +} + +#[derive(Clone, Debug)] +pub struct OpenOptions { + // generic + read: bool, + write: bool, + append: bool, + truncate: bool, + create: bool, + create_new: bool, + // system-specific + custom_flags: i32, + mode: mode_t, +} + +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct FilePermissions { + mode: mode_t, +} + +#[derive(Copy, Clone)] +pub struct FileTimes([libc::timespec; 2]); + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct FileType { + mode: mode_t, +} + +#[derive(Debug)] +pub struct DirBuilder { + mode: mode_t, +} + +cfg_has_statx! {{ + impl FileAttr { + fn from_stat64(stat: stat64) -> Self { + Self { stat, statx_extra_fields: None } + } + + #[cfg(target_pointer_width = "32")] + pub fn stx_mtime(&self) -> Option<&libc::statx_timestamp> { + if let Some(ext) = &self.statx_extra_fields { + if (ext.stx_mask & libc::STATX_MTIME) != 0 { + return Some(&ext.stx_mtime); + } + } + None + } + + #[cfg(target_pointer_width = "32")] + pub fn stx_atime(&self) -> Option<&libc::statx_timestamp> { + if let Some(ext) = &self.statx_extra_fields { + if (ext.stx_mask & libc::STATX_ATIME) != 0 { + return Some(&ext.stx_atime); + } + } + None + } + + #[cfg(target_pointer_width = "32")] + pub fn stx_ctime(&self) -> Option<&libc::statx_timestamp> { + if let Some(ext) = &self.statx_extra_fields { + if (ext.stx_mask & libc::STATX_CTIME) != 0 { + return Some(&ext.stx_ctime); + } + } + None + } + } +} else { + impl FileAttr { + fn from_stat64(stat: stat64) -> Self { + Self { stat } + } + } +}} + +impl FileAttr { + pub fn size(&self) -> u64 { + self.stat.st_size as u64 + } + pub fn perm(&self) -> FilePermissions { + FilePermissions { mode: (self.stat.st_mode as mode_t) } + } + + pub fn file_type(&self) -> FileType { + FileType { mode: self.stat.st_mode as mode_t } + } +} + +#[cfg(target_os = "netbsd")] +impl FileAttr { + pub fn modified(&self) -> io::Result { + Ok(SystemTime::new(self.stat.st_mtime as i64, self.stat.st_mtimensec as i64)) + } + + pub fn accessed(&self) -> io::Result { + Ok(SystemTime::new(self.stat.st_atime as i64, self.stat.st_atimensec as i64)) + } + + pub fn created(&self) -> io::Result { + Ok(SystemTime::new(self.stat.st_birthtime as i64, self.stat.st_birthtimensec as i64)) + } +} + +#[cfg(not(target_os = "netbsd"))] +impl FileAttr { + #[cfg(not(any(target_os = "vxworks", target_os = "espidf", target_os = "horizon")))] + pub fn modified(&self) -> io::Result { + #[cfg(target_pointer_width = "32")] + cfg_has_statx! { + if let Some(mtime) = self.stx_mtime() { + return Ok(SystemTime::new(mtime.tv_sec, mtime.tv_nsec as i64)); + } + } + + Ok(SystemTime::new(self.stat.st_mtime as i64, self.stat.st_mtime_nsec as i64)) + } + + #[cfg(any(target_os = "vxworks", target_os = "espidf"))] + pub fn modified(&self) -> io::Result { + Ok(SystemTime::new(self.stat.st_mtime as i64, 0)) + } + + #[cfg(target_os = "horizon")] + pub fn modified(&self) -> io::Result { + Ok(SystemTime::from(self.stat.st_mtim)) + } + + #[cfg(not(any(target_os = "vxworks", target_os = "espidf", target_os = "horizon")))] + pub fn accessed(&self) -> io::Result { + #[cfg(target_pointer_width = "32")] + cfg_has_statx! { + if let Some(atime) = self.stx_atime() { + return Ok(SystemTime::new(atime.tv_sec, atime.tv_nsec as i64)); + } + } + + Ok(SystemTime::new(self.stat.st_atime as i64, self.stat.st_atime_nsec as i64)) + } + + #[cfg(any(target_os = "vxworks", target_os = "espidf"))] + pub fn accessed(&self) -> io::Result { + Ok(SystemTime::new(self.stat.st_atime as i64, 0)) + } + + #[cfg(target_os = "horizon")] + pub fn accessed(&self) -> io::Result { + Ok(SystemTime::from(self.stat.st_atim)) + } + + #[cfg(any( + target_os = "freebsd", + target_os = "openbsd", + target_os = "macos", + target_os = "ios", + target_os = "watchos", + ))] + pub fn created(&self) -> io::Result { + Ok(SystemTime::new(self.stat.st_birthtime as i64, self.stat.st_birthtime_nsec as i64)) + } + + #[cfg(not(any( + target_os = "freebsd", + target_os = "openbsd", + target_os = "macos", + target_os = "ios", + target_os = "watchos", + )))] + pub fn created(&self) -> io::Result { + cfg_has_statx! { + if let Some(ext) = &self.statx_extra_fields { + return if (ext.stx_mask & libc::STATX_BTIME) != 0 { + Ok(SystemTime::new(ext.stx_btime.tv_sec, ext.stx_btime.tv_nsec as i64)) + } else { + Err(io::const_io_error!( + io::ErrorKind::Uncategorized, + "creation time is not available for the filesystem", + )) + }; + } + } + + Err(io::const_io_error!( + io::ErrorKind::Unsupported, + "creation time is not available on this platform \ + currently", + )) + } +} + +impl AsInner for FileAttr { + fn as_inner(&self) -> &stat64 { + &self.stat + } +} + +impl FilePermissions { + pub fn readonly(&self) -> bool { + // check if any class (owner, group, others) has write permission + self.mode & 0o222 == 0 + } + + pub fn set_readonly(&mut self, readonly: bool) { + if readonly { + // remove write permission for all classes; equivalent to `chmod a-w ` + self.mode &= !0o222; + } else { + // add write permission for all classes; equivalent to `chmod a+w ` + self.mode |= 0o222; + } + } + pub fn mode(&self) -> u32 { + self.mode as u32 + } +} + +impl FileTimes { + pub fn set_accessed(&mut self, t: SystemTime) { + self.0[0] = t.t.to_timespec().expect("Invalid system time"); + } + + pub fn set_modified(&mut self, t: SystemTime) { + self.0[1] = t.t.to_timespec().expect("Invalid system time"); + } +} + +struct TimespecDebugAdapter<'a>(&'a libc::timespec); + +impl fmt::Debug for TimespecDebugAdapter<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("timespec") + .field("tv_sec", &self.0.tv_sec) + .field("tv_nsec", &self.0.tv_nsec) + .finish() + } +} + +impl fmt::Debug for FileTimes { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("FileTimes") + .field("accessed", &TimespecDebugAdapter(&self.0[0])) + .field("modified", &TimespecDebugAdapter(&self.0[1])) + .finish() + } +} + +impl Default for FileTimes { + fn default() -> Self { + // Redox doesn't appear to support `UTIME_OMIT`, so we stub it out here, and always return + // an error in `set_times`. + // ESP-IDF does not support `futimens` at all and the behavior for that OS is therefore + // the same as for Redox. + #[cfg(any(target_os = "redox", target_os = "espidf"))] + let omit = libc::timespec { tv_sec: 0, tv_nsec: 0 }; + #[cfg(not(any(target_os = "redox", target_os = "espidf")))] + let omit = libc::timespec { tv_sec: 0, tv_nsec: libc::UTIME_OMIT as _ }; + Self([omit; 2]) + } +} + +impl FileType { + pub fn is_dir(&self) -> bool { + self.is(libc::S_IFDIR) + } + pub fn is_file(&self) -> bool { + self.is(libc::S_IFREG) + } + pub fn is_symlink(&self) -> bool { + self.is(libc::S_IFLNK) + } + + pub fn is(&self, mode: mode_t) -> bool { + self.mode & libc::S_IFMT == mode + } +} + +impl FromInner for FilePermissions { + fn from_inner(mode: u32) -> FilePermissions { + FilePermissions { mode: mode as mode_t } + } +} + +impl fmt::Debug for ReadDir { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // This will only be called from std::fs::ReadDir, which will add a "ReadDir()" frame. + // Thus the result will be e g 'ReadDir("/home")' + fmt::Debug::fmt(&*self.inner.root, f) + } +} + +impl Iterator for ReadDir { + type Item = io::Result; + + #[cfg(any( + target_os = "android", + target_os = "linux", + target_os = "solaris", + target_os = "fuchsia", + target_os = "redox", + target_os = "illumos" + ))] + fn next(&mut self) -> Option> { + unsafe { + loop { + // As of POSIX.1-2017, readdir() is not required to be thread safe; only + // readdir_r() is. However, readdir_r() cannot correctly handle platforms + // with unlimited or variable NAME_MAX. Many modern platforms guarantee + // thread safety for readdir() as long an individual DIR* is not accessed + // concurrently, which is sufficient for Rust. + super::os::set_errno(0); + let entry_ptr = readdir64(self.inner.dirp.0); + if entry_ptr.is_null() { + // null can mean either the end is reached or an error occurred. + // So we had to clear errno beforehand to check for an error now. + return match super::os::errno() { + 0 => None, + e => Some(Err(Error::from_raw_os_error(e))), + }; + } + + // Only d_reclen bytes of *entry_ptr are valid, so we can't just copy the + // whole thing (#93384). Instead, copy everything except the name. + let mut copy: dirent64 = mem::zeroed(); + // Can't dereference entry_ptr, so use the local entry to get + // offsetof(struct dirent, d_name) + let copy_bytes = &mut copy as *mut _ as *mut u8; + let copy_name = &mut copy.d_name as *mut _ as *mut u8; + let name_offset = copy_name.offset_from(copy_bytes) as usize; + let entry_bytes = entry_ptr as *const u8; + let entry_name = entry_bytes.add(name_offset); + ptr::copy_nonoverlapping(entry_bytes, copy_bytes, name_offset); + + let entry = dirent64_min { + d_ino: copy.d_ino as u64, + #[cfg(not(any(target_os = "solaris", target_os = "illumos")))] + d_type: copy.d_type as u8, + }; + + let ret = DirEntry { + entry, + // d_name is guaranteed to be null-terminated. + name: CStr::from_ptr(entry_name as *const _).to_owned(), + dir: Arc::clone(&self.inner), + }; + if ret.name_bytes() != b"." && ret.name_bytes() != b".." { + return Some(Ok(ret)); + } + } + } + } + + #[cfg(not(any( + target_os = "android", + target_os = "linux", + target_os = "solaris", + target_os = "fuchsia", + target_os = "redox", + target_os = "illumos" + )))] + fn next(&mut self) -> Option> { + if self.end_of_stream { + return None; + } + + unsafe { + let mut ret = DirEntry { entry: mem::zeroed(), dir: Arc::clone(&self.inner) }; + let mut entry_ptr = ptr::null_mut(); + loop { + let err = readdir64_r(self.inner.dirp.0, &mut ret.entry, &mut entry_ptr); + if err != 0 { + if entry_ptr.is_null() { + // We encountered an error (which will be returned in this iteration), but + // we also reached the end of the directory stream. The `end_of_stream` + // flag is enabled to make sure that we return `None` in the next iteration + // (instead of looping forever) + self.end_of_stream = true; + } + return Some(Err(Error::from_raw_os_error(err))); + } + if entry_ptr.is_null() { + return None; + } + if ret.name_bytes() != b"." && ret.name_bytes() != b".." { + return Some(Ok(ret)); + } + } + } + } +} + +impl Drop for Dir { + fn drop(&mut self) { + let r = unsafe { libc::closedir(self.0) }; + debug_assert_eq!(r, 0); + } +} + +impl DirEntry { + pub fn path(&self) -> PathBuf { + self.dir.root.join(self.file_name_os_str()) + } + + pub fn file_name(&self) -> OsString { + self.file_name_os_str().to_os_string() + } + + #[cfg(any(target_os = "linux", target_os = "emscripten", target_os = "android"))] + pub fn metadata(&self) -> io::Result { + let fd = cvt(unsafe { dirfd(self.dir.dirp.0) })?; + let name = self.name_cstr().as_ptr(); + + cfg_has_statx! { + if let Some(ret) = unsafe { try_statx( + fd, + name, + libc::AT_SYMLINK_NOFOLLOW | libc::AT_STATX_SYNC_AS_STAT, + libc::STATX_ALL, + ) } { + return ret; + } + } + + let mut stat: stat64 = unsafe { mem::zeroed() }; + cvt(unsafe { fstatat64(fd, name, &mut stat, libc::AT_SYMLINK_NOFOLLOW) })?; + Ok(FileAttr::from_stat64(stat)) + } + + #[cfg(not(any(target_os = "linux", target_os = "emscripten", target_os = "android")))] + pub fn metadata(&self) -> io::Result { + lstat(&self.path()) + } + + #[cfg(any( + target_os = "solaris", + target_os = "illumos", + target_os = "haiku", + target_os = "vxworks" + ))] + pub fn file_type(&self) -> io::Result { + self.metadata().map(|m| m.file_type()) + } + + #[cfg(not(any( + target_os = "solaris", + target_os = "illumos", + target_os = "haiku", + target_os = "vxworks" + )))] + pub fn file_type(&self) -> io::Result { + match self.entry.d_type { + libc::DT_CHR => Ok(FileType { mode: libc::S_IFCHR }), + libc::DT_FIFO => Ok(FileType { mode: libc::S_IFIFO }), + libc::DT_LNK => Ok(FileType { mode: libc::S_IFLNK }), + libc::DT_REG => Ok(FileType { mode: libc::S_IFREG }), + libc::DT_SOCK => Ok(FileType { mode: libc::S_IFSOCK }), + libc::DT_DIR => Ok(FileType { mode: libc::S_IFDIR }), + libc::DT_BLK => Ok(FileType { mode: libc::S_IFBLK }), + _ => self.metadata().map(|m| m.file_type()), + } + } + + #[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "watchos", + target_os = "linux", + target_os = "emscripten", + target_os = "android", + target_os = "solaris", + target_os = "illumos", + target_os = "haiku", + target_os = "l4re", + target_os = "fuchsia", + target_os = "redox", + target_os = "vxworks", + target_os = "espidf", + target_os = "horizon" + ))] + pub fn ino(&self) -> u64 { + self.entry.d_ino as u64 + } + + #[cfg(any( + target_os = "freebsd", + target_os = "openbsd", + target_os = "netbsd", + target_os = "dragonfly" + ))] + pub fn ino(&self) -> u64 { + self.entry.d_fileno as u64 + } + + #[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "watchos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "freebsd", + target_os = "dragonfly" + ))] + fn name_bytes(&self) -> &[u8] { + use crate::slice; + unsafe { + slice::from_raw_parts( + self.entry.d_name.as_ptr() as *const u8, + self.entry.d_namlen as usize, + ) + } + } + #[cfg(not(any( + target_os = "macos", + target_os = "ios", + target_os = "watchos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "freebsd", + target_os = "dragonfly" + )))] + fn name_bytes(&self) -> &[u8] { + self.name_cstr().to_bytes() + } + + #[cfg(not(any( + target_os = "android", + target_os = "linux", + target_os = "solaris", + target_os = "illumos", + target_os = "fuchsia", + target_os = "redox" + )))] + fn name_cstr(&self) -> &CStr { + unsafe { CStr::from_ptr(self.entry.d_name.as_ptr()) } + } + #[cfg(any( + target_os = "android", + target_os = "linux", + target_os = "solaris", + target_os = "illumos", + target_os = "fuchsia", + target_os = "redox" + ))] + fn name_cstr(&self) -> &CStr { + &self.name + } + + pub fn file_name_os_str(&self) -> &OsStr { + OsStr::from_bytes(self.name_bytes()) + } +} + +impl OpenOptions { + pub fn new() -> OpenOptions { + OpenOptions { + // generic + read: false, + write: false, + append: false, + truncate: false, + create: false, + create_new: false, + // system-specific + custom_flags: 0, + mode: 0o666, + } + } + + pub fn read(&mut self, read: bool) { + self.read = read; + } + pub fn write(&mut self, write: bool) { + self.write = write; + } + pub fn append(&mut self, append: bool) { + self.append = append; + } + pub fn truncate(&mut self, truncate: bool) { + self.truncate = truncate; + } + pub fn create(&mut self, create: bool) { + self.create = create; + } + pub fn create_new(&mut self, create_new: bool) { + self.create_new = create_new; + } + + pub fn custom_flags(&mut self, flags: i32) { + self.custom_flags = flags; + } + pub fn mode(&mut self, mode: u32) { + self.mode = mode as mode_t; + } + + fn get_access_mode(&self) -> io::Result { + match (self.read, self.write, self.append) { + (true, false, false) => Ok(libc::O_RDONLY), + (false, true, false) => Ok(libc::O_WRONLY), + (true, true, false) => Ok(libc::O_RDWR), + (false, _, true) => Ok(libc::O_WRONLY | libc::O_APPEND), + (true, _, true) => Ok(libc::O_RDWR | libc::O_APPEND), + (false, false, false) => Err(Error::from_raw_os_error(libc::EINVAL)), + } + } + + fn get_creation_mode(&self) -> io::Result { + match (self.write, self.append) { + (true, false) => {} + (false, false) => { + if self.truncate || self.create || self.create_new { + return Err(Error::from_raw_os_error(libc::EINVAL)); + } + } + (_, true) => { + if self.truncate && !self.create_new { + return Err(Error::from_raw_os_error(libc::EINVAL)); + } + } + } + + Ok(match (self.create, self.truncate, self.create_new) { + (false, false, false) => 0, + (true, false, false) => libc::O_CREAT, + (false, true, false) => libc::O_TRUNC, + (true, true, false) => libc::O_CREAT | libc::O_TRUNC, + (_, _, true) => libc::O_CREAT | libc::O_EXCL, + }) + } +} + +impl File { + pub fn open(path: &Path, opts: &OpenOptions) -> io::Result { + let path = cstr(path)?; + File::open_c(&path, opts) + } + + pub fn open_c(path: &CStr, opts: &OpenOptions) -> io::Result { + let flags = libc::O_CLOEXEC + | opts.get_access_mode()? + | opts.get_creation_mode()? + | (opts.custom_flags as c_int & !libc::O_ACCMODE); + // The third argument of `open64` is documented to have type `mode_t`. On + // some platforms (like macOS, where `open64` is actually `open`), `mode_t` is `u16`. + // However, since this is a variadic function, C integer promotion rules mean that on + // the ABI level, this still gets passed as `c_int` (aka `u32` on Unix platforms). + let fd = cvt_r(|| unsafe { open64(path.as_ptr(), flags, opts.mode as c_int) })?; + Ok(File(unsafe { FileDesc::from_raw_fd(fd) })) + } + + pub fn file_attr(&self) -> io::Result { + let fd = self.as_raw_fd(); + + cfg_has_statx! { + if let Some(ret) = unsafe { try_statx( + fd, + b"\0" as *const _ as *const c_char, + libc::AT_EMPTY_PATH | libc::AT_STATX_SYNC_AS_STAT, + libc::STATX_ALL, + ) } { + return ret; + } + } + + let mut stat: stat64 = unsafe { mem::zeroed() }; + cvt(unsafe { fstat64(fd, &mut stat) })?; + Ok(FileAttr::from_stat64(stat)) + } + + pub fn fsync(&self) -> io::Result<()> { + cvt_r(|| unsafe { os_fsync(self.as_raw_fd()) })?; + return Ok(()); + + #[cfg(any(target_os = "macos", target_os = "ios", target_os = "watchos"))] + unsafe fn os_fsync(fd: c_int) -> c_int { + libc::fcntl(fd, libc::F_FULLFSYNC) + } + #[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "watchos")))] + unsafe fn os_fsync(fd: c_int) -> c_int { + libc::fsync(fd) + } + } + + pub fn datasync(&self) -> io::Result<()> { + cvt_r(|| unsafe { os_datasync(self.as_raw_fd()) })?; + return Ok(()); + + #[cfg(any(target_os = "macos", target_os = "ios", target_os = "watchos"))] + unsafe fn os_datasync(fd: c_int) -> c_int { + libc::fcntl(fd, libc::F_FULLFSYNC) + } + #[cfg(any( + target_os = "freebsd", + target_os = "linux", + target_os = "android", + target_os = "netbsd", + target_os = "openbsd" + ))] + unsafe fn os_datasync(fd: c_int) -> c_int { + libc::fdatasync(fd) + } + #[cfg(not(any( + target_os = "android", + target_os = "freebsd", + target_os = "ios", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "watchos", + )))] + unsafe fn os_datasync(fd: c_int) -> c_int { + libc::fsync(fd) + } + } + + pub fn truncate(&self, size: u64) -> io::Result<()> { + let size: off64_t = + size.try_into().map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?; + cvt_r(|| unsafe { ftruncate64(self.as_raw_fd(), size) }).map(drop) + } + + pub fn read(&self, buf: &mut [u8]) -> io::Result { + self.0.read(buf) + } + + pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { + self.0.read_vectored(bufs) + } + + #[inline] + pub fn is_read_vectored(&self) -> bool { + self.0.is_read_vectored() + } + + pub fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result { + self.0.read_at(buf, offset) + } + + pub fn read_buf(&self, buf: &mut ReadBuf<'_>) -> io::Result<()> { + self.0.read_buf(buf) + } + + pub fn write(&self, buf: &[u8]) -> io::Result { + self.0.write(buf) + } + + pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result { + self.0.write_vectored(bufs) + } + + #[inline] + pub fn is_write_vectored(&self) -> bool { + self.0.is_write_vectored() + } + + pub fn write_at(&self, buf: &[u8], offset: u64) -> io::Result { + self.0.write_at(buf, offset) + } + + pub fn flush(&self) -> io::Result<()> { + Ok(()) + } + + pub fn seek(&self, pos: SeekFrom) -> io::Result { + let (whence, pos) = match pos { + // Casting to `i64` is fine, too large values will end up as + // negative which will cause an error in `lseek64`. + SeekFrom::Start(off) => (libc::SEEK_SET, off as i64), + SeekFrom::End(off) => (libc::SEEK_END, off), + SeekFrom::Current(off) => (libc::SEEK_CUR, off), + }; + let n = cvt(unsafe { lseek64(self.as_raw_fd(), pos as off64_t, whence) })?; + Ok(n as u64) + } + + pub fn duplicate(&self) -> io::Result { + self.0.duplicate().map(File) + } + + pub fn set_permissions(&self, perm: FilePermissions) -> io::Result<()> { + cvt_r(|| unsafe { libc::fchmod(self.as_raw_fd(), perm.mode) })?; + Ok(()) + } + + pub fn set_times(&self, times: FileTimes) -> io::Result<()> { + cfg_if::cfg_if! { + if #[cfg(any(target_os = "redox", target_os = "espidf"))] { + // Redox doesn't appear to support `UTIME_OMIT`. + // ESP-IDF does not support `futimens` at all and the behavior for that OS is therefore + // the same as for Redox. + drop(times); + Err(io::const_io_error!( + io::ErrorKind::Unsupported, + "setting file times not supported", + )) + } else if #[cfg(any(target_os = "android", target_os = "macos"))] { + // futimens requires macOS 10.13, and Android API level 19 + cvt(unsafe { + weak!(fn futimens(c_int, *const libc::timespec) -> c_int); + match futimens.get() { + Some(futimens) => futimens(self.as_raw_fd(), times.0.as_ptr()), + #[cfg(target_os = "macos")] + None => { + fn ts_to_tv(ts: &libc::timespec) -> libc::timeval { + libc::timeval { + tv_sec: ts.tv_sec, + tv_usec: (ts.tv_nsec / 1000) as _ + } + } + let timevals = [ts_to_tv(×.0[0]), ts_to_tv(×.0[1])]; + libc::futimes(self.as_raw_fd(), timevals.as_ptr()) + } + // futimes requires even newer Android. + #[cfg(target_os = "android")] + None => return Err(io::const_io_error!( + io::ErrorKind::Unsupported, + "setting file times requires Android API level >= 19", + )), + } + })?; + Ok(()) + } else { + cvt(unsafe { libc::futimens(self.as_raw_fd(), times.0.as_ptr()) })?; + Ok(()) + } + } + } +} + +impl DirBuilder { + pub fn new() -> DirBuilder { + DirBuilder { mode: 0o777 } + } + + pub fn mkdir(&self, p: &Path) -> io::Result<()> { + let p = cstr(p)?; + cvt(unsafe { libc::mkdir(p.as_ptr(), self.mode) })?; + Ok(()) + } + + pub fn set_mode(&mut self, mode: u32) { + self.mode = mode as mode_t; + } +} + +fn cstr(path: &Path) -> io::Result { + Ok(CString::new(path.as_os_str().as_bytes())?) +} + +impl AsInner for File { + fn as_inner(&self) -> &FileDesc { + &self.0 + } +} + +impl AsInnerMut for File { + fn as_inner_mut(&mut self) -> &mut FileDesc { + &mut self.0 + } +} + +impl IntoInner for File { + fn into_inner(self) -> FileDesc { + self.0 + } +} + +impl FromInner for File { + fn from_inner(file_desc: FileDesc) -> Self { + Self(file_desc) + } +} + +impl AsFd for File { + fn as_fd(&self) -> BorrowedFd<'_> { + self.0.as_fd() + } +} + +impl AsRawFd for File { + fn as_raw_fd(&self) -> RawFd { + self.0.as_raw_fd() + } +} + +impl IntoRawFd for File { + fn into_raw_fd(self) -> RawFd { + self.0.into_raw_fd() + } +} + +impl FromRawFd for File { + unsafe fn from_raw_fd(raw_fd: RawFd) -> Self { + Self(FromRawFd::from_raw_fd(raw_fd)) + } +} + +impl fmt::Debug for File { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + #[cfg(any(target_os = "linux", target_os = "netbsd"))] + fn get_path(fd: c_int) -> Option { + let mut p = PathBuf::from("/proc/self/fd"); + p.push(&fd.to_string()); + readlink(&p).ok() + } + + #[cfg(target_os = "macos")] + fn get_path(fd: c_int) -> Option { + // FIXME: The use of PATH_MAX is generally not encouraged, but it + // is inevitable in this case because macOS defines `fcntl` with + // `F_GETPATH` in terms of `MAXPATHLEN`, and there are no + // alternatives. If a better method is invented, it should be used + // instead. + let mut buf = vec![0; libc::PATH_MAX as usize]; + let n = unsafe { libc::fcntl(fd, libc::F_GETPATH, buf.as_ptr()) }; + if n == -1 { + return None; + } + let l = buf.iter().position(|&c| c == 0).unwrap(); + buf.truncate(l as usize); + buf.shrink_to_fit(); + Some(PathBuf::from(OsString::from_vec(buf))) + } + + #[cfg(all(target_os = "freebsd", target_arch = "x86_64"))] + fn get_path(fd: c_int) -> Option { + let info = Box::::new_zeroed(); + let mut info = unsafe { info.assume_init() }; + info.kf_structsize = mem::size_of::() as libc::c_int; + let n = unsafe { libc::fcntl(fd, libc::F_KINFO, &mut *info) }; + if n == -1 { + return None; + } + let buf = unsafe { CStr::from_ptr(info.kf_path.as_mut_ptr()).to_bytes().to_vec() }; + Some(PathBuf::from(OsString::from_vec(buf))) + } + + #[cfg(target_os = "vxworks")] + fn get_path(fd: c_int) -> Option { + let mut buf = vec![0; libc::PATH_MAX as usize]; + let n = unsafe { libc::ioctl(fd, libc::FIOGETNAME, buf.as_ptr()) }; + if n == -1 { + return None; + } + let l = buf.iter().position(|&c| c == 0).unwrap(); + buf.truncate(l as usize); + Some(PathBuf::from(OsString::from_vec(buf))) + } + + #[cfg(not(any( + target_os = "linux", + target_os = "macos", + target_os = "vxworks", + all(target_os = "freebsd", target_arch = "x86_64"), + target_os = "netbsd" + )))] + fn get_path(_fd: c_int) -> Option { + // FIXME(#24570): implement this for other Unix platforms + None + } + + #[cfg(any(target_os = "linux", target_os = "macos", target_os = "vxworks"))] + fn get_mode(fd: c_int) -> Option<(bool, bool)> { + let mode = unsafe { libc::fcntl(fd, libc::F_GETFL) }; + if mode == -1 { + return None; + } + match mode & libc::O_ACCMODE { + libc::O_RDONLY => Some((true, false)), + libc::O_RDWR => Some((true, true)), + libc::O_WRONLY => Some((false, true)), + _ => None, + } + } + + #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "vxworks")))] + fn get_mode(_fd: c_int) -> Option<(bool, bool)> { + // FIXME(#24570): implement this for other Unix platforms + None + } + + let fd = self.as_raw_fd(); + let mut b = f.debug_struct("File"); + b.field("fd", &fd); + if let Some(path) = get_path(fd) { + b.field("path", &path); + } + if let Some((read, write)) = get_mode(fd) { + b.field("read", &read).field("write", &write); + } + b.finish() + } +} + +pub fn readdir(p: &Path) -> io::Result { + let root = p.to_path_buf(); + let p = cstr(p)?; + unsafe { + let ptr = libc::opendir(p.as_ptr()); + if ptr.is_null() { + Err(Error::last_os_error()) + } else { + let inner = InnerReadDir { dirp: Dir(ptr), root }; + Ok(ReadDir { + inner: Arc::new(inner), + #[cfg(not(any( + target_os = "android", + target_os = "linux", + target_os = "solaris", + target_os = "illumos", + target_os = "fuchsia", + target_os = "redox", + )))] + end_of_stream: false, + }) + } + } +} + +pub fn unlink(p: &Path) -> io::Result<()> { + let p = cstr(p)?; + cvt(unsafe { libc::unlink(p.as_ptr()) })?; + Ok(()) +} + +pub fn rename(old: &Path, new: &Path) -> io::Result<()> { + let old = cstr(old)?; + let new = cstr(new)?; + cvt(unsafe { libc::rename(old.as_ptr(), new.as_ptr()) })?; + Ok(()) +} + +pub fn set_perm(p: &Path, perm: FilePermissions) -> io::Result<()> { + let p = cstr(p)?; + cvt_r(|| unsafe { libc::chmod(p.as_ptr(), perm.mode) })?; + Ok(()) +} + +pub fn rmdir(p: &Path) -> io::Result<()> { + let p = cstr(p)?; + cvt(unsafe { libc::rmdir(p.as_ptr()) })?; + Ok(()) +} + +pub fn readlink(p: &Path) -> io::Result { + let c_path = cstr(p)?; + let p = c_path.as_ptr(); + + let mut buf = Vec::with_capacity(256); + + loop { + let buf_read = + cvt(unsafe { libc::readlink(p, buf.as_mut_ptr() as *mut _, buf.capacity()) })? as usize; + + unsafe { + buf.set_len(buf_read); + } + + if buf_read != buf.capacity() { + buf.shrink_to_fit(); + + return Ok(PathBuf::from(OsString::from_vec(buf))); + } + + // Trigger the internal buffer resizing logic of `Vec` by requiring + // more space than the current capacity. The length is guaranteed to be + // the same as the capacity due to the if statement above. + buf.reserve(1); + } +} + +pub fn symlink(original: &Path, link: &Path) -> io::Result<()> { + let original = cstr(original)?; + let link = cstr(link)?; + cvt(unsafe { libc::symlink(original.as_ptr(), link.as_ptr()) })?; + Ok(()) +} + +pub fn link(original: &Path, link: &Path) -> io::Result<()> { + let original = cstr(original)?; + let link = cstr(link)?; + cfg_if::cfg_if! { + if #[cfg(any(target_os = "vxworks", target_os = "redox", target_os = "android", target_os = "espidf", target_os = "horizon"))] { + // VxWorks, Redox and ESP-IDF lack `linkat`, so use `link` instead. POSIX leaves + // it implementation-defined whether `link` follows symlinks, so rely on the + // `symlink_hard_link` test in library/std/src/fs/tests.rs to check the behavior. + // Android has `linkat` on newer versions, but we happen to know `link` + // always has the correct behavior, so it's here as well. + cvt(unsafe { libc::link(original.as_ptr(), link.as_ptr()) })?; + } else if #[cfg(target_os = "macos")] { + // On MacOS, older versions (<=10.9) lack support for linkat while newer + // versions have it. We want to use linkat if it is available, so we use weak! + // to check. `linkat` is preferable to `link` because it gives us a flag to + // specify how symlinks should be handled. We pass 0 as the flags argument, + // meaning it shouldn't follow symlinks. + weak!(fn linkat(c_int, *const c_char, c_int, *const c_char, c_int) -> c_int); + + if let Some(f) = linkat.get() { + cvt(unsafe { f(libc::AT_FDCWD, original.as_ptr(), libc::AT_FDCWD, link.as_ptr(), 0) })?; + } else { + cvt(unsafe { libc::link(original.as_ptr(), link.as_ptr()) })?; + }; + } else { + // Where we can, use `linkat` instead of `link`; see the comment above + // this one for details on why. + cvt(unsafe { libc::linkat(libc::AT_FDCWD, original.as_ptr(), libc::AT_FDCWD, link.as_ptr(), 0) })?; + } + } + Ok(()) +} + +pub fn stat(p: &Path) -> io::Result { + let p = cstr(p)?; + + cfg_has_statx! { + if let Some(ret) = unsafe { try_statx( + libc::AT_FDCWD, + p.as_ptr(), + libc::AT_STATX_SYNC_AS_STAT, + libc::STATX_ALL, + ) } { + return ret; + } + } + + let mut stat: stat64 = unsafe { mem::zeroed() }; + cvt(unsafe { stat64(p.as_ptr(), &mut stat) })?; + Ok(FileAttr::from_stat64(stat)) +} + +pub fn lstat(p: &Path) -> io::Result { + let p = cstr(p)?; + + cfg_has_statx! { + if let Some(ret) = unsafe { try_statx( + libc::AT_FDCWD, + p.as_ptr(), + libc::AT_SYMLINK_NOFOLLOW | libc::AT_STATX_SYNC_AS_STAT, + libc::STATX_ALL, + ) } { + return ret; + } + } + + let mut stat: stat64 = unsafe { mem::zeroed() }; + cvt(unsafe { lstat64(p.as_ptr(), &mut stat) })?; + Ok(FileAttr::from_stat64(stat)) +} + +pub fn canonicalize(p: &Path) -> io::Result { + let path = CString::new(p.as_os_str().as_bytes())?; + let buf; + unsafe { + let r = libc::realpath(path.as_ptr(), ptr::null_mut()); + if r.is_null() { + return Err(io::Error::last_os_error()); + } + buf = CStr::from_ptr(r).to_bytes().to_vec(); + libc::free(r as *mut _); + } + Ok(PathBuf::from(OsString::from_vec(buf))) +} + +fn open_from(from: &Path) -> io::Result<(crate::fs::File, crate::fs::Metadata)> { + use crate::fs::File; + use crate::sys_common::fs::NOT_FILE_ERROR; + + let reader = File::open(from)?; + let metadata = reader.metadata()?; + if !metadata.is_file() { + return Err(NOT_FILE_ERROR); + } + Ok((reader, metadata)) +} + +#[cfg(target_os = "espidf")] +fn open_to_and_set_permissions( + to: &Path, + reader_metadata: crate::fs::Metadata, +) -> io::Result<(crate::fs::File, crate::fs::Metadata)> { + use crate::fs::OpenOptions; + let writer = OpenOptions::new().open(to)?; + let writer_metadata = writer.metadata()?; + Ok((writer, writer_metadata)) +} + +#[cfg(not(target_os = "espidf"))] +fn open_to_and_set_permissions( + to: &Path, + reader_metadata: crate::fs::Metadata, +) -> io::Result<(crate::fs::File, crate::fs::Metadata)> { + use crate::fs::OpenOptions; + use crate::os::unix::fs::{OpenOptionsExt, PermissionsExt}; + + let perm = reader_metadata.permissions(); + let writer = OpenOptions::new() + // create the file with the correct mode right away + .mode(perm.mode()) + .write(true) + .create(true) + .truncate(true) + .open(to)?; + let writer_metadata = writer.metadata()?; + if writer_metadata.is_file() { + // Set the correct file permissions, in case the file already existed. + // Don't set the permissions on already existing non-files like + // pipes/FIFOs or device nodes. + writer.set_permissions(perm)?; + } + Ok((writer, writer_metadata)) +} + +#[cfg(not(any( + target_os = "linux", + target_os = "android", + target_os = "macos", + target_os = "ios", + target_os = "watchos", +)))] +pub fn copy(from: &Path, to: &Path) -> io::Result { + let (mut reader, reader_metadata) = open_from(from)?; + let (mut writer, _) = open_to_and_set_permissions(to, reader_metadata)?; + + io::copy(&mut reader, &mut writer) +} + +#[cfg(any(target_os = "linux", target_os = "android"))] +pub fn copy(from: &Path, to: &Path) -> io::Result { + let (mut reader, reader_metadata) = open_from(from)?; + let max_len = u64::MAX; + let (mut writer, _) = open_to_and_set_permissions(to, reader_metadata)?; + + use super::kernel_copy::{copy_regular_files, CopyResult}; + + match copy_regular_files(reader.as_raw_fd(), writer.as_raw_fd(), max_len) { + CopyResult::Ended(bytes) => Ok(bytes), + CopyResult::Error(e, _) => Err(e), + CopyResult::Fallback(written) => match io::copy::generic_copy(&mut reader, &mut writer) { + Ok(bytes) => Ok(bytes + written), + Err(e) => Err(e), + }, + } +} + +#[cfg(any(target_os = "macos", target_os = "ios", target_os = "watchos"))] +pub fn copy(from: &Path, to: &Path) -> io::Result { + use crate::sync::atomic::{AtomicBool, Ordering}; + + const COPYFILE_ACL: u32 = 1 << 0; + const COPYFILE_STAT: u32 = 1 << 1; + const COPYFILE_XATTR: u32 = 1 << 2; + const COPYFILE_DATA: u32 = 1 << 3; + + const COPYFILE_SECURITY: u32 = COPYFILE_STAT | COPYFILE_ACL; + const COPYFILE_METADATA: u32 = COPYFILE_SECURITY | COPYFILE_XATTR; + const COPYFILE_ALL: u32 = COPYFILE_METADATA | COPYFILE_DATA; + + const COPYFILE_STATE_COPIED: u32 = 8; + + #[allow(non_camel_case_types)] + type copyfile_state_t = *mut libc::c_void; + #[allow(non_camel_case_types)] + type copyfile_flags_t = u32; + + extern "C" { + fn fcopyfile( + from: libc::c_int, + to: libc::c_int, + state: copyfile_state_t, + flags: copyfile_flags_t, + ) -> libc::c_int; + fn copyfile_state_alloc() -> copyfile_state_t; + fn copyfile_state_free(state: copyfile_state_t) -> libc::c_int; + fn copyfile_state_get( + state: copyfile_state_t, + flag: u32, + dst: *mut libc::c_void, + ) -> libc::c_int; + } + + struct FreeOnDrop(copyfile_state_t); + impl Drop for FreeOnDrop { + fn drop(&mut self) { + // The code below ensures that `FreeOnDrop` is never a null pointer + unsafe { + // `copyfile_state_free` returns -1 if the `to` or `from` files + // cannot be closed. However, this is not considered this an + // error. + copyfile_state_free(self.0); + } + } + } + + // MacOS prior to 10.12 don't support `fclonefileat` + // We store the availability in a global to avoid unnecessary syscalls + static HAS_FCLONEFILEAT: AtomicBool = AtomicBool::new(true); + syscall! { + fn fclonefileat( + srcfd: libc::c_int, + dst_dirfd: libc::c_int, + dst: *const c_char, + flags: libc::c_int + ) -> libc::c_int + } + + let (reader, reader_metadata) = open_from(from)?; + + // Opportunistically attempt to create a copy-on-write clone of `from` + // using `fclonefileat`. + if HAS_FCLONEFILEAT.load(Ordering::Relaxed) { + let to = cstr(to)?; + let clonefile_result = + cvt(unsafe { fclonefileat(reader.as_raw_fd(), libc::AT_FDCWD, to.as_ptr(), 0) }); + match clonefile_result { + Ok(_) => return Ok(reader_metadata.len()), + Err(err) => match err.raw_os_error() { + // `fclonefileat` will fail on non-APFS volumes, if the + // destination already exists, or if the source and destination + // are on different devices. In all these cases `fcopyfile` + // should succeed. + Some(libc::ENOTSUP) | Some(libc::EEXIST) | Some(libc::EXDEV) => (), + Some(libc::ENOSYS) => HAS_FCLONEFILEAT.store(false, Ordering::Relaxed), + _ => return Err(err), + }, + } + } + + // Fall back to using `fcopyfile` if `fclonefileat` does not succeed. + let (writer, writer_metadata) = open_to_and_set_permissions(to, reader_metadata)?; + + // We ensure that `FreeOnDrop` never contains a null pointer so it is + // always safe to call `copyfile_state_free` + let state = unsafe { + let state = copyfile_state_alloc(); + if state.is_null() { + return Err(crate::io::Error::last_os_error()); + } + FreeOnDrop(state) + }; + + let flags = if writer_metadata.is_file() { COPYFILE_ALL } else { COPYFILE_DATA }; + + cvt(unsafe { fcopyfile(reader.as_raw_fd(), writer.as_raw_fd(), state.0, flags) })?; + + let mut bytes_copied: libc::off_t = 0; + cvt(unsafe { + copyfile_state_get( + state.0, + COPYFILE_STATE_COPIED, + &mut bytes_copied as *mut libc::off_t as *mut libc::c_void, + ) + })?; + Ok(bytes_copied as u64) +} + +pub fn chown(path: &Path, uid: u32, gid: u32) -> io::Result<()> { + let path = cstr(path)?; + cvt(unsafe { libc::chown(path.as_ptr(), uid as libc::uid_t, gid as libc::gid_t) })?; + Ok(()) +} + +pub fn fchown(fd: c_int, uid: u32, gid: u32) -> io::Result<()> { + cvt(unsafe { libc::fchown(fd, uid as libc::uid_t, gid as libc::gid_t) })?; + Ok(()) +} + +pub fn lchown(path: &Path, uid: u32, gid: u32) -> io::Result<()> { + let path = cstr(path)?; + cvt(unsafe { libc::lchown(path.as_ptr(), uid as libc::uid_t, gid as libc::gid_t) })?; + Ok(()) +} + +#[cfg(not(any(target_os = "fuchsia", target_os = "vxworks")))] +pub fn chroot(dir: &Path) -> io::Result<()> { + let dir = cstr(dir)?; + cvt(unsafe { libc::chroot(dir.as_ptr()) })?; + Ok(()) +} + +pub use remove_dir_impl::remove_dir_all; + +// Fallback for REDOX, ESP-ID, Horizon, and Miri +#[cfg(any(target_os = "redox", target_os = "espidf", target_os = "horizon", miri))] +mod remove_dir_impl { + pub use crate::sys_common::fs::remove_dir_all; +} + +// Modern implementation using openat(), unlinkat() and fdopendir() +#[cfg(not(any(target_os = "redox", target_os = "espidf", target_os = "horizon", miri)))] +mod remove_dir_impl { + use super::{cstr, lstat, Dir, DirEntry, InnerReadDir, ReadDir}; + use crate::ffi::CStr; + use crate::io; + use crate::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd}; + use crate::os::unix::prelude::{OwnedFd, RawFd}; + use crate::path::{Path, PathBuf}; + use crate::sync::Arc; + use crate::sys::{cvt, cvt_r}; + + #[cfg(not(all(target_os = "macos", not(target_arch = "aarch64")),))] + use libc::{fdopendir, openat, unlinkat}; + #[cfg(all(target_os = "macos", not(target_arch = "aarch64")))] + use macos_weak::{fdopendir, openat, unlinkat}; + + #[cfg(all(target_os = "macos", not(target_arch = "aarch64")))] + mod macos_weak { + use crate::sys::weak::weak; + use libc::{c_char, c_int, DIR}; + + fn get_openat_fn() -> Option c_int> { + weak!(fn openat(c_int, *const c_char, c_int) -> c_int); + openat.get() + } + + pub fn has_openat() -> bool { + get_openat_fn().is_some() + } + + pub unsafe fn openat(dirfd: c_int, pathname: *const c_char, flags: c_int) -> c_int { + get_openat_fn().map(|openat| openat(dirfd, pathname, flags)).unwrap_or_else(|| { + crate::sys::unix::os::set_errno(libc::ENOSYS); + -1 + }) + } + + pub unsafe fn fdopendir(fd: c_int) -> *mut DIR { + #[cfg(all(target_os = "macos", target_arch = "x86"))] + weak!(fn fdopendir(c_int) -> *mut DIR, "fdopendir$INODE64$UNIX2003"); + #[cfg(all(target_os = "macos", target_arch = "x86_64"))] + weak!(fn fdopendir(c_int) -> *mut DIR, "fdopendir$INODE64"); + fdopendir.get().map(|fdopendir| fdopendir(fd)).unwrap_or_else(|| { + crate::sys::unix::os::set_errno(libc::ENOSYS); + crate::ptr::null_mut() + }) + } + + pub unsafe fn unlinkat(dirfd: c_int, pathname: *const c_char, flags: c_int) -> c_int { + weak!(fn unlinkat(c_int, *const c_char, c_int) -> c_int); + unlinkat.get().map(|unlinkat| unlinkat(dirfd, pathname, flags)).unwrap_or_else(|| { + crate::sys::unix::os::set_errno(libc::ENOSYS); + -1 + }) + } + } + + pub fn openat_nofollow_dironly(parent_fd: Option, p: &CStr) -> io::Result { + let fd = cvt_r(|| unsafe { + openat( + parent_fd.unwrap_or(libc::AT_FDCWD), + p.as_ptr(), + libc::O_CLOEXEC | libc::O_RDONLY | libc::O_NOFOLLOW | libc::O_DIRECTORY, + ) + })?; + Ok(unsafe { OwnedFd::from_raw_fd(fd) }) + } + + fn fdreaddir(dir_fd: OwnedFd) -> io::Result<(ReadDir, RawFd)> { + let ptr = unsafe { fdopendir(dir_fd.as_raw_fd()) }; + if ptr.is_null() { + return Err(io::Error::last_os_error()); + } + let dirp = Dir(ptr); + // file descriptor is automatically closed by libc::closedir() now, so give up ownership + let new_parent_fd = dir_fd.into_raw_fd(); + // a valid root is not needed because we do not call any functions involving the full path + // of the DirEntrys. + let dummy_root = PathBuf::new(); + Ok(( + ReadDir { + inner: Arc::new(InnerReadDir { dirp, root: dummy_root }), + #[cfg(not(any( + target_os = "android", + target_os = "linux", + target_os = "solaris", + target_os = "illumos", + target_os = "fuchsia", + target_os = "redox", + )))] + end_of_stream: false, + }, + new_parent_fd, + )) + } + + #[cfg(any( + target_os = "solaris", + target_os = "illumos", + target_os = "haiku", + target_os = "vxworks", + ))] + fn is_dir(_ent: &DirEntry) -> Option { + None + } + + #[cfg(not(any( + target_os = "solaris", + target_os = "illumos", + target_os = "haiku", + target_os = "vxworks", + )))] + fn is_dir(ent: &DirEntry) -> Option { + match ent.entry.d_type { + libc::DT_UNKNOWN => None, + libc::DT_DIR => Some(true), + _ => Some(false), + } + } + + fn remove_dir_all_recursive(parent_fd: Option, path: &CStr) -> io::Result<()> { + // try opening as directory + let fd = match openat_nofollow_dironly(parent_fd, &path) { + Err(err) if matches!(err.raw_os_error(), Some(libc::ENOTDIR | libc::ELOOP)) => { + // not a directory - don't traverse further + // (for symlinks, older Linux kernels may return ELOOP instead of ENOTDIR) + return match parent_fd { + // unlink... + Some(parent_fd) => { + cvt(unsafe { unlinkat(parent_fd, path.as_ptr(), 0) }).map(drop) + } + // ...unless this was supposed to be the deletion root directory + None => Err(err), + }; + } + result => result?, + }; + + // open the directory passing ownership of the fd + let (dir, fd) = fdreaddir(fd)?; + for child in dir { + let child = child?; + let child_name = child.name_cstr(); + match is_dir(&child) { + Some(true) => { + remove_dir_all_recursive(Some(fd), child_name)?; + } + Some(false) => { + cvt(unsafe { unlinkat(fd, child_name.as_ptr(), 0) })?; + } + None => { + // POSIX specifies that calling unlink()/unlinkat(..., 0) on a directory can succeed + // if the process has the appropriate privileges. This however can causing orphaned + // directories requiring an fsck e.g. on Solaris and Illumos. So we try recursing + // into it first instead of trying to unlink() it. + remove_dir_all_recursive(Some(fd), child_name)?; + } + } + } + + // unlink the directory after removing its contents + cvt(unsafe { + unlinkat(parent_fd.unwrap_or(libc::AT_FDCWD), path.as_ptr(), libc::AT_REMOVEDIR) + })?; + Ok(()) + } + + fn remove_dir_all_modern(p: &Path) -> io::Result<()> { + // We cannot just call remove_dir_all_recursive() here because that would not delete a passed + // symlink. No need to worry about races, because remove_dir_all_recursive() does not recurse + // into symlinks. + let attr = lstat(p)?; + if attr.file_type().is_symlink() { + crate::fs::remove_file(p) + } else { + remove_dir_all_recursive(None, &cstr(p)?) + } + } + + #[cfg(not(all(target_os = "macos", not(target_arch = "aarch64"))))] + pub fn remove_dir_all(p: &Path) -> io::Result<()> { + remove_dir_all_modern(p) + } + + #[cfg(all(target_os = "macos", not(target_arch = "aarch64")))] + pub fn remove_dir_all(p: &Path) -> io::Result<()> { + if macos_weak::has_openat() { + // openat() is available with macOS 10.10+, just like unlinkat() and fdopendir() + remove_dir_all_modern(p) + } else { + // fall back to classic implementation + crate::sys_common::fs::remove_dir_all(p) + } + } +} -- cgit v1.2.3