diff --git a/Cargo.lock b/Cargo.lock index 4534bc7ea1aa..89a0671b22f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1663,9 +1663,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f" dependencies = [ "compiler_builtins", "rustc-std-workspace-alloc", diff --git a/library/std/Cargo.toml b/library/std/Cargo.toml index 5b32bc5117c0..fe5a3e860d37 100644 --- a/library/std/Cargo.toml +++ b/library/std/Cargo.toml @@ -48,7 +48,7 @@ dlmalloc = { version = "0.2.4", features = ['rustc-dep-of-std'] } fortanix-sgx-abi = { version = "0.5.0", features = ['rustc-dep-of-std'], public = true } [target.'cfg(target_os = "hermit")'.dependencies] -hermit-abi = { version = "0.3.2", features = ['rustc-dep-of-std'], public = true } +hermit-abi = { version = "0.3.4", features = ['rustc-dep-of-std'], public = true } [target.'cfg(target_os = "wasi")'.dependencies] wasi = { version = "0.11.0", features = ['rustc-dep-of-std'], default-features = false } diff --git a/library/std/src/sys/pal/hermit/fs.rs b/library/std/src/sys/pal/hermit/fs.rs index b0b8d701f032..b111c4da53a9 100644 --- a/library/std/src/sys/pal/hermit/fs.rs +++ b/library/std/src/sys/pal/hermit/fs.rs @@ -1,6 +1,9 @@ -use super::abi::{self, O_APPEND, O_CREAT, O_EXCL, O_RDONLY, O_RDWR, O_TRUNC, O_WRONLY}; +use super::abi::{ + self, dirent64, stat as stat_struct, DT_DIR, DT_LNK, DT_REG, DT_UNKNOWN, O_APPEND, O_CREAT, + O_EXCL, O_RDONLY, O_RDWR, O_TRUNC, O_WRONLY, S_IFDIR, S_IFLNK, S_IFMT, S_IFREG, +}; use super::fd::FileDesc; -use crate::ffi::{CStr, OsString}; +use crate::ffi::{CStr, OsStr, OsString}; use crate::fmt; use crate::io::{self, Error, ErrorKind}; use crate::io::{BorrowedCursor, IoSlice, IoSliceMut, SeekFrom}; @@ -8,7 +11,6 @@ use crate::mem; use crate::os::hermit::ffi::OsStringExt; use crate::os::hermit::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, RawFd}; use crate::path::{Path, PathBuf}; -use crate::ptr; use crate::sync::Arc; use crate::sys::common::small_c_string::run_path_with_cstr; use crate::sys::cvt; @@ -17,7 +19,6 @@ use crate::sys::unsupported; use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner}; pub use crate::sys_common::fs::{copy, try_exists}; -//pub use crate::sys_common::fs::remove_dir_all; #[derive(Debug)] pub struct File(FileDesc); @@ -34,32 +35,38 @@ impl FileAttr { // all DirEntry's will have a reference to this struct struct InnerReadDir { - dirp: FileDesc, root: PathBuf, + dir: Vec<u8>, +} + +impl InnerReadDir { + pub fn new(root: PathBuf, dir: Vec<u8>) -> Self { + Self { root, dir } + } } pub struct ReadDir { inner: Arc<InnerReadDir>, - end_of_stream: bool, + pos: i64, } impl ReadDir { fn new(inner: InnerReadDir) -> Self { - Self { inner: Arc::new(inner), end_of_stream: false } + Self { inner: Arc::new(inner), pos: 0 } } } pub struct DirEntry { - dir: Arc<InnerReadDir>, - entry: dirent_min, + /// path to the entry + root: PathBuf, + /// 64-bit inode number + ino: u64, + /// File type + type_: u32, + /// name of the entry name: OsString, } -struct dirent_min { - d_ino: u64, - d_type: u32, -} - #[derive(Clone, Debug)] pub struct OpenOptions { // generic @@ -105,15 +112,24 @@ pub struct DirBuilder { impl FileAttr { pub fn modified(&self) -> io::Result<SystemTime> { - Ok(SystemTime::new(self.stat_val.st_mtime, self.stat_val.st_mtime_nsec)) + Ok(SystemTime::new( + self.stat_val.st_mtime.try_into().unwrap(), + self.stat_val.st_mtime_nsec.try_into().unwrap(), + )) } pub fn accessed(&self) -> io::Result<SystemTime> { - Ok(SystemTime::new(self.stat_val.st_atime, self.stat_val.st_atime_nsec)) + Ok(SystemTime::new( + self.stat_val.st_atime.try_into().unwrap(), + self.stat_val.st_atime_nsec.try_into().unwrap(), + )) } pub fn created(&self) -> io::Result<SystemTime> { - Ok(SystemTime::new(self.stat_val.st_ctime, self.stat_val.st_ctime_nsec)) + Ok(SystemTime::new( + self.stat_val.st_ctime.try_into().unwrap(), + self.stat_val.st_ctime_nsec.try_into().unwrap(), + )) } pub fn size(&self) -> u64 { @@ -171,7 +187,7 @@ impl FileType { 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")' + // Thus the result will be e.g. 'ReadDir("/home")' fmt::Debug::fmt(&*self.inner.root, f) } } @@ -180,84 +196,55 @@ impl Iterator for ReadDir { type Item = io::Result<DirEntry>; fn next(&mut self) -> Option<io::Result<DirEntry>> { - if self.end_of_stream { - return None; - } + let mut counter: usize = 0; + let mut offset: i64 = 0; + + // loop over all directory entries and search the entry for the current position + loop { + // leave function, if the loop reaches the of the buffer (with all entries) + if offset >= self.inner.dir.len().try_into().unwrap() { + return None; + } - 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. - let entry_ptr = match abi::readdir(self.inner.dirp.as_raw_fd()) { - abi::DirectoryEntry::Invalid(e) => { - // We either encountered an error, or reached the end. Either way, - // the next call to next() should return None. - self.end_of_stream = true; - - return Some(Err(Error::from_raw_os_error(e))); - } - abi::DirectoryEntry::Valid(ptr) => { - if ptr.is_null() { - return None; - } - - ptr - } + let dir = unsafe { + &*(self.inner.dir.as_ptr().offset(offset.try_into().unwrap()) as *const dirent64) + }; + + if counter == self.pos.try_into().unwrap() { + self.pos += 1; + + // After dirent64, the file name is stored. d_reclen represents the length of the dirent64 + // plus the length of the file name. Consequently, file name has a size of d_reclen minus + // the size of dirent64. The file name is always a C string and terminated by `\0`. + // Consequently, we are able to ignore the last byte. + let name_bytes = unsafe { + core::slice::from_raw_parts( + &dir.d_name as *const _ as *const u8, + dir.d_reclen as usize - core::mem::size_of::<dirent64>() - 1, + ) + .to_vec() }; - - macro_rules! offset_ptr { - ($entry_ptr:expr, $field:ident) => {{ - const OFFSET: isize = { - let delusion = MaybeUninit::<dirent>::uninit(); - let entry_ptr = delusion.as_ptr(); - unsafe { - ptr::addr_of!((*entry_ptr).$field) - .cast::<u8>() - .offset_from(entry_ptr.cast::<u8>()) - } - }; - if true { - // Cast to the same type determined by the else branch. - $entry_ptr.byte_offset(OFFSET).cast::<_>() - } else { - #[allow(deref_nullptr)] - { - ptr::addr_of!((*ptr::null::<dirent>()).$field) - } - } - }}; - } - - // d_name is NOT guaranteed to be null-terminated. - let name_bytes = core::slice::from_raw_parts( - offset_ptr!(entry_ptr, d_name) as *const u8, - *offset_ptr!(entry_ptr, d_namelen) as usize, - ) - .to_vec(); - - if name_bytes == b"." || name_bytes == b".." { - continue; - } - - let name = OsString::from_vec(name_bytes); - - let entry = dirent_min { - d_ino: *offset_ptr!(entry_ptr, d_ino), - d_type: *offset_ptr!(entry_ptr, d_type), + let entry = DirEntry { + root: self.inner.root.clone(), + ino: dir.d_ino, + type_: dir.d_type as u32, + name: OsString::from_vec(name_bytes), }; - return Some(Ok(DirEntry { entry, name: name, dir: Arc::clone(&self.inner) })); + return Some(Ok(entry)); } + + counter += 1; + + // move to the next dirent64, which is directly stored after the previous one + offset = offset + dir.d_off; } } } impl DirEntry { pub fn path(&self) -> PathBuf { - self.dir.root.join(self.file_name_os_str()) + self.root.join(self.file_name_os_str()) } pub fn file_name(&self) -> OsString { @@ -265,16 +252,18 @@ impl DirEntry { } pub fn metadata(&self) -> io::Result<FileAttr> { - lstat(&self.path()) + let mut path = self.path(); + path.set_file_name(self.file_name_os_str()); + lstat(&path) } pub fn file_type(&self) -> io::Result<FileType> { - Ok(FileType { mode: self.entry.d_type }) + Ok(FileType { mode: self.type_ as u32 }) } #[allow(dead_code)] pub fn ino(&self) -> u64 { - self.entry.d_ino + self.ino } pub fn file_name_os_str(&self) -> &OsStr { @@ -522,8 +511,39 @@ pub fn readdir(path: &Path) -> io::Result<ReadDir> { let fd_raw = run_path_with_cstr(path, |path| cvt(unsafe { abi::opendir(path.as_ptr()) }))?; let fd = unsafe { FileDesc::from_raw_fd(fd_raw as i32) }; let root = path.to_path_buf(); - let inner = InnerReadDir { dirp: fd, root }; - Ok(ReadDir::new(inner)) + + // read all director entries + let mut vec: Vec<u8> = Vec::new(); + let mut sz = 512; + loop { + // reserve memory to receive all directory entries + vec.resize(sz, 0); + + let readlen = + unsafe { abi::getdents64(fd.as_raw_fd(), vec.as_mut_ptr() as *mut dirent64, sz) }; + if readlen > 0 { + // shrink down to the minimal size + vec.resize(readlen.try_into().unwrap(), 0); + break; + } + + // if the buffer is too small, getdents64 returns EINVAL + // otherwise, getdents64 returns an error number + if readlen != (-abi::errno::EINVAL).into() { + return Err(Error::from_raw_os_error(readlen.try_into().unwrap())); + } + + // we don't have enough memory => try to increase the vector size + sz = sz * 2; + + // 1 MB for directory entries should be enough + // stop here to avoid an endless loop + if sz > 0x100000 { + return Err(Error::from(ErrorKind::Uncategorized)); + } + } + + Ok(ReadDir::new(InnerReadDir::new(root, vec))) } pub fn unlink(path: &Path) -> io::Result<()> {