From aa11bf6f0a1ba4e6eb92f31423feb831fae2b7d2 Mon Sep 17 00:00:00 2001 From: simonschoening Date: Wed, 14 Jun 2023 22:51:51 +0200 Subject: [PATCH 1/2] Extending filesystem support for hermit-os --- library/std/src/sys/pal/hermit/fd.rs | 5 + library/std/src/sys/pal/hermit/fs.rs | 306 +++++++++++++++++-------- library/std/src/sys/pal/hermit/time.rs | 10 + 3 files changed, 225 insertions(+), 96 deletions(-) diff --git a/library/std/src/sys/pal/hermit/fd.rs b/library/std/src/sys/pal/hermit/fd.rs index 5eb828fea1f9e..962577bb1ed83 100644 --- a/library/std/src/sys/pal/hermit/fd.rs +++ b/library/std/src/sys/pal/hermit/fd.rs @@ -48,6 +48,11 @@ impl FileDesc { pub fn set_nonblocking(&self, _nonblocking: bool) -> io::Result<()> { unsupported() } + + pub fn fstat(&self, stat: *mut abi::stat) -> io::Result<()> { + cvt(unsafe { abi::fstat(self.fd.as_raw_fd(), stat) })?; + Ok(()) + } } impl<'a> Read for &'a FileDesc { diff --git a/library/std/src/sys/pal/hermit/fs.rs b/library/std/src/sys/pal/hermit/fs.rs index d4da53fd3df9e..306e4d2210ac6 100644 --- a/library/std/src/sys/pal/hermit/fs.rs +++ b/library/std/src/sys/pal/hermit/fs.rs @@ -2,11 +2,14 @@ use super::abi::{self, O_APPEND, O_CREAT, O_EXCL, O_RDONLY, O_RDWR, O_TRUNC, O_W use super::fd::FileDesc; use crate::ffi::{CStr, OsString}; use crate::fmt; -use crate::hash::{Hash, Hasher}; use crate::io::{self, Error, ErrorKind}; use crate::io::{BorrowedCursor, IoSlice, IoSliceMut, SeekFrom}; +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; use crate::sys::time::SystemTime; @@ -18,12 +21,44 @@ pub use crate::sys_common::fs::{copy, try_exists}; #[derive(Debug)] pub struct File(FileDesc); +#[derive(Clone)] +pub struct FileAttr { + stat_val: stat_struct, +} -pub struct FileAttr(!); +impl FileAttr { + fn from_stat(stat_val: stat_struct) -> Self { + Self { stat_val } + } +} -pub struct ReadDir(!); +// all DirEntry's will have a reference to this struct +struct InnerReadDir { + dirp: FileDesc, + root: PathBuf, +} + +pub struct ReadDir { + inner: Arc, + end_of_stream: bool, +} -pub struct DirEntry(!); +impl ReadDir { + fn new(inner: InnerReadDir) -> Self { + Self { inner: Arc::new(inner), end_of_stream: false } + } +} + +pub struct DirEntry { + dir: Arc, + entry: dirent_min, + name: OsString, +} + +struct dirent_min { + d_ino: u64, + d_type: u32, +} #[derive(Clone, Debug)] pub struct OpenOptions { @@ -41,72 +76,78 @@ pub struct OpenOptions { #[derive(Copy, Clone, Debug, Default)] pub struct FileTimes {} -pub struct FilePermissions(!); - -pub struct FileType(!); +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct FilePermissions { + mode: u32, +} -#[derive(Debug)] -pub struct DirBuilder {} +#[derive(Copy, Clone, Eq, Debug)] +pub struct FileType { + mode: u32, +} -impl FileAttr { - pub fn size(&self) -> u64 { - self.0 +impl PartialEq for FileType { + fn eq(&self, other: &Self) -> bool { + self.mode == other.mode } +} - pub fn perm(&self) -> FilePermissions { - self.0 +impl core::hash::Hash for FileType { + fn hash(&self, state: &mut H) { + self.mode.hash(state); } +} - pub fn file_type(&self) -> FileType { - self.0 - } +#[derive(Debug)] +pub struct DirBuilder { + mode: u32, +} +impl FileAttr { pub fn modified(&self) -> io::Result { - self.0 + Ok(SystemTime::new(self.stat_val.st_mtime, self.stat_val.st_mtime_nsec)) } pub fn accessed(&self) -> io::Result { - self.0 + Ok(SystemTime::new(self.stat_val.st_atime, self.stat_val.st_atime_nsec)) } pub fn created(&self) -> io::Result { - self.0 + Ok(SystemTime::new(self.stat_val.st_ctime, self.stat_val.st_ctime_nsec)) } -} -impl Clone for FileAttr { - fn clone(&self) -> FileAttr { - self.0 + pub fn size(&self) -> u64 { + self.stat_val.st_size as u64 } -} - -impl FilePermissions { - pub fn readonly(&self) -> bool { - self.0 + pub fn perm(&self) -> FilePermissions { + FilePermissions { mode: (self.stat_val.st_mode) } } - pub fn set_readonly(&mut self, _readonly: bool) { - self.0 + pub fn file_type(&self) -> FileType { + let masked_mode = self.stat_val.st_mode & S_IFMT; + let mode = match masked_mode { + S_IFDIR => DT_DIR, + S_IFLNK => DT_LNK, + S_IFREG => DT_REG, + _ => DT_UNKNOWN, + }; + FileType { mode: mode } } } -impl Clone for FilePermissions { - fn clone(&self) -> FilePermissions { - self.0 +impl FilePermissions { + pub fn readonly(&self) -> bool { + // check if any class (owner, group, others) has write permission + self.mode & 0o222 == 0 } -} -impl PartialEq for FilePermissions { - fn eq(&self, _other: &FilePermissions) -> bool { - self.0 + pub fn set_readonly(&mut self, _readonly: bool) { + unimplemented!() } -} - -impl Eq for FilePermissions {} -impl fmt::Debug for FilePermissions { - fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0 + #[allow(dead_code)] + pub fn mode(&self) -> u32 { + self.mode as u32 } } @@ -117,75 +158,127 @@ impl FileTimes { impl FileType { pub fn is_dir(&self) -> bool { - self.0 + self.mode == DT_DIR } - pub fn is_file(&self) -> bool { - self.0 + self.mode == DT_REG } - pub fn is_symlink(&self) -> bool { - self.0 + self.mode == DT_LNK } } -impl Clone for FileType { - fn clone(&self) -> FileType { - self.0 +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 Copy for FileType {} +impl Iterator for ReadDir { + type Item = io::Result; -impl PartialEq for FileType { - fn eq(&self, _other: &FileType) -> bool { - self.0 - } -} + fn next(&mut self) -> Option> { + if self.end_of_stream { + return None; + } -impl Eq for FileType {} + 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 + } + }; + + macro_rules! offset_ptr { + ($entry_ptr:expr, $field:ident) => {{ + const OFFSET: isize = { + let delusion = MaybeUninit::::uninit(); + let entry_ptr = delusion.as_ptr(); + unsafe { + ptr::addr_of!((*entry_ptr).$field) + .cast::() + .offset_from(entry_ptr.cast::()) + } + }; + 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::()).$field) + } + } + }}; + } -impl Hash for FileType { - fn hash(&self, _h: &mut H) { - self.0 - } -} + // 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(); -impl fmt::Debug for FileType { - fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0 - } -} + if name_bytes == b"." || name_bytes == b".." { + continue; + } -impl fmt::Debug for ReadDir { - fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0 - } -} + let name = OsString::from_vec(name_bytes); -impl Iterator for ReadDir { - type Item = io::Result; + let entry = dirent_min { + d_ino: *offset_ptr!(entry_ptr, d_ino), + d_type: *offset_ptr!(entry_ptr, d_type), + }; - fn next(&mut self) -> Option> { - self.0 + return Some(Ok(DirEntry { entry, name: name, dir: Arc::clone(&self.inner) })); + } + } } } impl DirEntry { pub fn path(&self) -> PathBuf { - self.0 + self.dir.root.join(self.file_name_os_str()) } pub fn file_name(&self) -> OsString { - self.0 + self.file_name_os_str().to_os_string() } pub fn metadata(&self) -> io::Result { - self.0 + lstat(&self.path()) } pub fn file_type(&self) -> io::Result { - self.0 + Ok(FileType { mode: self.entry.d_type }) + } + + #[allow(dead_code)] + pub fn ino(&self) -> u64 { + self.entry.d_ino + } + + pub fn file_name_os_str(&self) -> &OsStr { + self.name.as_os_str() } } @@ -288,7 +381,9 @@ impl File { } pub fn file_attr(&self) -> io::Result { - Err(Error::from_raw_os_error(22)) + let mut stat_val: stat_struct = unsafe { mem::zeroed() }; + self.0.fstat(&mut stat_val)?; + Ok(FileAttr::from_stat(stat_val)) } pub fn fsync(&self) -> io::Result<()> { @@ -357,11 +452,18 @@ impl File { impl DirBuilder { pub fn new() -> DirBuilder { - DirBuilder {} + DirBuilder { mode: 0o777 } } - pub fn mkdir(&self, _p: &Path) -> io::Result<()> { - unsupported() + pub fn mkdir(&self, path: &Path) -> io::Result<()> { + run_path_with_cstr(path, |path| { + cvt(unsafe { abi::mkdir(path.as_ptr(), self.mode) }).map(|_| ()) + }) + } + + #[allow(dead_code)] + pub fn set_mode(&mut self, mode: u32) { + self.mode = mode as u32; } } @@ -416,8 +518,12 @@ impl FromRawFd for File { } } -pub fn readdir(_p: &Path) -> io::Result { - unsupported() +pub fn readdir(path: &Path) -> io::Result { + 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)) } pub fn unlink(path: &Path) -> io::Result<()> { @@ -428,12 +534,12 @@ pub fn rename(_old: &Path, _new: &Path) -> io::Result<()> { unsupported() } -pub fn set_perm(_p: &Path, perm: FilePermissions) -> io::Result<()> { - match perm.0 {} +pub fn set_perm(_p: &Path, _perm: FilePermissions) -> io::Result<()> { + Err(Error::from_raw_os_error(22)) } -pub fn rmdir(_p: &Path) -> io::Result<()> { - unsupported() +pub fn rmdir(path: &Path) -> io::Result<()> { + run_path_with_cstr(path, |path| cvt(unsafe { abi::rmdir(path.as_ptr()) }).map(|_| ())) } pub fn remove_dir_all(_path: &Path) -> io::Result<()> { @@ -453,12 +559,20 @@ pub fn link(_original: &Path, _link: &Path) -> io::Result<()> { unsupported() } -pub fn stat(_p: &Path) -> io::Result { - unsupported() +pub fn stat(path: &Path) -> io::Result { + run_path_with_cstr(path, |path| { + let mut stat_val: stat_struct = unsafe { mem::zeroed() }; + cvt(unsafe { abi::stat(path.as_ptr(), &mut stat_val) })?; + Ok(FileAttr::from_stat(stat_val)) + }) } -pub fn lstat(_p: &Path) -> io::Result { - unsupported() +pub fn lstat(path: &Path) -> io::Result { + run_path_with_cstr(path, |path| { + let mut stat_val: stat_struct = unsafe { mem::zeroed() }; + cvt(unsafe { abi::lstat(path.as_ptr(), &mut stat_val) })?; + Ok(FileAttr::from_stat(stat_val)) + }) } pub fn canonicalize(_p: &Path) -> io::Result { diff --git a/library/std/src/sys/pal/hermit/time.rs b/library/std/src/sys/pal/hermit/time.rs index f289dafd8bc56..fa10b7638ec20 100644 --- a/library/std/src/sys/pal/hermit/time.rs +++ b/library/std/src/sys/pal/hermit/time.rs @@ -18,6 +18,12 @@ impl Timespec { Timespec { t: timespec { tv_sec: 0, tv_nsec: 0 } } } + const fn new(tv_sec: i64, tv_nsec: i64) -> Timespec { + assert!(tv_nsec >= 0 && tv_nsec < NSEC_PER_SEC as i64); + // SAFETY: The assert above checks tv_nsec is within the valid range + Timespec { t: timespec { tv_sec: tv_sec, tv_nsec: tv_nsec } } + } + fn sub_timespec(&self, other: &Timespec) -> Result { if self >= other { Ok(if self.t.tv_nsec >= other.t.tv_nsec { @@ -195,6 +201,10 @@ pub struct SystemTime(Timespec); pub const UNIX_EPOCH: SystemTime = SystemTime(Timespec::zero()); impl SystemTime { + pub fn new(tv_sec: i64, tv_nsec: i64) -> SystemTime { + SystemTime(Timespec::new(tv_sec, tv_nsec)) + } + pub fn now() -> SystemTime { let mut time: Timespec = Timespec::zero(); let _ = unsafe { abi::clock_gettime(CLOCK_REALTIME, core::ptr::addr_of_mut!(time.t)) }; From b1c7804c684301213775a4f490605302aeec7789 Mon Sep 17 00:00:00 2001 From: Stefan Lankes Date: Wed, 17 Jan 2024 20:19:16 +0100 Subject: [PATCH 2/2] revise interface to read directory entries The new interface has some similarities to Linux system call getdents64. The system call reads several dirent64 structures. At the end of each dirent64 is stored the name of the file. The length of file name is implictly part of dirent64 because d_reclen contains size of dirent64 plus the length of the file name. --- library/std/src/sys/pal/hermit/fs.rs | 213 +++++++++++++++------------ 1 file changed, 116 insertions(+), 97 deletions(-) diff --git a/library/std/src/sys/pal/hermit/fs.rs b/library/std/src/sys/pal/hermit/fs.rs index 306e4d2210ac6..6519cc22f1f6f 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, +} + +impl InnerReadDir { + pub fn new(root: PathBuf, dir: Vec) -> Self { + Self { root, dir } + } } pub struct ReadDir { inner: Arc, - 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, - 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 { - 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 { - 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 { - 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; fn next(&mut self) -> Option> { - 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::() - 1, + ) + .to_vec() }; - - macro_rules! offset_ptr { - ($entry_ptr:expr, $field:ident) => {{ - const OFFSET: isize = { - let delusion = MaybeUninit::::uninit(); - let entry_ptr = delusion.as_ptr(); - unsafe { - ptr::addr_of!((*entry_ptr).$field) - .cast::() - .offset_from(entry_ptr.cast::()) - } - }; - 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::()).$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 { - 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 { - 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 { @@ -456,7 +445,7 @@ impl DirBuilder { } pub fn mkdir(&self, path: &Path) -> io::Result<()> { - run_path_with_cstr(path, |path| { + run_path_with_cstr(path, &|path| { cvt(unsafe { abi::mkdir(path.as_ptr(), self.mode) }).map(|_| ()) }) } @@ -519,11 +508,42 @@ impl FromRawFd for File { } pub fn readdir(path: &Path) -> io::Result { - let fd_raw = run_path_with_cstr(path, |path| cvt(unsafe { abi::opendir(path.as_ptr()) }))?; + 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 = 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<()> { @@ -539,12 +559,11 @@ pub fn set_perm(_p: &Path, _perm: FilePermissions) -> io::Result<()> { } pub fn rmdir(path: &Path) -> io::Result<()> { - run_path_with_cstr(path, |path| cvt(unsafe { abi::rmdir(path.as_ptr()) }).map(|_| ())) + run_path_with_cstr(path, &|path| cvt(unsafe { abi::rmdir(path.as_ptr()) }).map(|_| ())) } pub fn remove_dir_all(_path: &Path) -> io::Result<()> { - //unsupported() - Ok(()) + unsupported() } pub fn readlink(_p: &Path) -> io::Result { @@ -560,7 +579,7 @@ pub fn link(_original: &Path, _link: &Path) -> io::Result<()> { } pub fn stat(path: &Path) -> io::Result { - run_path_with_cstr(path, |path| { + run_path_with_cstr(path, &|path| { let mut stat_val: stat_struct = unsafe { mem::zeroed() }; cvt(unsafe { abi::stat(path.as_ptr(), &mut stat_val) })?; Ok(FileAttr::from_stat(stat_val)) @@ -568,7 +587,7 @@ pub fn stat(path: &Path) -> io::Result { } pub fn lstat(path: &Path) -> io::Result { - run_path_with_cstr(path, |path| { + run_path_with_cstr(path, &|path| { let mut stat_val: stat_struct = unsafe { mem::zeroed() }; cvt(unsafe { abi::lstat(path.as_ptr(), &mut stat_val) })?; Ok(FileAttr::from_stat(stat_val))