From 02731914dc8fb0e8808eba0a5a033de5d86ee34f Mon Sep 17 00:00:00 2001 From: LI Qing Date: Wed, 8 Mar 2023 15:08:31 +0800 Subject: [PATCH] Add support for getdents64 syscall --- .../fs/file_handle/inode_handle/dyn_cap.rs | 4 +- .../src/fs/file_handle/inode_handle/mod.rs | 13 +- .../fs/file_handle/inode_handle/static_cap.rs | 4 +- .../libs/jinux-std/src/fs/ramfs/fs.rs | 93 +++++---- .../jinux-std/src/fs/utils/dirent_visitor.rs | 32 ++++ .../jinux-std/src/fs/utils/dirent_writer.rs | 31 --- .../libs/jinux-std/src/fs/utils/inode.rs | 4 +- .../libs/jinux-std/src/fs/utils/mod.rs | 4 +- .../libs/jinux-std/src/fs/utils/vnode.rs | 6 +- .../libs/jinux-std/src/syscall/getdents64.rs | 179 ++++++++++++++++++ .../libs/jinux-std/src/syscall/mod.rs | 4 + 11 files changed, 276 insertions(+), 98 deletions(-) create mode 100644 src/services/libs/jinux-std/src/fs/utils/dirent_visitor.rs delete mode 100644 src/services/libs/jinux-std/src/fs/utils/dirent_writer.rs create mode 100644 src/services/libs/jinux-std/src/syscall/getdents64.rs diff --git a/src/services/libs/jinux-std/src/fs/file_handle/inode_handle/dyn_cap.rs b/src/services/libs/jinux-std/src/fs/file_handle/inode_handle/dyn_cap.rs index 626c57cd7..b7c5c7540 100644 --- a/src/services/libs/jinux-std/src/fs/file_handle/inode_handle/dyn_cap.rs +++ b/src/services/libs/jinux-std/src/fs/file_handle/inode_handle/dyn_cap.rs @@ -57,11 +57,11 @@ impl InodeHandle { self.0.write(buf) } - pub fn readdir(&self, writer: &mut dyn DirentWriter) -> Result { + pub fn readdir(&self, visitor: &mut dyn DirentVisitor) -> Result { if !self.1.contains(Rights::READ) { return_errno_with_message!(Errno::EBADF, "File is not readable"); } - self.0.readdir(writer) + self.0.readdir(visitor) } } diff --git a/src/services/libs/jinux-std/src/fs/file_handle/inode_handle/mod.rs b/src/services/libs/jinux-std/src/fs/file_handle/inode_handle/mod.rs index a5132b6c8..8610379c5 100644 --- a/src/services/libs/jinux-std/src/fs/file_handle/inode_handle/mod.rs +++ b/src/services/libs/jinux-std/src/fs/file_handle/inode_handle/mod.rs @@ -3,9 +3,7 @@ mod dyn_cap; mod static_cap; -use crate::fs::utils::{ - AccessMode, Dentry, DirentWriter, DirentWriterContext, InodeType, SeekFrom, StatusFlags, -}; +use crate::fs::utils::{AccessMode, Dentry, DirentVisitor, InodeType, SeekFrom, StatusFlags}; use crate::prelude::*; use crate::rights::Rights; @@ -113,12 +111,11 @@ impl InodeHandle_ { status_flags.insert(new_status_flags & valid_flags_mask); } - pub fn readdir(&self, writer: &mut dyn DirentWriter) -> Result { + pub fn readdir(&self, visitor: &mut dyn DirentVisitor) -> Result { let mut offset = self.offset.lock(); - let mut dir_writer_ctx = DirentWriterContext::new(*offset, writer); - let written_size = self.dentry.vnode().readdir(&mut dir_writer_ctx)?; - *offset = dir_writer_ctx.pos(); - Ok(written_size) + let read_cnt = self.dentry.vnode().readdir_at(*offset, visitor)?; + *offset += read_cnt; + Ok(read_cnt) } } diff --git a/src/services/libs/jinux-std/src/fs/file_handle/inode_handle/static_cap.rs b/src/services/libs/jinux-std/src/fs/file_handle/inode_handle/static_cap.rs index 37872b4e0..0e4ca9c8f 100644 --- a/src/services/libs/jinux-std/src/fs/file_handle/inode_handle/static_cap.rs +++ b/src/services/libs/jinux-std/src/fs/file_handle/inode_handle/static_cap.rs @@ -21,7 +21,7 @@ impl InodeHandle { } #[require(R > Read)] - pub fn readdir(&self, writer: &mut dyn DirentWriter) -> Result { - self.0.readdir(writer) + pub fn readdir(&self, visitor: &mut dyn DirentVisitor) -> Result { + self.0.readdir(visitor) } } diff --git a/src/services/libs/jinux-std/src/fs/ramfs/fs.rs b/src/services/libs/jinux-std/src/fs/ramfs/fs.rs index 4ee181b17..e2fb8f90e 100644 --- a/src/services/libs/jinux-std/src/fs/ramfs/fs.rs +++ b/src/services/libs/jinux-std/src/fs/ramfs/fs.rs @@ -9,7 +9,7 @@ use spin::{RwLock, RwLockWriteGuard}; use super::*; use crate::fs::utils::{ - DirentWriterContext, FileSystem, Inode, InodeMode, InodeType, IoctlCmd, Metadata, SuperBlock, + DirentVisitor, FileSystem, Inode, InodeMode, InodeType, IoctlCmd, Metadata, SuperBlock, }; pub struct RamFS { @@ -144,32 +144,6 @@ struct DirEntry { parent: Weak, } -macro_rules! write_inode_entry { - ($ctx:expr, $name:expr, $inode:expr, $total_written:expr) => { - let ctx = $ctx; - let name = $name; - let inode = $inode; - let total_written = $total_written; - - match ctx.write_entry( - name.as_ref(), - inode.metadata().ino as u64, - inode.metadata().type_, - ) { - Ok(written_len) => { - *total_written += written_len; - } - Err(e) => { - if *total_written == 0 { - return Err(e); - } else { - return Ok(*total_written); - } - } - } - }; -} - impl DirEntry { fn new() -> Self { Self { @@ -228,24 +202,47 @@ impl DirEntry { *name = Str256::from(new_name); } - fn iterate_entries(&self, mut ctx: &mut DirentWriterContext) -> Result { - let mut total_written_len = 0; - let idx = ctx.pos(); - // Write the two special entries - if idx == 0 { - let this_inode = self.this.upgrade().unwrap(); - write_inode_entry!(&mut ctx, ".", &this_inode, &mut total_written_len); + fn visit_entry(&self, mut idx: usize, visitor: &mut dyn DirentVisitor) -> Result { + let try_visit = |idx: &mut usize, visitor: &mut dyn DirentVisitor| -> Result<()> { + // Read the two special entries("." and ".."). + if *idx == 0 { + let this_inode = self.this.upgrade().unwrap(); + visitor.visit( + ".", + this_inode.metadata().ino as u64, + this_inode.metadata().type_, + *idx, + )?; + *idx += 1; + } + if *idx == 1 { + let parent_inode = self.parent.upgrade().unwrap(); + visitor.visit( + "..", + parent_inode.metadata().ino as u64, + parent_inode.metadata().type_, + *idx, + )?; + *idx += 1; + } + // Read the normal child entries. + for (name, child) in self.children.iter().skip(*idx - 2) { + visitor.visit( + name.as_ref(), + child.metadata().ino as u64, + child.metadata().type_, + *idx, + )?; + *idx += 1; + } + Ok(()) + }; + + let initial_idx = idx; + match try_visit(&mut idx, visitor) { + Err(e) if idx == initial_idx => Err(e), + _ => Ok(idx - initial_idx), } - if idx <= 1 { - let parent_inode = self.parent.upgrade().unwrap(); - write_inode_entry!(&mut ctx, "..", &parent_inode, &mut total_written_len); - } - // Write the normal entries - let skipped_children = if idx < 2 { 0 } else { idx - 2 }; - for (name, child) in self.children.iter().skip(skipped_children) { - write_inode_entry!(&mut ctx, name, child, &mut total_written_len); - } - Ok(total_written_len) } fn is_empty_children(&self) -> bool { @@ -382,17 +379,17 @@ impl Inode for RamInode { Ok(new_inode) } - fn readdir(&self, ctx: &mut DirentWriterContext) -> Result { + fn readdir_at(&self, offset: usize, visitor: &mut dyn DirentVisitor) -> Result { if self.0.read().metadata.type_ != InodeType::Dir { return_errno_with_message!(Errno::ENOTDIR, "self is not dir"); } let self_inode = self.0.read(); - let total_written_len = self_inode + let cnt = self_inode .inner .as_direntry() .unwrap() - .iterate_entries(ctx)?; - Ok(total_written_len) + .visit_entry(offset, visitor)?; + Ok(cnt) } fn link(&self, old: &Arc, name: &str) -> Result<()> { diff --git a/src/services/libs/jinux-std/src/fs/utils/dirent_visitor.rs b/src/services/libs/jinux-std/src/fs/utils/dirent_visitor.rs new file mode 100644 index 000000000..2bd2642ce --- /dev/null +++ b/src/services/libs/jinux-std/src/fs/utils/dirent_visitor.rs @@ -0,0 +1,32 @@ +use super::InodeType; +use crate::prelude::*; + +/// A visitor for dir entries. +pub trait DirentVisitor { + /// Visit a dir entry. + /// + /// If the visitor succeeds in visiting the given inode, an `Ok(())` is returned; + /// Otherwise, an error is returned. Different implementations for `DirentVisitor` + /// may choose to report errors for different reasons. Regardless of the exact + /// errors and reasons, `readdir`-family methods shall stop feeding the visitor + /// with the next inode as long as an error is returned by the visitor. + /// + /// # Example + /// + /// `Vec` is implemented as `DirentVisitor` so that the file names + /// under a dir can be easily collected, which is convenient for testing purposes. + /// + /// ```no_run + /// let mut all_dirents = Vec::new(); + /// let dir_inode = todo!("create an inode"); + /// dir_inode.readdir_at(0, &mut all_dirents).unwrap(); + /// ``` + fn visit(&mut self, name: &str, ino: u64, type_: InodeType, offset: usize) -> Result<()>; +} + +impl DirentVisitor for Vec { + fn visit(&mut self, name: &str, ino: u64, type_: InodeType, offset: usize) -> Result<()> { + self.push(name.into()); + Ok(()) + } +} diff --git a/src/services/libs/jinux-std/src/fs/utils/dirent_writer.rs b/src/services/libs/jinux-std/src/fs/utils/dirent_writer.rs deleted file mode 100644 index cb07ad66c..000000000 --- a/src/services/libs/jinux-std/src/fs/utils/dirent_writer.rs +++ /dev/null @@ -1,31 +0,0 @@ -use super::InodeType; -use crate::prelude::*; - -/// DirentWriterContext is a wrapper of DirentWriter with directory position -/// After a successful write, the position increases correspondingly -pub struct DirentWriterContext<'a> { - pos: usize, - writer: &'a mut dyn DirentWriter, -} - -impl<'a> DirentWriterContext<'a> { - pub fn new(pos: usize, writer: &'a mut dyn DirentWriter) -> Self { - Self { pos, writer } - } - - pub fn write_entry(&mut self, name: &str, ino: u64, type_: InodeType) -> Result { - let written_len = self.writer.write_entry(name, ino, type_)?; - self.pos += 1; - Ok(written_len) - } - - pub fn pos(&self) -> usize { - self.pos - } -} - -/// DirentWriter is used to write directory entry, -/// the object which implements it can decide how to format the data -pub trait DirentWriter: Sync + Send { - fn write_entry(&mut self, name: &str, ino: u64, type_: InodeType) -> Result; -} diff --git a/src/services/libs/jinux-std/src/fs/utils/inode.rs b/src/services/libs/jinux-std/src/fs/utils/inode.rs index b0690b519..ab255558a 100644 --- a/src/services/libs/jinux-std/src/fs/utils/inode.rs +++ b/src/services/libs/jinux-std/src/fs/utils/inode.rs @@ -5,7 +5,7 @@ use core::any::Any; use core::time::Duration; use jinux_frame::vm::VmFrame; -use super::{DirentWriterContext, FileSystem, IoctlCmd, SuperBlock}; +use super::{DirentVisitor, FileSystem, IoctlCmd, SuperBlock}; use crate::prelude::*; #[repr(u32)] @@ -177,7 +177,7 @@ pub trait Inode: Any + Sync + Send { fn mknod(&self, name: &str, type_: InodeType, mode: InodeMode) -> Result>; - fn readdir(&self, ctx: &mut DirentWriterContext) -> Result; + fn readdir_at(&self, offset: usize, visitor: &mut dyn DirentVisitor) -> Result; fn link(&self, old: &Arc, name: &str) -> Result<()>; diff --git a/src/services/libs/jinux-std/src/fs/utils/mod.rs b/src/services/libs/jinux-std/src/fs/utils/mod.rs index f461d103b..14ca8338a 100644 --- a/src/services/libs/jinux-std/src/fs/utils/mod.rs +++ b/src/services/libs/jinux-std/src/fs/utils/mod.rs @@ -3,7 +3,7 @@ pub use access_mode::AccessMode; pub use creation_flags::CreationFlags; pub use dentry_cache::Dentry; -pub use dirent_writer::{DirentWriter, DirentWriterContext}; +pub use dirent_visitor::DirentVisitor; pub use events::IoEvents; pub use fcntl::FcntlCmd; pub use file_creation_mask::FileCreationMask; @@ -18,7 +18,7 @@ pub use vnode::Vnode; mod access_mode; mod creation_flags; mod dentry_cache; -mod dirent_writer; +mod dirent_visitor; mod events; mod fcntl; mod file_creation_mask; diff --git a/src/services/libs/jinux-std/src/fs/utils/vnode.rs b/src/services/libs/jinux-std/src/fs/utils/vnode.rs index 1a3687c3b..a3adb7cc8 100644 --- a/src/services/libs/jinux-std/src/fs/utils/vnode.rs +++ b/src/services/libs/jinux-std/src/fs/utils/vnode.rs @@ -1,4 +1,4 @@ -use super::{DirentWriterContext, Inode, InodeMode, InodeType, Metadata, PageCacheManager}; +use super::{DirentVisitor, Inode, InodeMode, InodeType, Metadata, PageCacheManager}; use crate::prelude::*; use crate::rights::Rights; use crate::vm::vmo::{Vmo, VmoFlags, VmoOptions}; @@ -164,8 +164,8 @@ impl Vnode { self.inner.write().inode.write_link(target) } - pub fn readdir(&self, ctx: &mut DirentWriterContext) -> Result { - self.inner.read().inode.readdir(ctx) + pub fn readdir_at(&self, offset: usize, visitor: &mut dyn DirentVisitor) -> Result { + self.inner.read().inode.readdir_at(offset, visitor) } pub fn metadata(&self) -> Metadata { diff --git a/src/services/libs/jinux-std/src/syscall/getdents64.rs b/src/services/libs/jinux-std/src/syscall/getdents64.rs new file mode 100644 index 000000000..5edcf6f5a --- /dev/null +++ b/src/services/libs/jinux-std/src/syscall/getdents64.rs @@ -0,0 +1,179 @@ +use crate::fs::{ + file_table::FileDescripter, + utils::{DirentVisitor, InodeType}, +}; +use crate::log_syscall_entry; +use crate::prelude::*; +use crate::util::write_bytes_to_user; +use core::marker::PhantomData; + +use super::SyscallReturn; +use super::SYS_GETDENTS64; + +pub fn sys_getdents64( + fd: FileDescripter, + buf_addr: Vaddr, + buf_len: usize, +) -> Result { + log_syscall_entry!(SYS_GETDENTS64); + debug!( + "fd = {}, buf_addr = 0x{:x}, buf_len = 0x{:x}", + fd, buf_addr, buf_len + ); + + let current = current!(); + let file_table = current.file_table().lock(); + let file = file_table.get_file(fd)?; + let inode_handle = file + .as_inode_handle() + .ok_or(Error::with_message(Errno::EBADE, "not inode"))?; + if inode_handle.dentry().inode_type() != InodeType::Dir { + return_errno!(Errno::ENOTDIR); + } + let mut buffer = vec![0u8; buf_len]; + let mut reader = DirentBufferReader::::new(&mut buffer); + let _ = inode_handle.readdir(&mut reader)?; + let read_len = reader.read_len(); + write_bytes_to_user(buf_addr, &buffer[..read_len])?; + Ok(SyscallReturn::Return(read_len as _)) +} + +/// The Buffered DirentReader to visit the dir entry. +/// The DirentSerializer T decides how to serialize the data. +struct DirentBufferReader<'a, T: DirentSerializer> { + buffer: &'a mut [u8], + read_len: usize, + phantom: PhantomData, +} + +impl<'a, T: DirentSerializer> DirentBufferReader<'a, T> { + pub fn new(buffer: &'a mut [u8]) -> Self { + Self { + buffer, + read_len: 0, + phantom: PhantomData, + } + } + + pub fn read_len(&self) -> usize { + self.read_len + } +} + +impl<'a, T: DirentSerializer> DirentVisitor for DirentBufferReader<'a, T> { + fn visit(&mut self, name: &str, ino: u64, type_: InodeType, offset: usize) -> Result<()> { + let dirent_serializer = T::new(ino, offset as u64, type_, CString::new(name)?); + if self.read_len >= self.buffer.len() { + return_errno_with_message!(Errno::EINVAL, "buffer is too small"); + } + dirent_serializer.serialize(&mut self.buffer[self.read_len..])?; + self.read_len += dirent_serializer.len(); + Ok(()) + } +} + +/// The DirentSerializer can decide how to serialize the data. +trait DirentSerializer { + /// Create a DirentSerializer. + fn new(ino: u64, offset: u64, type_: InodeType, name: CString) -> Self; + /// Get the length of a directory entry. + fn len(&self) -> usize; + /// Try to serialize a directory entry into buffer. + fn serialize(&self, buf: &mut [u8]) -> Result<()>; +} + +#[derive(Debug)] +struct Dirent64 { + inner: Dirent64Inner, + name: CString, +} + +#[repr(packed)] +#[derive(Debug, Clone, Copy)] +struct Dirent64Inner { + d_ino: u64, + d_off: u64, + d_reclen: u16, + d_type: u8, +} + +impl DirentSerializer for Dirent64 { + fn new(ino: u64, offset: u64, type_: InodeType, name: CString) -> Self { + let d_reclen = { + let len = + core::mem::size_of::() + name.as_c_str().to_bytes_with_nul().len(); + align_up(len, 8) as u16 + }; + let d_type = DirentType::from(type_) as u8; + Self { + inner: Dirent64Inner { + d_ino: ino, + d_off: offset, + d_reclen, + d_type, + }, + name, + } + } + + fn len(&self) -> usize { + self.inner.d_reclen as usize + } + + fn serialize(&self, buf: &mut [u8]) -> Result<()> { + if self.len() > buf.len() { + return_errno_with_message!(Errno::EINVAL, "buffer is too small"); + } + + let d_ino = self.inner.d_ino; + let d_off = self.inner.d_off; + let d_reclen = self.inner.d_reclen; + let d_type = self.inner.d_type; + let items: [&[u8]; 5] = [ + d_ino.as_bytes(), + d_off.as_bytes(), + d_reclen.as_bytes(), + d_type.as_bytes(), + self.name.as_c_str().to_bytes_with_nul(), + ]; + let mut offset = 0; + for item in items { + buf[offset..offset + item.len()].copy_from_slice(item); + offset += item.len(); + } + Ok(()) + } +} + +#[allow(non_camel_case_types)] +#[repr(u8)] +#[derive(Debug, Clone, Copy)] +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 From for DirentType { + fn from(type_: InodeType) -> Self { + match type_ { + InodeType::File => DirentType::DT_REG, + InodeType::Dir => DirentType::DT_DIR, + InodeType::SymLink => DirentType::DT_LNK, + InodeType::CharDevice => DirentType::DT_CHR, + InodeType::BlockDevice => DirentType::DT_BLK, + InodeType::Socket => DirentType::DT_SOCK, + InodeType::NamedPipe => DirentType::DT_FIFO, + } + } +} + +fn align_up(size: usize, align: usize) -> usize { + (size + align - 1) & !(align - 1) +} diff --git a/src/services/libs/jinux-std/src/syscall/mod.rs b/src/services/libs/jinux-std/src/syscall/mod.rs index edd8e7967..1f98f3e09 100644 --- a/src/services/libs/jinux-std/src/syscall/mod.rs +++ b/src/services/libs/jinux-std/src/syscall/mod.rs @@ -16,6 +16,7 @@ use crate::syscall::fcntl::sys_fcntl; use crate::syscall::fork::sys_fork; use crate::syscall::futex::sys_futex; use crate::syscall::getcwd::sys_getcwd; +use crate::syscall::getdents64::sys_getdents64; use crate::syscall::getegid::sys_getegid; use crate::syscall::geteuid::sys_geteuid; use crate::syscall::getgid::sys_getgid; @@ -79,6 +80,7 @@ mod fcntl; mod fork; mod futex; mod getcwd; +mod getdents64; mod getegid; mod geteuid; mod getgid; @@ -212,6 +214,7 @@ define_syscall_nums!( SYS_GETTID = 186, SYS_TIME = 201, SYS_FUTEX = 202, + SYS_GETDENTS64 = 217, SYS_SET_TID_ADDRESS = 218, SYS_CLOCK_NANOSLEEP = 230, SYS_EXIT_GROUP = 231, @@ -342,6 +345,7 @@ pub fn syscall_dispatch( SYS_GETTID => syscall_handler!(0, sys_gettid), SYS_TIME => syscall_handler!(1, sys_time, args), SYS_FUTEX => syscall_handler!(6, sys_futex, args), + SYS_GETDENTS64 => syscall_handler!(3, sys_getdents64, args), SYS_SET_TID_ADDRESS => syscall_handler!(1, sys_set_tid_address, args), SYS_CLOCK_NANOSLEEP => syscall_handler!(4, sys_clock_nanosleep, args), SYS_EXIT_GROUP => syscall_handler!(1, sys_exit_group, args),