From f2a90174bb36b9ad528e863ab34c02ebce002b02 Mon Sep 17 00:00:00 2001 From: Valentin Date: Fri, 15 Jun 2018 18:57:24 +0200 Subject: Update for latest nightly 2018-06-09 (#70) * Update for latest nightly 2018-06-09 * We now have a proper horizon os and sys modules in libstd --- ctr-std/src/sys/unix/fs.rs | 341 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 298 insertions(+), 43 deletions(-) (limited to 'ctr-std/src/sys/unix/fs.rs') diff --git a/ctr-std/src/sys/unix/fs.rs b/ctr-std/src/sys/unix/fs.rs index 9dba863..7743403 100644 --- a/ctr-std/src/sys/unix/fs.rs +++ b/ctr-std/src/sys/unix/fs.rs @@ -8,8 +8,6 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -#![allow(dead_code)] - use os::unix::prelude::*; use ffi::{CString, CStr, OsString, OsStr}; @@ -25,8 +23,24 @@ use sys::time::SystemTime; use sys::{cvt, cvt_r}; use sys_common::{AsInner, FromInner}; +#[cfg(any(target_os = "linux", target_os = "emscripten", target_os = "l4re"))] +use libc::{stat64, fstat64, lstat64, off64_t, ftruncate64, lseek64, dirent64, readdir64_r, open64}; +#[cfg(any(target_os = "linux", target_os = "emscripten", target_os = "android"))] +use libc::{fstatat, dirfd}; +#[cfg(target_os = "android")] +use libc::{stat as stat64, fstat as fstat64, lstat as lstat64, lseek64, + dirent as dirent64, open as open64}; +#[cfg(not(any(target_os = "linux", + target_os = "emscripten", + target_os = "l4re", + target_os = "android")))] use libc::{stat as stat64, fstat as fstat64, lstat as lstat64, off_t as off64_t, ftruncate as ftruncate64, lseek as lseek64, dirent as dirent64, open as open64}; +#[cfg(not(any(target_os = "linux", + target_os = "emscripten", + target_os = "solaris", + target_os = "l4re", + target_os = "fuchsia")))] use libc::{readdir_r as readdir64_r}; pub struct File(FileDesc); @@ -36,11 +50,15 @@ pub struct FileAttr { stat: stat64, } -pub struct ReadDir { +// all DirEntry's will have a reference to this struct +struct InnerReadDir { dirp: Dir, - root: Arc, + root: PathBuf, } +#[derive(Clone)] +pub struct ReadDir(Arc); + struct Dir(*mut libc::DIR); unsafe impl Send for Dir {} @@ -48,8 +66,8 @@ unsafe impl Sync for Dir {} pub struct DirEntry { entry: dirent64, - root: Arc, - // We need to store an owned copy of the directory name + dir: ReadDir, + // We need to store an owned copy of the entry name // on Solaris and Fuchsia because a) it uses a zero-length // array to store the name, b) its lifetime between readdir // calls is not guaranteed. @@ -91,19 +109,63 @@ impl FileAttr { } } +#[cfg(target_os = "netbsd")] impl FileAttr { pub fn modified(&self) -> io::Result { - Err(io::Error::new(io::ErrorKind::Other, - "modification time is not available on this platform \ - currently")) + Ok(SystemTime::from(libc::timespec { + tv_sec: self.stat.st_mtime as libc::time_t, + tv_nsec: self.stat.st_mtimensec as libc::c_long, + })) } pub fn accessed(&self) -> io::Result { - Err(io::Error::new(io::ErrorKind::Other, - "access time is not available on this platform \ - currently")) + Ok(SystemTime::from(libc::timespec { + tv_sec: self.stat.st_atime as libc::time_t, + tv_nsec: self.stat.st_atimensec as libc::c_long, + })) + } + + pub fn created(&self) -> io::Result { + Ok(SystemTime::from(libc::timespec { + tv_sec: self.stat.st_birthtime as libc::time_t, + tv_nsec: self.stat.st_birthtimensec as libc::c_long, + })) + } +} + +#[cfg(not(target_os = "netbsd"))] +impl FileAttr { + pub fn modified(&self) -> io::Result { + Ok(SystemTime::from(libc::timespec { + tv_sec: self.stat.st_mtime as libc::time_t, + tv_nsec: self.stat.st_mtime_nsec as _, + })) + } + + pub fn accessed(&self) -> io::Result { + Ok(SystemTime::from(libc::timespec { + tv_sec: self.stat.st_atime as libc::time_t, + tv_nsec: self.stat.st_atime_nsec as _, + })) } + #[cfg(any(target_os = "bitrig", + target_os = "freebsd", + target_os = "openbsd", + target_os = "macos", + target_os = "ios"))] + pub fn created(&self) -> io::Result { + Ok(SystemTime::from(libc::timespec { + tv_sec: self.stat.st_birthtime as libc::time_t, + tv_nsec: self.stat.st_birthtime_nsec as libc::c_long, + })) + } + + #[cfg(not(any(target_os = "bitrig", + target_os = "freebsd", + target_os = "openbsd", + target_os = "macos", + target_os = "ios")))] pub fn created(&self) -> io::Result { Err(io::Error::new(io::ErrorKind::Other, "creation time is not available on this platform \ @@ -151,7 +213,7 @@ 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.root, f) + fmt::Debug::fmt(&*self.0.root, f) } } @@ -167,7 +229,7 @@ impl Iterator for ReadDir { // is safe to use in threaded applications and it is generally preferred // over the readdir_r(3C) function. super::os::set_errno(0); - let entry_ptr = libc::readdir(self.dirp.0); + let entry_ptr = libc::readdir(self.0.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. @@ -177,14 +239,14 @@ impl Iterator for ReadDir { } } - let name = (*entry_ptr).d_name.as_ptr() as *const _; + let name = (*entry_ptr).d_name.as_ptr(); let namelen = libc::strlen(name) as usize; let ret = DirEntry { entry: *entry_ptr, name: ::slice::from_raw_parts(name as *const u8, namelen as usize).to_owned().into_boxed_slice(), - root: self.root.clone() + dir: self.clone() }; if ret.name_bytes() != b"." && ret.name_bytes() != b".." { return Some(Ok(ret)) @@ -198,11 +260,11 @@ impl Iterator for ReadDir { unsafe { let mut ret = DirEntry { entry: mem::zeroed(), - root: self.root.clone() + dir: self.clone(), }; let mut entry_ptr = ptr::null_mut(); loop { - if readdir64_r(self.dirp.0, &mut ret.entry, &mut entry_ptr) != 0 { + if readdir64_r(self.0.dirp.0, &mut ret.entry, &mut entry_ptr) != 0 { return Some(Err(Error::last_os_error())) } if entry_ptr.is_null() { @@ -225,13 +287,27 @@ impl Drop for Dir { impl DirEntry { pub fn path(&self) -> PathBuf { - self.root.join(OsStr::from_bytes(self.name_bytes())) + self.dir.0.root.join(OsStr::from_bytes(self.name_bytes())) } pub fn file_name(&self) -> OsString { OsStr::from_bytes(self.name_bytes()).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.0.dirp.0)})?; + let mut stat: stat64 = unsafe { mem::zeroed() }; + cvt(unsafe { + fstatat(fd, + self.entry.d_name.as_ptr(), + &mut stat as *mut _ as *mut _, + libc::AT_SYMLINK_NOFOLLOW) + })?; + Ok(FileAttr { stat: stat }) + } + + #[cfg(not(any(target_os = "linux", target_os = "emscripten", target_os = "android")))] pub fn metadata(&self) -> io::Result { lstat(&self.path()) } @@ -255,15 +331,56 @@ impl DirEntry { } } + #[cfg(any(target_os = "macos", + target_os = "ios", + target_os = "linux", + target_os = "emscripten", + target_os = "android", + target_os = "solaris", + target_os = "haiku", + target_os = "l4re", + target_os = "fuchsia"))] pub fn ino(&self) -> u64 { self.entry.d_ino as u64 } + #[cfg(any(target_os = "freebsd", + target_os = "openbsd", + target_os = "bitrig", + 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 = "netbsd", + target_os = "openbsd", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "bitrig"))] fn name_bytes(&self) -> &[u8] { unsafe { - CStr::from_ptr(self.entry.d_name.as_ptr() as *const _).to_bytes() + ::slice::from_raw_parts(self.entry.d_name.as_ptr() as *const u8, + self.entry.d_namlen as usize) } } + #[cfg(any(target_os = "android", + target_os = "linux", + target_os = "emscripten", + target_os = "l4re", + target_os = "haiku"))] + fn name_bytes(&self) -> &[u8] { + unsafe { + CStr::from_ptr(self.entry.d_name.as_ptr()).to_bytes() + } + } + #[cfg(any(target_os = "solaris", + target_os = "fuchsia"))] + fn name_bytes(&self) -> &[u8] { + &*self.name + } } impl OpenOptions { @@ -338,21 +455,54 @@ impl File { opts.get_creation_mode()? | (opts.custom_flags as c_int & !libc::O_ACCMODE); let fd = cvt_r(|| unsafe { - open64(path.as_ptr() as *const _, flags, opts.mode as c_int) + open64(path.as_ptr(), flags, opts.mode as c_int) })?; let fd = FileDesc::new(fd); // Currently the standard library supports Linux 2.6.18 which did not // have the O_CLOEXEC flag (passed above). If we're running on an older - // Linux kernel then the flag is just ignored by the OS, so we continue - // to explicitly ask for a CLOEXEC fd here. + // Linux kernel then the flag is just ignored by the OS. After we open + // the first file, we check whether it has CLOEXEC set. If it doesn't, + // we will explicitly ask for a CLOEXEC fd for every further file we + // open, if it does, we will skip that step. // // The CLOEXEC flag, however, is supported on versions of macOS/BSD/etc // that we support, so we only do this on Linux currently. - if cfg!(target_os = "linux") { - fd.set_cloexec()?; + #[cfg(target_os = "linux")] + fn ensure_cloexec(fd: &FileDesc) -> io::Result<()> { + use sync::atomic::{AtomicUsize, Ordering}; + + const OPEN_CLOEXEC_UNKNOWN: usize = 0; + const OPEN_CLOEXEC_SUPPORTED: usize = 1; + const OPEN_CLOEXEC_NOTSUPPORTED: usize = 2; + static OPEN_CLOEXEC: AtomicUsize = AtomicUsize::new(OPEN_CLOEXEC_UNKNOWN); + + let need_to_set; + match OPEN_CLOEXEC.load(Ordering::Relaxed) { + OPEN_CLOEXEC_UNKNOWN => { + need_to_set = !fd.get_cloexec()?; + OPEN_CLOEXEC.store(if need_to_set { + OPEN_CLOEXEC_NOTSUPPORTED + } else { + OPEN_CLOEXEC_SUPPORTED + }, Ordering::Relaxed); + }, + OPEN_CLOEXEC_SUPPORTED => need_to_set = false, + OPEN_CLOEXEC_NOTSUPPORTED => need_to_set = true, + _ => unreachable!(), + } + if need_to_set { + fd.set_cloexec()?; + } + Ok(()) + } + + #[cfg(not(target_os = "linux"))] + fn ensure_cloexec(_: &FileDesc) -> io::Result<()> { + Ok(()) } + ensure_cloexec(&fd)?; Ok(File(fd)) } @@ -373,6 +523,15 @@ impl File { cvt_r(|| unsafe { os_datasync(self.0.raw()) })?; return Ok(()); + #[cfg(any(target_os = "macos", target_os = "ios"))] + unsafe fn os_datasync(fd: c_int) -> c_int { + libc::fcntl(fd, libc::F_FULLFSYNC) + } + #[cfg(target_os = "linux")] + unsafe fn os_datasync(fd: c_int) -> c_int { libc::fdatasync(fd) } + #[cfg(not(any(target_os = "macos", + target_os = "ios", + target_os = "linux")))] unsafe fn os_datasync(fd: c_int) -> c_int { libc::fsync(fd) } } @@ -439,7 +598,7 @@ impl DirBuilder { pub fn mkdir(&self, p: &Path) -> io::Result<()> { let p = cstr(p)?; - cvt(unsafe { libc::mkdir(p.as_ptr() as *const _, self.mode) })?; + cvt(unsafe { libc::mkdir(p.as_ptr(), self.mode) })?; Ok(()) } @@ -475,7 +634,7 @@ impl fmt::Debug for File { // 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() as *const _) }; + let n = unsafe { libc::fcntl(fd, libc::F_GETPATH, buf.as_ptr()) }; if n == -1 { return None; } @@ -525,40 +684,41 @@ impl fmt::Debug for File { } pub fn readdir(p: &Path) -> io::Result { - let root = Arc::new(p.to_path_buf()); + let root = p.to_path_buf(); let p = cstr(p)?; unsafe { - let ptr = libc::opendir(p.as_ptr() as *const _); + let ptr = libc::opendir(p.as_ptr()); if ptr.is_null() { Err(Error::last_os_error()) } else { - Ok(ReadDir { dirp: Dir(ptr), root: root }) + let inner = InnerReadDir { dirp: Dir(ptr), root }; + Ok(ReadDir(Arc::new(inner))) } } } pub fn unlink(p: &Path) -> io::Result<()> { let p = cstr(p)?; - cvt(unsafe { libc::unlink(p.as_ptr() as *const _) })?; + 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() as *const _, new.as_ptr() as *const _) })?; + 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() as *const _, perm.mode) })?; + 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() as *const _) })?; + cvt(unsafe { libc::rmdir(p.as_ptr()) })?; Ok(()) } @@ -585,7 +745,7 @@ fn remove_dir_all_recursive(path: &Path) -> io::Result<()> { pub fn readlink(p: &Path) -> io::Result { let c_path = cstr(p)?; - let p = c_path.as_ptr() as *const _; + let p = c_path.as_ptr(); let mut buf = Vec::with_capacity(256); @@ -612,14 +772,14 @@ pub fn readlink(p: &Path) -> io::Result { pub fn symlink(src: &Path, dst: &Path) -> io::Result<()> { let src = cstr(src)?; let dst = cstr(dst)?; - cvt(unsafe { libc::symlink(src.as_ptr() as *const _, dst.as_ptr() as *const _) })?; + cvt(unsafe { libc::symlink(src.as_ptr(), dst.as_ptr()) })?; Ok(()) } pub fn link(src: &Path, dst: &Path) -> io::Result<()> { let src = cstr(src)?; let dst = cstr(dst)?; - cvt(unsafe { libc::link(src.as_ptr() as *const _, dst.as_ptr() as *const _) })?; + cvt(unsafe { libc::link(src.as_ptr(), dst.as_ptr()) })?; Ok(()) } @@ -627,7 +787,7 @@ pub fn stat(p: &Path) -> io::Result { let p = cstr(p)?; let mut stat: stat64 = unsafe { mem::zeroed() }; cvt(unsafe { - stat64(p.as_ptr() as *const _, &mut stat as *mut _ as *mut _) + stat64(p.as_ptr(), &mut stat as *mut _ as *mut _) })?; Ok(FileAttr { stat: stat }) } @@ -636,7 +796,7 @@ pub fn lstat(p: &Path) -> io::Result { let p = cstr(p)?; let mut stat: stat64 = unsafe { mem::zeroed() }; cvt(unsafe { - lstat64(p.as_ptr() as *const _, &mut stat as *mut _ as *mut _) + lstat64(p.as_ptr(), &mut stat as *mut _ as *mut _) })?; Ok(FileAttr { stat: stat }) } @@ -645,18 +805,19 @@ 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() as *const _, ptr::null_mut()); + 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 as *const _).to_bytes().to_vec(); + buf = CStr::from_ptr(r).to_bytes().to_vec(); libc::free(r as *mut _); } Ok(PathBuf::from(OsString::from_vec(buf))) } +#[cfg(not(any(target_os = "linux", target_os = "android")))] pub fn copy(from: &Path, to: &Path) -> io::Result { - use fs::{File, set_permissions}; + use fs::File; if !from.is_file() { return Err(Error::new(ErrorKind::InvalidInput, "the source path is not an existing regular file")) @@ -667,6 +828,100 @@ pub fn copy(from: &Path, to: &Path) -> io::Result { let perm = reader.metadata()?.permissions(); let ret = io::copy(&mut reader, &mut writer)?; - set_permissions(to, perm)?; + writer.set_permissions(perm)?; Ok(ret) } + +#[cfg(any(target_os = "linux", target_os = "android"))] +pub fn copy(from: &Path, to: &Path) -> io::Result { + use cmp; + use fs::File; + use sync::atomic::{AtomicBool, Ordering}; + + // Kernel prior to 4.5 don't have copy_file_range + // We store the availability in a global to avoid unneccessary syscalls + static HAS_COPY_FILE_RANGE: AtomicBool = AtomicBool::new(true); + + unsafe fn copy_file_range( + fd_in: libc::c_int, + off_in: *mut libc::loff_t, + fd_out: libc::c_int, + off_out: *mut libc::loff_t, + len: libc::size_t, + flags: libc::c_uint, + ) -> libc::c_long { + libc::syscall( + libc::SYS_copy_file_range, + fd_in, + off_in, + fd_out, + off_out, + len, + flags, + ) + } + + if !from.is_file() { + return Err(Error::new(ErrorKind::InvalidInput, + "the source path is not an existing regular file")) + } + + let mut reader = File::open(from)?; + let mut writer = File::create(to)?; + let (perm, len) = { + let metadata = reader.metadata()?; + (metadata.permissions(), metadata.size()) + }; + + let has_copy_file_range = HAS_COPY_FILE_RANGE.load(Ordering::Relaxed); + let mut written = 0u64; + while written < len { + let copy_result = if has_copy_file_range { + let bytes_to_copy = cmp::min(len - written, usize::max_value() as u64) as usize; + let copy_result = unsafe { + // We actually don't have to adjust the offsets, + // because copy_file_range adjusts the file offset automatically + cvt(copy_file_range(reader.as_raw_fd(), + ptr::null_mut(), + writer.as_raw_fd(), + ptr::null_mut(), + bytes_to_copy, + 0) + ) + }; + if let Err(ref copy_err) = copy_result { + match copy_err.raw_os_error() { + Some(libc::ENOSYS) | Some(libc::EPERM) => { + HAS_COPY_FILE_RANGE.store(false, Ordering::Relaxed); + } + _ => {} + } + } + copy_result + } else { + Err(io::Error::from_raw_os_error(libc::ENOSYS)) + }; + match copy_result { + Ok(ret) => written += ret as u64, + Err(err) => { + match err.raw_os_error() { + Some(os_err) if os_err == libc::ENOSYS + || os_err == libc::EXDEV + || os_err == libc::EPERM => { + // Try fallback io::copy if either: + // - Kernel version is < 4.5 (ENOSYS) + // - Files are mounted on different fs (EXDEV) + // - copy_file_range is disallowed, for example by seccomp (EPERM) + assert_eq!(written, 0); + let ret = io::copy(&mut reader, &mut writer)?; + writer.set_permissions(perm)?; + return Ok(ret) + }, + _ => return Err(err), + } + } + } + } + writer.set_permissions(perm)?; + Ok(written) +} -- cgit v1.2.3