diff --git a/src/services/libs/jinux-std/src/fs/file_table.rs b/src/services/libs/jinux-std/src/fs/file_table.rs index 5e9a9186..cc47f45c 100644 --- a/src/services/libs/jinux-std/src/fs/file_table.rs +++ b/src/services/libs/jinux-std/src/fs/file_table.rs @@ -1,3 +1,4 @@ +use crate::events::{Events, Observer, Subject}; use crate::prelude::*; use super::{ @@ -7,15 +8,16 @@ use super::{ pub type FileDescripter = i32; -#[derive(Clone)] pub struct FileTable { table: BTreeMap, + subject: Subject, } impl FileTable { pub fn new() -> Self { Self { table: BTreeMap::new(), + subject: Subject::new(), } } @@ -27,7 +29,10 @@ impl FileTable { table.insert(FD_STDIN, FileHandle::new_file(Arc::new(stdin))); table.insert(FD_STDOUT, FileHandle::new_file(Arc::new(stdout))); table.insert(FD_STDERR, FileHandle::new_file(Arc::new(stderr))); - Self { table } + Self { + table, + subject: Subject::new(), + } } pub fn dup(&mut self, fd: FileDescripter, new_fd: Option) -> Result<()> { @@ -59,11 +64,19 @@ impl FileTable { } pub fn insert_at(&mut self, fd: FileDescripter, item: FileHandle) -> Option { - self.table.insert(fd, item) + let file = self.table.insert(fd, item); + if file.is_some() { + self.notify_close_fd_event(fd); + } + file } pub fn close_file(&mut self, fd: FileDescripter) -> Option { - self.table.remove(&fd) + let file = self.table.remove(&fd); + if file.is_some() { + self.notify_close_fd_event(fd); + } + file } pub fn get_file(&self, fd: FileDescripter) -> Result<&FileHandle> { @@ -71,4 +84,45 @@ impl FileTable { .get(&fd) .ok_or(Error::with_message(Errno::EBADF, "fd not exits")) } + + pub fn fds_and_files(&self) -> impl Iterator { + self.table.iter() + } + + pub fn register_observer(&self, observer: Weak>) { + self.subject.register_observer(observer); + } + + pub fn unregister_observer(&self, observer: Weak>) { + self.subject.unregister_observer(observer); + } + + fn notify_close_fd_event(&self, fd: FileDescripter) { + let events = FdEvents::Close(fd); + self.subject.notify_observers(&events); + } } + +impl Clone for FileTable { + fn clone(&self) -> Self { + Self { + table: self.table.clone(), + subject: Subject::new(), + } + } +} + +impl Drop for FileTable { + fn drop(&mut self) { + let events = FdEvents::DropFileTable; + self.subject.notify_observers(&events); + } +} + +#[derive(Copy, Clone)] +pub enum FdEvents { + Close(FileDescripter), + DropFileTable, +} + +impl Events for FdEvents {} diff --git a/src/services/libs/jinux-std/src/fs/fs_resolver.rs b/src/services/libs/jinux-std/src/fs/fs_resolver.rs index 6b580321..c9fa8f89 100644 --- a/src/services/libs/jinux-std/src/fs/fs_resolver.rs +++ b/src/services/libs/jinux-std/src/fs/fs_resolver.rs @@ -4,6 +4,7 @@ use alloc::string::String; use super::file_handle::InodeHandle; use super::file_table::FileDescripter; +use super::procfs::ProcFS; use super::ramfs::RamFS; use super::utils::{ AccessMode, CreationFlags, Dentry, FileSystem, InodeMode, InodeType, StatusFlags, Vnode, @@ -19,6 +20,11 @@ lazy_static! { } init().unwrap() }; + static ref PROC_FS: Arc = ProcFS::new(); + static ref PROC_DENTRY: Arc = { + let vnode = Vnode::new(PROC_FS.root_inode()).unwrap(); + Dentry::new_root(vnode) + }; } pub struct FsResolver { @@ -127,7 +133,21 @@ impl FsResolver { fn lookup_inner(&self, path: &FsPath, follow_tail_link: bool) -> Result> { let dentry = match path.inner { FsPathInner::Absolute(path) => { - self.lookup_from_parent(&self.root, path.trim_start_matches('/'), follow_tail_link)? + // TODO: Mount procfs at "/proc" if mount feature is ready + if path.starts_with("/proc") { + let path = path.strip_prefix("/proc").unwrap(); + self.lookup_from_parent( + &PROC_DENTRY, + path.trim_start_matches('/'), + follow_tail_link, + )? + } else { + self.lookup_from_parent( + &self.root, + path.trim_start_matches('/'), + follow_tail_link, + )? + } } FsPathInner::CwdRelative(path) => { self.lookup_from_parent(&self.cwd, path, follow_tail_link)? diff --git a/src/services/libs/jinux-std/src/fs/mod.rs b/src/services/libs/jinux-std/src/fs/mod.rs index ed7305f6..021b7195 100644 --- a/src/services/libs/jinux-std/src/fs/mod.rs +++ b/src/services/libs/jinux-std/src/fs/mod.rs @@ -2,6 +2,7 @@ pub mod file_handle; pub mod file_table; pub mod fs_resolver; pub mod initramfs; +pub mod procfs; pub mod ramfs; pub mod stdio; pub mod utils; diff --git a/src/services/libs/jinux-std/src/fs/procfs/mod.rs b/src/services/libs/jinux-std/src/fs/procfs/mod.rs new file mode 100644 index 00000000..4bd26cec --- /dev/null +++ b/src/services/libs/jinux-std/src/fs/procfs/mod.rs @@ -0,0 +1,125 @@ +use alloc::string::{String, ToString}; +use core::any::Any; +use core::sync::atomic::{AtomicUsize, Ordering}; + +use crate::events::Observer; +use crate::fs::utils::{DirEntryVecExt, FileSystem, FsFlags, Inode, SuperBlock, NAME_MAX}; +use crate::prelude::*; +use crate::process::{process_table, process_table::PidEvent, Pid}; + +use self::pid::PidDirOps; +use self::self_::SelfSymOps; +use self::template::{DirOps, ProcDir, ProcDirBuilder, ProcSymBuilder, SymOps}; + +mod pid; +mod self_; +mod template; + +/// Magic number. +const PROC_MAGIC: usize = 0x9fa0; +/// Root Inode ID. +const PROC_ROOT_INO: usize = 1; +/// Block size. +const BLOCK_SIZE: usize = 1024; + +pub struct ProcFS { + sb: RwLock, + root: RwLock>>, + inode_allocator: AtomicUsize, +} + +impl ProcFS { + pub fn new() -> Arc { + let procfs = { + let sb = SuperBlock::new(PROC_MAGIC, BLOCK_SIZE, NAME_MAX); + Arc::new(Self { + sb: RwLock::new(sb), + root: RwLock::new(None), + inode_allocator: AtomicUsize::new(PROC_ROOT_INO), + }) + }; + + let root = RootDirOps::new_inode(&procfs); + *procfs.root.write() = Some(root); + procfs + } + + pub(in crate::fs::procfs) fn alloc_id(&self) -> usize { + let next_id = self.inode_allocator.fetch_add(1, Ordering::SeqCst); + next_id + } +} + +impl FileSystem for ProcFS { + fn sync(&self) -> Result<()> { + Ok(()) + } + + fn root_inode(&self) -> Arc { + self.root.read().as_ref().unwrap().clone() + } + + fn sb(&self) -> SuperBlock { + self.sb.read().clone() + } + + fn flags(&self) -> FsFlags { + FsFlags::NO_PAGECACHE + } + + fn as_any_ref(&self) -> &dyn Any { + self + } +} + +/// Represents the inode at `/proc`. +struct RootDirOps; + +impl RootDirOps { + pub fn new_inode(fs: &Arc) -> Arc { + let root_inode = ProcDirBuilder::new(Self).fs(fs.clone()).build().unwrap(); + let weak_ptr = Arc::downgrade(&root_inode); + process_table::register_observer(weak_ptr); + root_inode + } +} + +impl Observer for ProcDir { + fn on_events(&self, events: &PidEvent) { + let PidEvent::Exit(pid) = events; + let mut cached_children = self.cached_children().write(); + cached_children.remove_entry_by_name(&pid.to_string()); + } +} + +impl DirOps for RootDirOps { + fn lookup_child(&self, this_ptr: Weak, name: &str) -> Result> { + let child = if name == "self" { + SelfSymOps::new_inode(this_ptr.clone()) + } else if let Ok(pid) = name.parse::() { + let process_ref = + process_table::pid_to_process(pid).ok_or_else(|| Error::new(Errno::ENOENT))?; + PidDirOps::new_inode(process_ref, this_ptr.clone()) + } else { + return_errno!(Errno::ENOENT); + }; + Ok(child) + } + + fn populate_children(&self, this_ptr: Weak) { + let this = { + let this = this_ptr.upgrade().unwrap(); + this.downcast_ref::>().unwrap().this() + }; + let mut cached_children = this.cached_children().write(); + cached_children.put_entry_if_not_found("self", || SelfSymOps::new_inode(this_ptr.clone())); + + let processes = process_table::get_all_processes(); + for process in processes { + let pid = process.pid().to_string(); + cached_children.put_entry_if_not_found(&pid, || { + PidDirOps::new_inode(process.clone(), this_ptr.clone()) + }); + } + } +} diff --git a/src/services/libs/jinux-std/src/fs/procfs/pid/comm.rs b/src/services/libs/jinux-std/src/fs/procfs/pid/comm.rs new file mode 100644 index 00000000..24d65dd2 --- /dev/null +++ b/src/services/libs/jinux-std/src/fs/procfs/pid/comm.rs @@ -0,0 +1,30 @@ +use super::*; + +/// Represents the inode at `/proc/[pid]/comm`. +pub struct CommFileOps(Arc); + +impl CommFileOps { + pub fn new_inode(process_ref: Arc, parent: Weak) -> Arc { + ProcFileBuilder::new(Self(process_ref)) + .parent(parent) + .build() + .unwrap() + } +} + +impl FileOps for CommFileOps { + fn data(&self) -> Result> { + let mut comm_output = { + let exe_path = self.0.executable_path().read(); + let last_component = exe_path.rsplit('/').next().unwrap_or(&exe_path); + let mut comm = last_component.as_bytes().to_vec(); + comm.push(b'\0'); + comm.truncate(TASK_COMM_LEN); + comm + }; + comm_output.push(b'\n'); + Ok(comm_output) + } +} + +const TASK_COMM_LEN: usize = 16; diff --git a/src/services/libs/jinux-std/src/fs/procfs/pid/exe.rs b/src/services/libs/jinux-std/src/fs/procfs/pid/exe.rs new file mode 100644 index 00000000..5818586f --- /dev/null +++ b/src/services/libs/jinux-std/src/fs/procfs/pid/exe.rs @@ -0,0 +1,19 @@ +use super::*; + +/// Represents the inode at `/proc/[pid]/exe`. +pub struct ExeSymOps(Arc); + +impl ExeSymOps { + pub fn new_inode(process_ref: Arc, parent: Weak) -> Arc { + ProcSymBuilder::new(Self(process_ref)) + .parent(parent) + .build() + .unwrap() + } +} + +impl SymOps for ExeSymOps { + fn read_link(&self) -> Result { + Ok(self.0.executable_path().read().clone()) + } +} diff --git a/src/services/libs/jinux-std/src/fs/procfs/pid/fd.rs b/src/services/libs/jinux-std/src/fs/procfs/pid/fd.rs new file mode 100644 index 00000000..c7778ae1 --- /dev/null +++ b/src/services/libs/jinux-std/src/fs/procfs/pid/fd.rs @@ -0,0 +1,88 @@ +use super::*; +use crate::fs::file_handle::FileHandle; +use crate::fs::file_table::FileDescripter; + +/// Represents the inode at `/proc/[pid]/fd`. +pub struct FdDirOps(Arc); + +impl FdDirOps { + pub fn new_inode(process_ref: Arc, parent: Weak) -> Arc { + let fd_inode = ProcDirBuilder::new(Self(process_ref.clone())) + .parent(parent) + .build() + .unwrap(); + let file_table = process_ref.file_table().lock(); + let weak_ptr = Arc::downgrade(&fd_inode); + file_table.register_observer(weak_ptr); + fd_inode + } +} + +impl Observer for ProcDir { + fn on_events(&self, events: &FdEvents) { + let fd_string = if let FdEvents::Close(fd) = events { + fd.to_string() + } else { + return; + }; + + let mut cached_children = self.cached_children().write(); + cached_children.remove_entry_by_name(&fd_string); + } +} + +impl DirOps for FdDirOps { + fn lookup_child(&self, this_ptr: Weak, name: &str) -> Result> { + let file = { + let fd = name + .parse::() + .map_err(|_| Error::new(Errno::ENOENT))?; + let file_table = self.0.file_table().lock(); + file_table + .get_file(fd) + .map_err(|_| Error::new(Errno::ENOENT))? + .clone() + }; + Ok(FileSymOps::new_inode(file, this_ptr.clone())) + } + + fn populate_children(&self, this_ptr: Weak) { + let this = { + let this = this_ptr.upgrade().unwrap(); + this.downcast_ref::>().unwrap().this() + }; + let file_table = self.0.file_table().lock(); + let mut cached_children = this.cached_children().write(); + for (fd, file) in file_table.fds_and_files() { + cached_children.put_entry_if_not_found(&fd.to_string(), || { + FileSymOps::new_inode(file.clone(), this_ptr.clone()) + }); + } + } +} + +/// Represents the inode at `/proc/[pid]/fd/N`. +struct FileSymOps(FileHandle); + +impl FileSymOps { + pub fn new_inode(file: FileHandle, parent: Weak) -> Arc { + ProcSymBuilder::new(Self(file)) + .parent(parent) + .build() + .unwrap() + } +} + +impl SymOps for FileSymOps { + fn read_link(&self) -> Result { + let path = if let Some(inode_handle) = self.0.as_inode_handle() { + inode_handle.dentry().abs_path() + } else if let Some(file) = self.0.as_file() { + // TODO: get the real path for stdio + String::from("/dev/tty") + } else { + unreachable!() + }; + Ok(path) + } +} diff --git a/src/services/libs/jinux-std/src/fs/procfs/pid/mod.rs b/src/services/libs/jinux-std/src/fs/procfs/pid/mod.rs new file mode 100644 index 00000000..da4548e2 --- /dev/null +++ b/src/services/libs/jinux-std/src/fs/procfs/pid/mod.rs @@ -0,0 +1,72 @@ +use crate::events::Observer; +use crate::fs::file_table::FdEvents; +use crate::fs::utils::{DirEntryVecExt, Inode}; +use crate::prelude::*; +use crate::process::Process; + +use self::comm::CommFileOps; +use self::exe::ExeSymOps; +use self::fd::FdDirOps; +use super::template::{ + DirOps, FileOps, ProcDir, ProcDirBuilder, ProcFileBuilder, ProcSymBuilder, SymOps, +}; + +mod comm; +mod exe; +mod fd; + +/// Represents the inode at `/proc/[pid]`. +pub struct PidDirOps(Arc); + +impl PidDirOps { + pub fn new_inode(process_ref: Arc, parent: Weak) -> Arc { + let pid_inode = ProcDirBuilder::new(Self(process_ref.clone())) + .parent(parent) + // The pid directories must be volatile, because it is just associated with one process. + .volatile() + .build() + .unwrap(); + let file_table = process_ref.file_table().lock(); + let weak_ptr = Arc::downgrade(&pid_inode); + file_table.register_observer(weak_ptr); + pid_inode + } +} + +impl Observer for ProcDir { + fn on_events(&self, events: &FdEvents) { + if let FdEvents::DropFileTable = events { + let mut cached_children = self.cached_children().write(); + cached_children.remove_entry_by_name("fd"); + } + } +} + +impl DirOps for PidDirOps { + fn lookup_child(&self, this_ptr: Weak, name: &str) -> Result> { + let inode = match name { + "exe" => ExeSymOps::new_inode(self.0.clone(), this_ptr.clone()), + "comm" => CommFileOps::new_inode(self.0.clone(), this_ptr.clone()), + "fd" => FdDirOps::new_inode(self.0.clone(), this_ptr.clone()), + _ => return_errno!(Errno::ENOENT), + }; + Ok(inode) + } + + fn populate_children(&self, this_ptr: Weak) { + let this = { + let this = this_ptr.upgrade().unwrap(); + this.downcast_ref::>().unwrap().this() + }; + let mut cached_children = this.cached_children().write(); + cached_children.put_entry_if_not_found("exe", || { + ExeSymOps::new_inode(self.0.clone(), this_ptr.clone()) + }); + cached_children.put_entry_if_not_found("comm", || { + CommFileOps::new_inode(self.0.clone(), this_ptr.clone()) + }); + cached_children.put_entry_if_not_found("fd", || { + FdDirOps::new_inode(self.0.clone(), this_ptr.clone()) + }) + } +} diff --git a/src/services/libs/jinux-std/src/fs/procfs/self_.rs b/src/services/libs/jinux-std/src/fs/procfs/self_.rs new file mode 100644 index 00000000..a046a765 --- /dev/null +++ b/src/services/libs/jinux-std/src/fs/procfs/self_.rs @@ -0,0 +1,16 @@ +use super::*; + +/// Represents the inode at `/proc/self`. +pub struct SelfSymOps; + +impl SelfSymOps { + pub fn new_inode(parent: Weak) -> Arc { + ProcSymBuilder::new(Self).parent(parent).build().unwrap() + } +} + +impl SymOps for SelfSymOps { + fn read_link(&self) -> Result { + Ok(current!().pid().to_string()) + } +} diff --git a/src/services/libs/jinux-std/src/fs/procfs/template/builder.rs b/src/services/libs/jinux-std/src/fs/procfs/template/builder.rs new file mode 100644 index 00000000..f0b40352 --- /dev/null +++ b/src/services/libs/jinux-std/src/fs/procfs/template/builder.rs @@ -0,0 +1,175 @@ +use crate::fs::utils::{FileSystem, Inode}; +use crate::prelude::*; + +use super::{ + dir::{DirOps, ProcDir}, + file::{FileOps, ProcFile}, + sym::{ProcSym, SymOps}, +}; + +pub struct ProcDirBuilder { + // Mandatory field + dir: O, + // Optional fields + optional_builder: Option, +} + +impl ProcDirBuilder { + pub fn new(dir: O) -> Self { + let optional_builder: OptionalBuilder = Default::default(); + Self { + dir, + optional_builder: Some(optional_builder), + } + } + + pub fn parent(self, parent: Weak) -> Self { + self.optional_builder(|ob| ob.parent(parent)) + } + + pub fn fs(self, fs: Arc) -> Self { + self.optional_builder(|ob| ob.fs(fs)) + } + + pub fn volatile(self) -> Self { + self.optional_builder(|ob| ob.volatile()) + } + + pub fn build(mut self) -> Result>> { + let (fs, parent, is_volatile) = self.optional_builder.take().unwrap().build()?; + Ok(ProcDir::new(self.dir, fs, parent, is_volatile)) + } + + fn optional_builder(mut self, f: F) -> Self + where + F: FnOnce(OptionalBuilder) -> OptionalBuilder, + { + let optional_builder = self.optional_builder.take().unwrap(); + self.optional_builder = Some(f(optional_builder)); + self + } +} + +pub struct ProcFileBuilder { + // Mandatory field + file: O, + // Optional fields + optional_builder: Option, +} + +impl ProcFileBuilder { + pub fn new(file: O) -> Self { + let optional_builder: OptionalBuilder = Default::default(); + Self { + file, + optional_builder: Some(optional_builder), + } + } + + pub fn parent(self, parent: Weak) -> Self { + self.optional_builder(|ob| ob.parent(parent)) + } + + pub fn volatile(self) -> Self { + self.optional_builder(|ob| ob.volatile()) + } + + pub fn build(mut self) -> Result>> { + let (fs, _, is_volatile) = self.optional_builder.take().unwrap().build()?; + Ok(ProcFile::new(self.file, fs, is_volatile)) + } + + fn optional_builder(mut self, f: F) -> Self + where + F: FnOnce(OptionalBuilder) -> OptionalBuilder, + { + let optional_builder = self.optional_builder.take().unwrap(); + self.optional_builder = Some(f(optional_builder)); + self + } +} + +pub struct ProcSymBuilder { + // Mandatory field + sym: O, + // Optional fields + optional_builder: Option, +} + +impl ProcSymBuilder { + pub fn new(sym: O) -> Self { + let optional_builder: OptionalBuilder = Default::default(); + Self { + sym, + optional_builder: Some(optional_builder), + } + } + + pub fn parent(self, parent: Weak) -> Self { + self.optional_builder(|ob| ob.parent(parent)) + } + + pub fn volatile(self) -> Self { + self.optional_builder(|ob| ob.volatile()) + } + + pub fn build(mut self) -> Result>> { + let (fs, _, is_volatile) = self.optional_builder.take().unwrap().build()?; + Ok(ProcSym::new(self.sym, fs, is_volatile)) + } + + fn optional_builder(mut self, f: F) -> Self + where + F: FnOnce(OptionalBuilder) -> OptionalBuilder, + { + let optional_builder = self.optional_builder.take().unwrap(); + self.optional_builder = Some(f(optional_builder)); + self + } +} + +#[derive(Default)] +struct OptionalBuilder { + parent: Option>, + fs: Option>, + is_volatile: bool, +} + +impl OptionalBuilder { + pub fn parent(mut self, parent: Weak) -> Self { + self.parent = Some(parent); + self + } + + pub fn fs(mut self, fs: Arc) -> Self { + self.fs = Some(fs); + self + } + + pub fn volatile(mut self) -> Self { + self.is_volatile = true; + self + } + + pub fn build(self) -> Result<(Arc, Option>, bool)> { + if self.parent.is_none() && self.fs.is_none() { + return_errno_with_message!(Errno::EINVAL, "must have parent or fs"); + } + let fs = self + .fs + .unwrap_or_else(|| self.parent.as_ref().unwrap().upgrade().unwrap().fs()); + + // The volatile property is inherited from parent. + let is_volatile = { + let mut is_volatile = self.is_volatile; + if let Some(parent) = self.parent.as_ref() { + if !parent.upgrade().unwrap().is_dentry_cacheable() { + is_volatile = true; + } + } + is_volatile + }; + + Ok((fs, self.parent, is_volatile)) + } +} diff --git a/src/services/libs/jinux-std/src/fs/procfs/template/dir.rs b/src/services/libs/jinux-std/src/fs/procfs/template/dir.rs new file mode 100644 index 00000000..0442e16a --- /dev/null +++ b/src/services/libs/jinux-std/src/fs/procfs/template/dir.rs @@ -0,0 +1,225 @@ +use alloc::string::String; +use core::any::Any; +use core::time::Duration; +use jinux_frame::vm::VmFrame; + +use crate::fs::utils::{ + DirEntryVec, DirentVisitor, FileSystem, Inode, InodeMode, InodeType, IoctlCmd, Metadata, +}; +use crate::prelude::*; + +use super::{ProcFS, ProcInodeInfo}; + +pub struct ProcDir { + inner: D, + this: Weak>, + parent: Option>, + cached_children: RwLock)>>, + info: ProcInodeInfo, +} + +impl ProcDir { + pub fn new( + dir: D, + fs: Arc, + parent: Option>, + is_volatile: bool, + ) -> Arc { + let info = { + let procfs = fs.downcast_ref::().unwrap(); + let metadata = Metadata::new_dir( + procfs.alloc_id(), + InodeMode::from_bits_truncate(0o555), + &fs.sb(), + ); + ProcInodeInfo::new(metadata, Arc::downgrade(&fs), is_volatile) + }; + Arc::new_cyclic(|weak_self| Self { + inner: dir, + this: weak_self.clone(), + parent, + cached_children: RwLock::new(DirEntryVec::new()), + info, + }) + } + + pub fn this(&self) -> Arc> { + self.this.upgrade().unwrap() + } + + pub fn parent(&self) -> Option> { + self.parent.as_ref().and_then(|p| p.upgrade()) + } + + pub fn cached_children(&self) -> &RwLock)>> { + &self.cached_children + } +} + +impl Inode for ProcDir { + fn len(&self) -> usize { + self.info.metadata().size + } + + fn resize(&self, _new_size: usize) {} + + fn metadata(&self) -> Metadata { + self.info.metadata().clone() + } + + fn atime(&self) -> Duration { + self.info.metadata().atime + } + + fn set_atime(&self, _time: Duration) {} + + fn mtime(&self) -> Duration { + self.info.metadata().mtime + } + + fn set_mtime(&self, _time: Duration) {} + + fn read_page(&self, _idx: usize, _frame: &VmFrame) -> Result<()> { + Err(Error::new(Errno::EISDIR)) + } + + fn write_page(&self, _idx: usize, _frame: &VmFrame) -> Result<()> { + Err(Error::new(Errno::EISDIR)) + } + + fn read_at(&self, _offset: usize, _buf: &mut [u8]) -> Result { + Err(Error::new(Errno::EISDIR)) + } + + fn write_at(&self, _offset: usize, _buf: &[u8]) -> Result { + Err(Error::new(Errno::EISDIR)) + } + + fn mknod(&self, _name: &str, _type_: InodeType, _mode: InodeMode) -> Result> { + Err(Error::new(Errno::EPERM)) + } + + fn readdir_at(&self, mut offset: usize, visitor: &mut dyn DirentVisitor) -> Result { + let try_readdir = |offset: &mut usize, visitor: &mut dyn DirentVisitor| -> Result<()> { + // Read the two special entries. + if *offset == 0 { + let this_inode = self.this(); + visitor.visit( + ".", + this_inode.info.metadata().ino as u64, + this_inode.info.metadata().type_, + *offset, + )?; + *offset += 1; + } + if *offset == 1 { + let parent_inode = self.parent().unwrap_or(self.this()); + visitor.visit( + "..", + parent_inode.metadata().ino as u64, + parent_inode.metadata().type_, + *offset, + )?; + *offset += 1; + } + + // Read the normal child entries. + self.inner.populate_children(self.this.clone()); + let cached_children = self.cached_children.read(); + for (idx, (name, child)) in cached_children + .idxes_and_entries() + .map(|(idx, (name, child))| (idx + 2, (name, child))) + { + if idx < *offset { + continue; + } + visitor.visit( + name.as_ref(), + child.metadata().ino as u64, + child.metadata().type_, + idx, + )?; + *offset = idx + 1; + } + Ok(()) + }; + + let initial_offset = offset; + match try_readdir(&mut offset, visitor) { + Err(e) if initial_offset == offset => Err(e), + _ => Ok(offset - initial_offset), + } + } + + fn link(&self, _old: &Arc, _name: &str) -> Result<()> { + Err(Error::new(Errno::EPERM)) + } + + fn unlink(&self, _name: &str) -> Result<()> { + Err(Error::new(Errno::EPERM)) + } + + fn rmdir(&self, _name: &str) -> Result<()> { + Err(Error::new(Errno::EPERM)) + } + + fn lookup(&self, name: &str) -> Result> { + let inode = match name { + "." => self.this(), + ".." => self.parent().unwrap_or(self.this()), + name => { + let mut cached_children = self.cached_children.write(); + if let Some((_, inode)) = cached_children + .iter() + .find(|(child_name, inode)| child_name.as_str() == name) + { + return Ok(inode.clone()); + } + let inode = self.inner.lookup_child(self.this.clone(), name)?; + cached_children.put((String::from(name), inode.clone())); + inode + } + }; + Ok(inode) + } + + fn rename(&self, _old_name: &str, _target: &Arc, _new_name: &str) -> Result<()> { + Err(Error::new(Errno::EPERM)) + } + + fn read_link(&self) -> Result { + Err(Error::new(Errno::EISDIR)) + } + + fn write_link(&self, _target: &str) -> Result<()> { + Err(Error::new(Errno::EISDIR)) + } + + fn ioctl(&self, _cmd: &IoctlCmd) -> Result<()> { + Err(Error::new(Errno::EISDIR)) + } + + fn sync(&self) -> Result<()> { + Ok(()) + } + + fn fs(&self) -> Arc { + self.info.fs().upgrade().unwrap() + } + + fn is_dentry_cacheable(&self) -> bool { + !self.info.is_volatile() + } + + fn as_any_ref(&self) -> &dyn Any { + self + } +} + +pub trait DirOps: Sync + Send { + fn lookup_child(&self, this_ptr: Weak, name: &str) -> Result> { + Err(Error::new(Errno::ENOENT)) + } + + fn populate_children(&self, this_ptr: Weak) {} +} diff --git a/src/services/libs/jinux-std/src/fs/procfs/template/file.rs b/src/services/libs/jinux-std/src/fs/procfs/template/file.rs new file mode 100644 index 00000000..cc06fee2 --- /dev/null +++ b/src/services/libs/jinux-std/src/fs/procfs/template/file.rs @@ -0,0 +1,136 @@ +use alloc::string::String; +use core::any::Any; +use core::time::Duration; +use jinux_frame::vm::VmFrame; + +use crate::fs::utils::{ + DirentVisitor, FileSystem, Inode, InodeMode, InodeType, IoctlCmd, Metadata, +}; +use crate::prelude::*; + +use super::{ProcFS, ProcInodeInfo}; + +pub struct ProcFile { + inner: F, + info: ProcInodeInfo, +} + +impl ProcFile { + pub fn new(file: F, fs: Arc, is_volatile: bool) -> Arc { + let info = { + let procfs = fs.downcast_ref::().unwrap(); + let metadata = Metadata::new_file( + procfs.alloc_id(), + InodeMode::from_bits_truncate(0o444), + &fs.sb(), + ); + ProcInodeInfo::new(metadata, Arc::downgrade(&fs), is_volatile) + }; + Arc::new(Self { inner: file, info }) + } +} + +impl Inode for ProcFile { + fn len(&self) -> usize { + self.info.metadata().size + } + + fn resize(&self, _new_size: usize) {} + + fn metadata(&self) -> Metadata { + self.info.metadata().clone() + } + + fn atime(&self) -> Duration { + self.info.metadata().atime + } + + fn set_atime(&self, _time: Duration) {} + + fn mtime(&self) -> Duration { + self.info.metadata().mtime + } + + fn set_mtime(&self, _time: Duration) {} + + fn read_page(&self, _idx: usize, _frame: &VmFrame) -> Result<()> { + unreachable!() + } + + fn write_page(&self, _idx: usize, _frame: &VmFrame) -> Result<()> { + unreachable!() + } + + fn read_at(&self, offset: usize, buf: &mut [u8]) -> Result { + let data = self.inner.data()?; + let start = data.len().min(offset); + let end = data.len().min(offset + buf.len()); + let len = end - start; + buf[0..len].copy_from_slice(&data[start..end]); + Ok(len) + } + + fn write_at(&self, _offset: usize, _buf: &[u8]) -> Result { + Err(Error::new(Errno::EPERM)) + } + + fn mknod(&self, _name: &str, _type_: InodeType, _mode: InodeMode) -> Result> { + Err(Error::new(Errno::ENOTDIR)) + } + + fn readdir_at(&self, _offset: usize, _visitor: &mut dyn DirentVisitor) -> Result { + Err(Error::new(Errno::ENOTDIR)) + } + + fn link(&self, _old: &Arc, _name: &str) -> Result<()> { + Err(Error::new(Errno::ENOTDIR)) + } + + fn unlink(&self, _name: &str) -> Result<()> { + Err(Error::new(Errno::ENOTDIR)) + } + + fn rmdir(&self, _name: &str) -> Result<()> { + Err(Error::new(Errno::ENOTDIR)) + } + + fn lookup(&self, _name: &str) -> Result> { + Err(Error::new(Errno::ENOTDIR)) + } + + fn rename(&self, _old_name: &str, _target: &Arc, _new_name: &str) -> Result<()> { + Err(Error::new(Errno::ENOTDIR)) + } + + fn read_link(&self) -> Result { + Err(Error::new(Errno::EINVAL)) + } + + fn write_link(&self, _target: &str) -> Result<()> { + Err(Error::new(Errno::EINVAL)) + } + + fn ioctl(&self, _cmd: &IoctlCmd) -> Result<()> { + Err(Error::new(Errno::EPERM)) + } + + fn sync(&self) -> Result<()> { + Ok(()) + } + + fn fs(&self) -> Arc { + self.info.fs().upgrade().unwrap() + } + + fn is_dentry_cacheable(&self) -> bool { + !self.info.is_volatile() + } + + fn as_any_ref(&self) -> &dyn Any { + self + } +} + +pub trait FileOps: Sync + Send { + fn data(&self) -> Result>; +} diff --git a/src/services/libs/jinux-std/src/fs/procfs/template/mod.rs b/src/services/libs/jinux-std/src/fs/procfs/template/mod.rs new file mode 100644 index 00000000..53a7f95b --- /dev/null +++ b/src/services/libs/jinux-std/src/fs/procfs/template/mod.rs @@ -0,0 +1,42 @@ +use crate::fs::utils::{FileSystem, Metadata}; +use crate::prelude::*; + +use super::ProcFS; + +pub use self::builder::{ProcDirBuilder, ProcFileBuilder, ProcSymBuilder}; +pub use self::dir::{DirOps, ProcDir}; +pub use self::file::FileOps; +pub use self::sym::SymOps; + +mod builder; +mod dir; +mod file; +mod sym; + +struct ProcInodeInfo { + metadata: Metadata, + fs: Weak, + is_volatile: bool, +} + +impl ProcInodeInfo { + pub fn new(metadata: Metadata, fs: Weak, is_volatile: bool) -> Self { + Self { + metadata, + fs, + is_volatile, + } + } + + pub fn fs(&self) -> &Weak { + &self.fs + } + + pub fn metadata(&self) -> &Metadata { + &self.metadata + } + + pub fn is_volatile(&self) -> bool { + self.is_volatile + } +} diff --git a/src/services/libs/jinux-std/src/fs/procfs/template/sym.rs b/src/services/libs/jinux-std/src/fs/procfs/template/sym.rs new file mode 100644 index 00000000..8074b079 --- /dev/null +++ b/src/services/libs/jinux-std/src/fs/procfs/template/sym.rs @@ -0,0 +1,131 @@ +use alloc::string::String; +use core::any::Any; +use core::time::Duration; +use jinux_frame::vm::VmFrame; + +use crate::fs::utils::{ + DirentVisitor, FileSystem, Inode, InodeMode, InodeType, IoctlCmd, Metadata, +}; +use crate::prelude::*; + +use super::{ProcFS, ProcInodeInfo}; + +pub struct ProcSym { + inner: S, + info: ProcInodeInfo, +} + +impl ProcSym { + pub fn new(sym: S, fs: Arc, is_volatile: bool) -> Arc { + let info = { + let procfs = fs.downcast_ref::().unwrap(); + let metadata = Metadata::new_symlink( + procfs.alloc_id(), + InodeMode::from_bits_truncate(0o777), + &fs.sb(), + ); + ProcInodeInfo::new(metadata, Arc::downgrade(&fs), is_volatile) + }; + Arc::new(Self { inner: sym, info }) + } +} + +impl Inode for ProcSym { + fn len(&self) -> usize { + self.info.metadata().size + } + + fn resize(&self, _new_size: usize) {} + + fn metadata(&self) -> Metadata { + self.info.metadata().clone() + } + + fn atime(&self) -> Duration { + self.info.metadata().atime + } + + fn set_atime(&self, _time: Duration) {} + + fn mtime(&self) -> Duration { + self.info.metadata().mtime + } + + fn set_mtime(&self, _time: Duration) {} + + fn read_page(&self, _idx: usize, _frame: &VmFrame) -> Result<()> { + Err(Error::new(Errno::EPERM)) + } + + fn write_page(&self, _idx: usize, _frame: &VmFrame) -> Result<()> { + Err(Error::new(Errno::EPERM)) + } + + fn read_at(&self, _offset: usize, _buf: &mut [u8]) -> Result { + Err(Error::new(Errno::EPERM)) + } + + fn write_at(&self, _offset: usize, _buf: &[u8]) -> Result { + Err(Error::new(Errno::EPERM)) + } + + fn mknod(&self, _name: &str, _type_: InodeType, _mode: InodeMode) -> Result> { + Err(Error::new(Errno::ENOTDIR)) + } + + fn readdir_at(&self, _offset: usize, _visitor: &mut dyn DirentVisitor) -> Result { + Err(Error::new(Errno::ENOTDIR)) + } + + fn link(&self, _old: &Arc, _name: &str) -> Result<()> { + Err(Error::new(Errno::ENOTDIR)) + } + + fn unlink(&self, _name: &str) -> Result<()> { + Err(Error::new(Errno::ENOTDIR)) + } + + fn rmdir(&self, _name: &str) -> Result<()> { + Err(Error::new(Errno::ENOTDIR)) + } + + fn lookup(&self, _name: &str) -> Result> { + Err(Error::new(Errno::ENOTDIR)) + } + + fn rename(&self, _old_name: &str, _target: &Arc, _new_name: &str) -> Result<()> { + Err(Error::new(Errno::ENOTDIR)) + } + + fn read_link(&self) -> Result { + self.inner.read_link() + } + + fn write_link(&self, _target: &str) -> Result<()> { + Err(Error::new(Errno::EPERM)) + } + + fn ioctl(&self, _cmd: &IoctlCmd) -> Result<()> { + Err(Error::new(Errno::EPERM)) + } + + fn sync(&self) -> Result<()> { + Ok(()) + } + + fn fs(&self) -> Arc { + self.info.fs().upgrade().unwrap() + } + + fn is_dentry_cacheable(&self) -> bool { + !self.info.is_volatile() + } + + fn as_any_ref(&self) -> &dyn Any { + self + } +} + +pub trait SymOps: Sync + Send { + fn read_link(&self) -> Result; +} 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 982a42d1..0b375b9d 100644 --- a/src/services/libs/jinux-std/src/fs/ramfs/fs.rs +++ b/src/services/libs/jinux-std/src/fs/ramfs/fs.rs @@ -67,6 +67,10 @@ impl FileSystem for RamFS { fn flags(&self) -> FsFlags { FsFlags::DENTRY_UNEVICTABLE } + + fn as_any_ref(&self) -> &dyn Any { + self + } } struct RamInode(RwLock); diff --git a/src/services/libs/jinux-std/src/fs/utils/dentry_cache.rs b/src/services/libs/jinux-std/src/fs/utils/dentry_cache.rs index 4461d9f3..bc28c500 100644 --- a/src/services/libs/jinux-std/src/fs/utils/dentry_cache.rs +++ b/src/services/libs/jinux-std/src/fs/utils/dentry_cache.rs @@ -292,8 +292,10 @@ impl Children { } pub fn insert_dentry(&mut self, dentry: &Arc) { - DCACHE.lock().insert(dentry.key(), dentry.clone()); - self.inner.insert(dentry.name(), Arc::downgrade(&dentry)); + if dentry.vnode().is_dentry_cacheable() { + DCACHE.lock().insert(dentry.key(), dentry.clone()); + } + self.inner.insert(dentry.name(), Arc::downgrade(dentry)); } pub fn delete_dentry(&mut self, name: &str) -> Option> { diff --git a/src/services/libs/jinux-std/src/fs/utils/direntry_vec.rs b/src/services/libs/jinux-std/src/fs/utils/direntry_vec.rs index 07899aa2..8e55e1c4 100644 --- a/src/services/libs/jinux-std/src/fs/utils/direntry_vec.rs +++ b/src/services/libs/jinux-std/src/fs/utils/direntry_vec.rs @@ -1,3 +1,4 @@ +use super::Inode; use crate::prelude::*; /// DirEntryVec is used to store the entries of a directory. @@ -81,3 +82,37 @@ impl DirEntryVec { self.slots.iter().filter_map(|x| x.as_ref()) } } + +pub trait DirEntryVecExt { + /// If the entry is not found by `name`, use `f` to get the inode, then put the entry into vec. + fn put_entry_if_not_found(&mut self, name: &str, f: impl Fn() -> Arc); + + /// Remove and returns the entry by name. + /// Returns `None` if the entry has been removed. + fn remove_entry_by_name(&mut self, name: &str) -> Option<(String, Arc)>; +} + +impl DirEntryVecExt for DirEntryVec<(String, Arc)> { + fn put_entry_if_not_found(&mut self, name: &str, f: impl Fn() -> Arc) { + if self + .iter() + .find(|(child_name, _)| child_name == name) + .is_none() + { + let inode = f(); + self.put((String::from(name), inode)); + } + } + + fn remove_entry_by_name(&mut self, name: &str) -> Option<(String, Arc)> { + let idx = self + .idxes_and_entries() + .find(|(_, (child_name, _))| child_name == name) + .map(|(idx, _)| idx); + if let Some(idx) = idx { + self.remove(idx) + } else { + None + } + } +} diff --git a/src/services/libs/jinux-std/src/fs/utils/fs.rs b/src/services/libs/jinux-std/src/fs/utils/fs.rs index a4b69b57..e5a9d79d 100644 --- a/src/services/libs/jinux-std/src/fs/utils/fs.rs +++ b/src/services/libs/jinux-std/src/fs/utils/fs.rs @@ -1,5 +1,6 @@ use alloc::sync::Arc; use bitflags::bitflags; +use core::any::Any; use super::Inode; use crate::prelude::*; @@ -46,7 +47,7 @@ bitflags! { } } -pub trait FileSystem: Sync + Send { +pub trait FileSystem: Any + Sync + Send { fn sync(&self) -> Result<()>; fn root_inode(&self) -> Arc; @@ -54,4 +55,12 @@ pub trait FileSystem: Sync + Send { fn sb(&self) -> SuperBlock; fn flags(&self) -> FsFlags; + + fn as_any_ref(&self) -> &dyn Any; +} + +impl dyn FileSystem { + pub fn downcast_ref(&self) -> Option<&T> { + self.as_any_ref().downcast_ref::() + } } 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 173ccb0c..88a50905 100644 --- a/src/services/libs/jinux-std/src/fs/utils/inode.rs +++ b/src/services/libs/jinux-std/src/fs/utils/inode.rs @@ -200,6 +200,26 @@ pub trait Inode: Any + Sync + Send { fn fs(&self) -> Arc; fn as_any_ref(&self) -> &dyn Any; + + /// Returns whether a VFS dentry for this inode should be put into the dentry cache. + /// + /// The dentry cache in the VFS layer can accelerate the lookup of inodes. So usually, + /// it is preferable to use the dentry cache. And thus, the default return value of this method + /// is `true`. + /// + /// But this caching can raise consistency issues in certain use cases. Specifically, the dentry + /// cache works on the assumption that all FS operations go through the dentry layer first. + /// This is why the dentry cache can reflect the up-to-date FS state. Yet, this assumption + /// may be broken. For example, an inode in procfs (say, `/proc/1/fd/2`) can "disappear" without + /// notice from the perspective of the dentry cache. So for such inodes, they are incompatible + /// with the dentry cache. And this method returns `false`. + /// + /// Note that if any ancestor directory of an inode has this method returns `false`, then + /// this inode would not be cached by the dentry cache, even when the method of this + /// inode returns `true`. + fn is_dentry_cacheable(&self) -> bool { + true + } } impl dyn Inode { 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 0dfcdf22..ee83cbae 100644 --- a/src/services/libs/jinux-std/src/fs/utils/mod.rs +++ b/src/services/libs/jinux-std/src/fs/utils/mod.rs @@ -4,7 +4,7 @@ pub use access_mode::AccessMode; pub use creation_flags::CreationFlags; pub use dentry_cache::Dentry; pub use dirent_visitor::DirentVisitor; -pub use direntry_vec::DirEntryVec; +pub use direntry_vec::{DirEntryVec, DirEntryVecExt}; pub use fcntl::FcntlCmd; pub use file_creation_mask::FileCreationMask; pub use fs::{FileSystem, FsFlags, SuperBlock}; 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 269231a5..51964b84 100644 --- a/src/services/libs/jinux-std/src/fs/utils/vnode.rs +++ b/src/services/libs/jinux-std/src/fs/utils/vnode.rs @@ -206,4 +206,8 @@ impl Vnode { pub fn set_mtime(&self, time: Duration) { self.inner.read().inode.set_mtime(time) } + + pub fn is_dentry_cacheable(&self) -> bool { + self.inner.read().inode.is_dentry_cacheable() + } } diff --git a/src/services/libs/jinux-std/src/process/process_table.rs b/src/services/libs/jinux-std/src/process/process_table.rs index 99351589..9d799810 100644 --- a/src/services/libs/jinux-std/src/process/process_table.rs +++ b/src/services/libs/jinux-std/src/process/process_table.rs @@ -2,6 +2,7 @@ //! This table can be used to get process with pid. //! TODO: progress group, thread all need similar mapping +use crate::events::{Events, Observer, Subject}; use crate::prelude::*; use super::{process_group::ProcessGroup, Pgid, Pid, Process}; @@ -10,6 +11,7 @@ lazy_static! { static ref PROCESS_TABLE: Mutex>> = Mutex::new(BTreeMap::new()); static ref PROCESS_GROUP_TABLE: Mutex>> = Mutex::new(BTreeMap::new()); + static ref PROCESS_TABLE_SUBJECT: Subject = Subject::new(); } /// add a process to global table @@ -21,6 +23,9 @@ pub fn add_process(process: Arc) { /// remove a process from global table pub fn remove_process(pid: Pid) { PROCESS_TABLE.lock().remove(&pid); + + let events = PidEvent::Exit(pid); + PROCESS_TABLE_SUBJECT.notify_observers(&events); } /// get a process with pid @@ -58,3 +63,18 @@ pub fn pgid_to_process_group(pgid: Pgid) -> Option> { .get(&pgid) .map(|process_group| process_group.clone()) } + +pub fn register_observer(observer: Weak>) { + PROCESS_TABLE_SUBJECT.register_observer(observer); +} + +pub fn unregister_observer(observer: Weak>) { + PROCESS_TABLE_SUBJECT.unregister_observer(observer); +} + +#[derive(Copy, Clone)] +pub enum PidEvent { + Exit(Pid), +} + +impl Events for PidEvent {} diff --git a/src/services/libs/jinux-std/src/syscall/getdents64.rs b/src/services/libs/jinux-std/src/syscall/getdents64.rs index 5edcf6f5..b60582c1 100644 --- a/src/services/libs/jinux-std/src/syscall/getdents64.rs +++ b/src/services/libs/jinux-std/src/syscall/getdents64.rs @@ -21,9 +21,11 @@ pub fn sys_getdents64( fd, buf_addr, buf_len ); - let current = current!(); - let file_table = current.file_table().lock(); - let file = file_table.get_file(fd)?; + let file = { + let current = current!(); + let file_table = current.file_table().lock(); + file_table.get_file(fd)?.clone() + }; let inode_handle = file .as_inode_handle() .ok_or(Error::with_message(Errno::EBADE, "not inode"))?; diff --git a/src/services/libs/jinux-std/src/syscall/readlink.rs b/src/services/libs/jinux-std/src/syscall/readlink.rs index ad0a9780..45551288 100644 --- a/src/services/libs/jinux-std/src/syscall/readlink.rs +++ b/src/services/libs/jinux-std/src/syscall/readlink.rs @@ -24,18 +24,6 @@ pub fn sys_readlinkat( ); let current = current!(); - if pathname == CString::new("/proc/self/exe")? { - // "proc/self/exe" is used to read the filename of current executable - let process_file_name = current.executable_path().read(); - debug!("process exec filename= {:?}", process_file_name); - // readlink does not append a terminating null byte to buf - let bytes = process_file_name.as_bytes(); - let write_len = bytes.len().min(usr_buf_len); - write_bytes_to_user(usr_buf_addr, &bytes[..write_len])?; - return Ok(SyscallReturn::Return(write_len as _)); - } - - // The common path let dentry = { let pathname = pathname.to_string_lossy(); if pathname.is_empty() {