diff --git a/src/libos/src/fs/file.rs b/src/libos/src/fs/file.rs index d9d8924d..62d588ed 100644 --- a/src/libos/src/fs/file.rs +++ b/src/libos/src/fs/file.rs @@ -55,8 +55,8 @@ pub trait File: Debug + Sync + Send + Any { return_op_unsupported_error!("set_len") } - fn read_entry(&self) -> Result { - return_op_unsupported_error!("read_entry", ENOTDIR) + fn iterate_entries(&self, writer: &mut dyn DirentWriter) -> Result { + return_op_unsupported_error!("iterate_entries") } fn sync_all(&self) -> Result<()> { diff --git a/src/libos/src/fs/file_ops/dirent.rs b/src/libos/src/fs/file_ops/dirent.rs deleted file mode 100644 index 4641e387..00000000 --- a/src/libos/src/fs/file_ops/dirent.rs +++ /dev/null @@ -1,180 +0,0 @@ -use super::*; - -pub fn do_getdents64(fd: FileDesc, buf: &mut [u8]) -> Result { - getdents_common::(fd, buf) -} - -pub fn do_getdents(fd: FileDesc, buf: &mut [u8]) -> Result { - getdents_common::<()>(fd, buf) -} - -fn getdents_common(fd: FileDesc, buf: &mut [u8]) -> Result { - debug!( - "getdents: fd: {}, buf: {:?}, buf_size: {}", - fd, - buf.as_ptr(), - buf.len(), - ); - - let file_ref = current!().file(fd)?; - let info = file_ref.metadata()?; - if info.type_ != FileType::Dir { - return_errno!(ENOTDIR, ""); - } - let mut writer = unsafe { DirentBufWriter::new(buf) }; - loop { - let name = match file_ref.read_entry() { - Err(e) => { - let errno = e.errno(); - if errno == ENOENT { - break; - } - return Err(e.cause_err(|_| errno!(errno, "failed to read entry"))); - } - Ok(name) => name, - }; - // TODO: get ino and type from dirent - let dirent = LinuxDirent::::new(1, &name, DT_UNKNOWN); - if let Err(e) = writer.try_write(&dirent, &name) { - file_ref.seek(SeekFrom::Current(-1))?; - if writer.written_size == 0 { - return Err(e); - } else { - break; - } - } - } - Ok(writer.written_size) -} - -const DT_UNKNOWN: u8 = 0; - -#[repr(packed)] // Don't use 'C'. Or its size will align up to 8 bytes. -struct LinuxDirent { - /// Inode number - ino: u64, - /// Offset to next structure - offset: u64, - /// Size of this dirent - reclen: u16, - /// File type - type_: T, - /// Filename (null-terminated) - name: [u8; 0], -} - -impl LinuxDirent { - fn new(ino: u64, name: &str, d_type: u8) -> Self { - let ori_len = if !T::at_the_end_of_linux_dirent() { - core::mem::size_of::>() + name.len() + 1 - } else { - // pad the file type at the end - core::mem::size_of::>() + name.len() + 1 + core::mem::size_of::() - }; - let len = align_up(ori_len, 8); // align up to 8 bytes - Self { - ino, - offset: 0, - reclen: len as u16, - type_: T::set_type(d_type), - name: [], - } - } - - fn len(&self) -> usize { - self.reclen as usize - } -} - -impl Copy for LinuxDirent {} - -impl Clone for LinuxDirent { - fn clone(&self) -> Self { - Self { - ino: self.ino, - offset: self.offset, - reclen: self.reclen, - type_: self.type_, - name: self.name, - } - } -} - -trait DirentType { - fn set_type(d_type: u8) -> Self; - fn at_the_end_of_linux_dirent() -> bool; -} - -impl DirentType for u8 { - fn set_type(d_type: u8) -> Self { - d_type - } - fn at_the_end_of_linux_dirent() -> bool { - false - } -} -impl DirentType for () { - fn set_type(d_type: u8) -> Self { - Default::default() - } - fn at_the_end_of_linux_dirent() -> bool { - true - } -} - -struct DirentBufWriter<'a> { - buf: &'a mut [u8], - rest_size: usize, - written_size: usize, -} - -impl<'a> DirentBufWriter<'a> { - unsafe fn new(buf: &'a mut [u8]) -> Self { - let rest_size = buf.len(); - DirentBufWriter { - buf, - rest_size, - written_size: 0, - } - } - - fn try_write( - &mut self, - dirent: &LinuxDirent, - name: &str, - ) -> Result<()> { - if self.rest_size < dirent.len() { - return_errno!(EINVAL, "the given buffer is too small"); - } - unsafe { - let ptr = self.buf.as_mut_ptr().add(self.written_size) as *mut LinuxDirent; - ptr.write(*dirent); - let name_ptr = ptr.add(1) as _; - write_cstr(name_ptr, name); - if T::at_the_end_of_linux_dirent() { - // pad zero bytes and file type at the end - let mut ptr = name_ptr.add(name.len() + 1); - let mut rest_len = { - let written_len = core::mem::size_of::>() + name.len() + 1; - dirent.len() - written_len - }; - while rest_len > 1 { - ptr.write(0); - ptr = ptr.add(1); - rest_len -= 1; - } - // the last one is file type - ptr.write(DT_UNKNOWN); - } - } - self.rest_size -= dirent.len(); - self.written_size += dirent.len(); - Ok(()) - } -} - -/// Write a Rust string to C string -unsafe fn write_cstr(ptr: *mut u8, s: &str) { - ptr.copy_from(s.as_ptr(), s.len()); - ptr.add(s.len()).write(0); -} diff --git a/src/libos/src/fs/file_ops/getdents.rs b/src/libos/src/fs/file_ops/getdents.rs new file mode 100644 index 00000000..39f10c62 --- /dev/null +++ b/src/libos/src/fs/file_ops/getdents.rs @@ -0,0 +1,216 @@ +use super::*; +use core::marker::PhantomData; + +pub fn do_getdents64(fd: FileDesc, buf: &mut [u8]) -> Result { + getdents_common::(fd, buf) +} + +pub fn do_getdents(fd: FileDesc, buf: &mut [u8]) -> Result { + getdents_common::(fd, buf) +} + +fn getdents_common(fd: FileDesc, buf: &mut [u8]) -> Result { + debug!( + "getdents: fd: {}, buf: {:?}, buf_size: {}", + fd, + buf.as_ptr(), + buf.len(), + ); + + let file_ref = current!().file(fd)?; + let info = file_ref.metadata()?; + if info.type_ != FileType::Dir { + return_errno!(ENOTDIR, ""); + } + let mut writer = DirentBufWriter::::new(buf); + let written_size = file_ref.iterate_entries(&mut writer)?; + Ok(written_size) +} + +struct DirentBufWriter<'a, T: Dirent> { + buf: &'a mut [u8], + written_size: usize, + phantom: PhantomData, +} + +impl<'a, T: Dirent> DirentBufWriter<'a, T> { + fn new(buf: &'a mut [u8]) -> Self { + Self { + buf, + written_size: 0, + phantom: PhantomData, + } + } + + fn try_write(&mut self, name: &str, ino: u64, type_: FileType) -> Result { + let dirent: T = Dirent::new(name, ino, type_); + if self.buf.len() - self.written_size < dirent.rec_len() { + return_errno!(EINVAL, "the given buffer is too small"); + } + dirent.dump(&mut self.buf[self.written_size..], name, type_)?; + self.written_size += dirent.rec_len(); + Ok(dirent.rec_len()) + } +} + +impl<'a, T: Dirent> DirentWriter for DirentBufWriter<'a, T> { + fn write_entry( + &mut self, + name: &str, + ino: u64, + type_: FileType, + ) -> rcore_fs::vfs::Result { + let written_len = self + .try_write(&name, ino, type_) + .map_err(|_| FsError::InvalidParam)?; + Ok(written_len) + } +} + +trait Dirent { + fn new(name: &str, ino: u64, type_: FileType) -> Self; + fn rec_len(&self) -> usize; + fn dump(&self, buf: &mut [u8], name: &str, type_: FileType) -> Result<()>; +} + +/// Same with struct linux_dirent64 +#[repr(packed)] // Don't use 'C'. Or its size will align up to 8 bytes. +#[derive(Debug, Clone, Copy)] +struct LinuxDirent64 { + /// Inode number + pub ino: u64, + /// Offset to next structure + pub offset: u64, + /// Size of this dirent + pub rec_len: u16, + /// File type + pub type_: DirentType, + /// Filename (null-terminated) + pub name: [u8; 0], +} + +impl Dirent for LinuxDirent64 { + fn new(name: &str, ino: u64, type_: FileType) -> Self { + let ori_len = core::mem::size_of::() + name.len() + 1; + let len = align_up(ori_len, 8); // align up to 8 bytes + Self { + ino, + offset: 0, + rec_len: len as u16, + type_: DirentType::from_file_type(type_), + name: [], + } + } + + fn rec_len(&self) -> usize { + self.rec_len as usize + } + + fn dump(&self, buf: &mut [u8], name: &str, _type_: FileType) -> Result<()> { + unsafe { + let ptr = buf.as_mut_ptr() as *mut Self; + ptr.write(*self); + let name_ptr = ptr.add(1) as _; + write_cstr(name_ptr, name); + } + Ok(()) + } +} + +/// Same with struct linux_dirent +#[repr(packed)] // Don't use 'C'. Or its size will align up to 8 bytes. +#[derive(Debug, Clone, Copy)] +struct LinuxDirent { + /// Inode number + pub ino: u64, + /// Offset to next structure + pub offset: u64, + /// Size of this dirent + pub rec_len: u16, + /// Filename (null-terminated) + pub name: [u8; 0], + /* + /// Zero padding byte + pub pad: [u8], + /// File type + pub type_: DirentType, + */ +} + +impl Dirent for LinuxDirent { + fn new(name: &str, ino: u64, type_: FileType) -> Self { + let ori_len = + core::mem::size_of::() + name.len() + 1 + core::mem::size_of::(); + let len = align_up(ori_len, 8); // align up to 8 bytes + Self { + ino, + offset: 0, + rec_len: len as u16, + name: [], + } + } + + fn rec_len(&self) -> usize { + self.rec_len as usize + } + + fn dump(&self, buf: &mut [u8], name: &str, type_: FileType) -> Result<()> { + unsafe { + let ptr = buf.as_mut_ptr() as *mut Self; + ptr.write(*self); + let mut ptr = ptr.add(1) as *mut u8; + write_cstr(ptr, name); + // Pad zero bytes if necessary + ptr = ptr.add(name.len() + 1); + let mut remaining_len = { + let written_len = core::mem::size_of::() + name.len() + 1; + self.rec_len() - written_len + }; + while remaining_len > 1 { + ptr.write(0); + ptr = ptr.add(1); + remaining_len -= 1; + } + // Write the type at the end + let dirent_type = DirentType::from_file_type(type_); + ptr.write(dirent_type as u8); + } + Ok(()) + } +} + +#[allow(non_camel_case_types)] +#[derive(Debug, Clone, Copy)] +#[repr(u8)] +enum DirentType { + DT_UNKNOWN = 0, + DT_FIFO = 1, + DT_CHR = 2, + DT_DIR = 4, + DT_BLK = 6, + DT_REG = 8, + DT_LNK = 10, + DT_SOCK = 12, + DT_WHT = 14, +} + +impl DirentType { + fn from_file_type(file_type: FileType) -> DirentType { + match file_type { + FileType::File => DirentType::DT_REG, + FileType::Dir => DirentType::DT_DIR, + FileType::SymLink => DirentType::DT_LNK, + FileType::CharDevice => DirentType::DT_CHR, + FileType::BlockDevice => DirentType::DT_BLK, + FileType::Socket => DirentType::DT_SOCK, + FileType::NamedPipe => DirentType::DT_FIFO, + _ => DirentType::DT_UNKNOWN, + } + } +} + +/// Write a Rust string to C string +unsafe fn write_cstr(ptr: *mut u8, s: &str) { + ptr.copy_from(s.as_ptr(), s.len()); + ptr.add(s.len()).write(0); +} diff --git a/src/libos/src/fs/file_ops/mod.rs b/src/libos/src/fs/file_ops/mod.rs index c641fd23..60107432 100644 --- a/src/libos/src/fs/file_ops/mod.rs +++ b/src/libos/src/fs/file_ops/mod.rs @@ -5,7 +5,6 @@ pub use self::access::{do_faccessat, AccessibilityCheckFlags, AccessibilityCheck pub use self::chmod::{do_fchmod, do_fchmodat, FileMode}; pub use self::chown::{do_fchown, do_fchownat, ChownFlags}; pub use self::close::do_close; -pub use self::dirent::{do_getdents, do_getdents64}; pub use self::dup::{do_dup, do_dup2, do_dup3}; pub use self::fallocate::do_fallocate; pub use self::fcntl::{do_fcntl, FcntlCmd}; @@ -13,6 +12,7 @@ pub use self::file_flags::{AccessMode, CreationFlags, StatusFlags}; pub use self::flock::{Flock, FlockType}; pub use self::fspath::{FsPath, AT_FDCWD}; pub use self::fsync::{do_fdatasync, do_fsync}; +pub use self::getdents::{do_getdents, do_getdents64}; pub use self::ioctl::{ do_ioctl, occlum_ocall_ioctl, BuiltinIoctlNum, IfConf, IoctlCmd, StructuredIoctlArgType, StructuredIoctlNum, @@ -35,7 +35,6 @@ mod access; mod chmod; mod chown; mod close; -mod dirent; mod dup; mod fallocate; mod fcntl; @@ -43,6 +42,7 @@ mod file_flags; mod flock; mod fspath; mod fsync; +mod getdents; mod ioctl; mod link; mod lseek; diff --git a/src/libos/src/fs/hostfs.rs b/src/libos/src/fs/hostfs.rs index 0829de42..f5818767 100644 --- a/src/libos/src/fs/hostfs.rs +++ b/src/libos/src/fs/hostfs.rs @@ -3,6 +3,7 @@ use alloc::sync::{Arc, Weak}; use core::any::Any; use rcore_fs::vfs::*; use std::io::{Read, Seek, SeekFrom, Write}; +use std::os::unix::fs::{DirEntryExt, FileTypeExt}; use std::path::{Path, PathBuf}; use std::sync::{SgxMutex as Mutex, SgxMutexGuard as MutexGuard}; use std::untrusted::fs; @@ -209,6 +210,39 @@ impl INode for HNode { } } + fn iterate_entries(&self, ctx: &mut DirentWriterContext) -> Result { + if !self.path.is_dir() { + return Err(FsError::NotDir); + } + let idx = ctx.pos(); + let mut total_written_len = 0; + for entry in try_std!(self.path.read_dir()).skip(idx) { + let entry = try_std!(entry); + let written_len = match ctx.write_entry( + &entry + .file_name() + .into_string() + .map_err(|_| FsError::InvalidParam)?, + entry.ino(), + entry + .file_type() + .map_err(|_| FsError::InvalidParam)? + .into_fs_filetype(), + ) { + Ok(written_len) => written_len, + Err(e) => { + if total_written_len == 0 { + return Err(e); + } else { + break; + } + } + }; + total_written_len += written_len; + } + Ok(total_written_len) + } + fn io_control(&self, cmd: u32, data: usize) -> Result<()> { warn!("HostFS: io_control is unimplemented"); Ok(()) @@ -265,6 +299,32 @@ impl IntoFsError for std::io::Error { } } +trait IntoFsFileType { + fn into_fs_filetype(self) -> FileType; +} + +impl IntoFsFileType for fs::FileType { + fn into_fs_filetype(self) -> FileType { + if self.is_dir() { + FileType::Dir + } else if self.is_file() { + FileType::File + } else if self.is_symlink() { + FileType::SymLink + } else if self.is_block_device() { + FileType::BlockDevice + } else if self.is_char_device() { + FileType::CharDevice + } else if self.is_fifo() { + FileType::NamedPipe + } else if self.is_socket() { + FileType::Socket + } else { + unimplemented!("unknown file type") + } + } +} + trait IntoFsMetadata { fn into_fs_metadata(self) -> Metadata; } diff --git a/src/libos/src/fs/inode_file.rs b/src/libos/src/fs/inode_file.rs index 338f8ad2..1a914f68 100644 --- a/src/libos/src/fs/inode_file.rs +++ b/src/libos/src/fs/inode_file.rs @@ -144,14 +144,15 @@ impl File for INodeFile { Ok(()) } - fn read_entry(&self) -> Result { + fn iterate_entries(&self, writer: &mut dyn DirentWriter) -> Result { if !self.access_mode.readable() { return_errno!(EACCES, "File not readable. Can't read entry."); } let mut offset = self.offset.lock().unwrap(); - let name = self.inode.get_entry(*offset)?; - *offset += 1; - Ok(name) + let mut dir_ctx = DirentWriterContext::new(*offset, writer); + let written_size = self.inode.iterate_entries(&mut dir_ctx)?; + *offset = dir_ctx.pos(); + Ok(written_size) } fn access_mode(&self) -> Result { diff --git a/src/libos/src/fs/mod.rs b/src/libos/src/fs/mod.rs index efbf9150..e014fb4d 100644 --- a/src/libos/src/fs/mod.rs +++ b/src/libos/src/fs/mod.rs @@ -1,7 +1,10 @@ use super::*; use process; -use rcore_fs::vfs::{FileSystem, FileType, FsError, INode, Metadata, Timespec, PATH_MAX}; +use rcore_fs::vfs::{ + DirentWriter, DirentWriterContext, FileSystem, FileType, FsError, INode, Metadata, Timespec, + PATH_MAX, +}; use std; use std::any::Any; use std::fmt;