Skip to content

extending filesystem support for Hermit #115984

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 8, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions library/std/src/sys/pal/hermit/fd.rs
Original file line number Diff line number Diff line change
@@ -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) })?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...Why did I have to trace through three different directories just to discover that this is referring to the hermit_abi crate? That is so confusing.

Copy link
Contributor Author

@stlankes stlankes Mar 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excuse me for my late response.

I am a little bit confused. Why do you have trace three different directories? Do you mean I should move fstat from from fd.rs to fs.rs?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or do you want that rename abi to hermit-abi? Maybe it is easier to understand...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I was a bit confused by the levels of indirection in general, but yes, I think probably using hermit_abi might be a bit clearer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I would prefer to create an additional PR for these changes because I have to touch every file in library/std/src/sys/pal/hermit/. In addition, it doesn't belong to this PR.

Ok(())
}
}

impl<'a> Read for &'a FileDesc {
339 changes: 236 additions & 103 deletions library/std/src/sys/pal/hermit/fs.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,71 @@
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::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::sync::Arc;
use crate::sys::common::small_c_string::run_path_with_cstr;
use crate::sys::cvt;
use crate::sys::time::SystemTime;
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);
#[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 {
root: PathBuf,
dir: Vec<u8>,
}

pub struct DirEntry(!);
impl InnerReadDir {
pub fn new(root: PathBuf, dir: Vec<u8>) -> Self {
Self { root, dir }
}
}

pub struct ReadDir {
inner: Arc<InnerReadDir>,
pos: i64,
}

impl ReadDir {
fn new(inner: InnerReadDir) -> Self {
Self { inner: Arc::new(inner), pos: 0 }
}
}

pub struct DirEntry {
/// path to the entry
root: PathBuf,
/// 64-bit inode number
ino: u64,
/// File type
type_: u32,
/// name of the entry
name: OsString,
}

#[derive(Clone, Debug)]
pub struct OpenOptions {
@@ -41,72 +83,87 @@ 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<H: core::hash::Hasher>(&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<SystemTime> {
self.0
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> {
self.0
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> {
self.0
Ok(SystemTime::new(
self.stat_val.st_ctime.try_into().unwrap(),
self.stat_val.st_ctime_nsec.try_into().unwrap(),
))
}
}

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 +174,100 @@ 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
}
}

impl Clone for FileType {
fn clone(&self) -> FileType {
self.0
}
}

impl Copy for FileType {}

impl PartialEq for FileType {
fn eq(&self, _other: &FileType) -> bool {
self.0
}
}

impl Eq for FileType {}

impl Hash for FileType {
fn hash<H: Hasher>(&self, _h: &mut H) {
self.0
}
}

impl fmt::Debug for FileType {
fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0
self.mode == DT_LNK
}
}

impl fmt::Debug for ReadDir {
fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0
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<DirEntry>;

fn next(&mut self) -> Option<io::Result<DirEntry>> {
self.0
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;
}

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()
};
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(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.0
self.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<FileAttr> {
self.0
let mut path = self.path();
path.set_file_name(self.file_name_os_str());
lstat(&path)
}

pub fn file_type(&self) -> io::Result<FileType> {
self.0
Ok(FileType { mode: self.type_ as u32 })
}

#[allow(dead_code)]
pub fn ino(&self) -> u64 {
self.ino
}

pub fn file_name_os_str(&self) -> &OsStr {
self.name.as_os_str()
}
}

@@ -288,7 +370,9 @@ impl File {
}

pub fn file_attr(&self) -> io::Result<FileAttr> {
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 +441,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 +507,43 @@ impl FromRawFd for File {
}
}

pub fn readdir(_p: &Path) -> io::Result<ReadDir> {
unsupported()
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();

// 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<()> {
@@ -428,17 +554,16 @@ 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<()> {
//unsupported()
Ok(())
unsupported()
}

pub fn readlink(_p: &Path) -> io::Result<PathBuf> {
@@ -453,12 +578,20 @@ pub fn link(_original: &Path, _link: &Path) -> io::Result<()> {
unsupported()
}

pub fn stat(_p: &Path) -> io::Result<FileAttr> {
unsupported()
pub fn stat(path: &Path) -> io::Result<FileAttr> {
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<FileAttr> {
unsupported()
pub fn lstat(path: &Path) -> io::Result<FileAttr> {
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<PathBuf> {
10 changes: 10 additions & 0 deletions library/std/src/sys/pal/hermit/time.rs
Original file line number Diff line number Diff line change
@@ -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<Duration, Duration> {
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)) };