diff --git a/src/services/libs/jinux-std/src/fs/mod.rs b/src/services/libs/jinux-std/src/fs/mod.rs index b663ab349..0338fb681 100644 --- a/src/services/libs/jinux-std/src/fs/mod.rs +++ b/src/services/libs/jinux-std/src/fs/mod.rs @@ -6,6 +6,7 @@ pub mod file_table; pub mod inode_handle; pub mod ioctl; pub mod poll; +pub mod ramfs; pub mod stat; pub mod stdio; pub mod utils; diff --git a/src/services/libs/jinux-std/src/fs/ramfs/fs.rs b/src/services/libs/jinux-std/src/fs/ramfs/fs.rs new file mode 100644 index 000000000..cd4d6d697 --- /dev/null +++ b/src/services/libs/jinux-std/src/fs/ramfs/fs.rs @@ -0,0 +1,568 @@ +use crate::prelude::*; +use alloc::str; +use alloc::string::String; +use core::any::Any; +use core::sync::atomic::{AtomicUsize, Ordering}; +use jinux_frame::vm::VmFrame; +use spin::{RwLock, RwLockWriteGuard}; + +use super::*; +use crate::fs::ioctl::IoctlCmd; +use crate::fs::utils::{ + DirentWriterContext, FileSystem, Inode, InodeMode, InodeType, Metadata, SuperBlock, +}; + +pub struct RamFS { + metadata: RwLock, + root: Arc, + inode_allocator: AtomicUsize, +} + +impl RamFS { + pub fn new() -> Arc { + let root = Arc::new(RamInode(RwLock::new(Inode_::new_dir( + ROOT_INO, + InodeMode::from_bits_truncate(0o755), + )))); + let ramfs = Arc::new(Self { + metadata: RwLock::new(SuperBlock::new(RAMFS_MAGIC, BLOCK_SIZE, NAME_MAX)), + root, + inode_allocator: AtomicUsize::new(ROOT_INO + 1), + }); + let mut root = ramfs.root.0.write(); + root.inner + .as_direntry_mut() + .unwrap() + .init(Arc::downgrade(&ramfs.root), Arc::downgrade(&ramfs.root)); + root.this = Arc::downgrade(&ramfs.root); + root.fs = Arc::downgrade(&ramfs); + drop(root); + ramfs + } + + fn alloc_id(&self) -> usize { + let next_id = self.inode_allocator.fetch_add(1, Ordering::SeqCst); + self.metadata.write().files += 1; + next_id + } +} + +impl FileSystem for RamFS { + fn sync(&self) -> Result<()> { + // do nothing + Ok(()) + } + + fn root_inode(&self) -> Arc { + self.root.clone() + } + + fn sb(&self) -> SuperBlock { + self.metadata.read().clone() + } +} + +struct RamInode(RwLock); + +struct Inode_ { + inner: Inner, + metadata: Metadata, + this: Weak, + fs: Weak, +} + +impl Inode_ { + pub fn new_dir(ino: usize, mode: InodeMode) -> Self { + Self { + inner: Inner::Dir(DirEntry::new()), + metadata: Metadata::new_dir(ino, mode), + this: Weak::default(), + fs: Weak::default(), + } + } + + pub fn new_file(ino: usize, mode: InodeMode) -> Self { + Self { + inner: Inner::File, + metadata: Metadata::new_file(ino, mode), + this: Weak::default(), + fs: Weak::default(), + } + } + + pub fn new_symlink(ino: usize, mode: InodeMode) -> Self { + Self { + inner: Inner::SymLink(Str256::from("")), + metadata: Metadata::new_synlink(ino, mode), + this: Weak::default(), + fs: Weak::default(), + } + } +} + +enum Inner { + Dir(DirEntry), + File, + SymLink(Str256), +} + +impl Inner { + fn as_direntry(&self) -> Option<&DirEntry> { + match self { + Inner::Dir(dir_entry) => Some(dir_entry), + _ => None, + } + } + + fn as_direntry_mut(&mut self) -> Option<&mut DirEntry> { + match self { + Inner::Dir(dir_entry) => Some(dir_entry), + _ => None, + } + } + + fn as_symlink(&self) -> Option<&str> { + match self { + Inner::SymLink(link) => Some(link.as_ref()), + _ => None, + } + } + + fn as_symlink_mut(&mut self) -> Option<&mut Str256> { + match self { + Inner::SymLink(link) => Some(link), + _ => None, + } + } +} + +struct DirEntry { + children: LinkedList<(Str256, Arc)>, + this: Weak, + 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 { + children: LinkedList::new(), + this: Weak::default(), + parent: Weak::default(), + } + } + + fn init(&mut self, this: Weak, parent: Weak) { + self.this = this; + self.set_parent(parent); + } + + fn set_parent(&mut self, parent: Weak) { + self.parent = parent; + } + + fn contains_entry(&self, name: &str) -> bool { + if name == "." || name == ".." { + true + } else { + self.children + .iter() + .find(|(child, _)| child == &Str256::from(name)) + .is_some() + } + } + + fn get_entry(&self, name: &str) -> Option<(usize, Arc)> { + if name == "." { + Some((0, self.this.upgrade().unwrap())) + } else if name == ".." { + Some((1, self.parent.upgrade().unwrap())) + } else { + self.children + .iter() + .enumerate() + .find(|(idx, (child, inode))| child == &Str256::from(name)) + .map(|(idx, (_, inode))| (idx + 2, inode.clone())) + } + } + + fn append_entry(&mut self, name: &str, inode: Arc) { + self.children.push_back((Str256::from(name), inode)) + } + + fn remove_entry(&mut self, idx: usize) -> (Str256, Arc) { + assert!(idx >= 2); + self.children.remove(idx - 2) + } + + fn modify_entry(&mut self, idx: usize, new_name: &str) { + assert!(idx >= 2); + let (name, _) = self.children.iter_mut().nth(idx - 2).unwrap(); + *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); + } + 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 { + self.children.is_empty() + } +} + +#[repr(C)] +#[derive(Clone, PartialEq, PartialOrd, Eq, Ord)] +pub struct Str256([u8; 256]); + +impl AsRef for Str256 { + fn as_ref(&self) -> &str { + let len = self.0.iter().enumerate().find(|(_, &b)| b == 0).unwrap().0; + str::from_utf8(&self.0[0..len]).unwrap() + } +} + +impl<'a> From<&'a str> for Str256 { + fn from(s: &'a str) -> Self { + let mut inner = [0u8; 256]; + let len = if s.len() > NAME_MAX { + NAME_MAX + } else { + s.len() + }; + inner[0..len].copy_from_slice(&s.as_bytes()[0..len]); + Str256(inner) + } +} + +impl core::fmt::Debug for Str256 { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "{}", self.as_ref()) + } +} + +impl Inode for RamInode { + fn read_page(&self, _idx: usize, _frame: &VmFrame) -> Result<()> { + // do nothing + Ok(()) + } + + fn write_page(&self, _idx: usize, _frame: &VmFrame) -> Result<()> { + // do nothing + Ok(()) + } + + fn read_at(&self, offset: usize, buf: &mut [u8]) -> Result { + return_errno_with_message!(Errno::EOPNOTSUPP, "direct read is not supported"); + } + + fn write_at(&self, offset: usize, buf: &[u8]) -> Result { + return_errno_with_message!(Errno::EOPNOTSUPP, "direct read is not supported"); + } + + fn resize(&self, new_size: usize) -> Result<()> { + if self.0.read().metadata.type_ != InodeType::File { + return_errno_with_message!(Errno::EISDIR, "self is not file"); + } + self.0.write().metadata.size = new_size; + Ok(()) + } + + fn mknod(&self, name: &str, type_: InodeType, mode: InodeMode) -> Result> { + if self.0.read().metadata.type_ != InodeType::Dir { + return_errno_with_message!(Errno::ENOTDIR, "self is not dir"); + } + let mut self_inode = self.0.write(); + if self_inode.inner.as_direntry().unwrap().contains_entry(name) { + return_errno_with_message!(Errno::EEXIST, "entry exists"); + } + let new_inode = match type_ { + InodeType::File => { + let file_inode = Arc::new(RamInode(RwLock::new(Inode_::new_file( + self_inode.fs.upgrade().unwrap().alloc_id(), + mode, + )))); + file_inode.0.write().fs = self_inode.fs.clone(); + file_inode + } + InodeType::Dir => { + let dir_inode = Arc::new(RamInode(RwLock::new(Inode_::new_dir( + self_inode.fs.upgrade().unwrap().alloc_id(), + mode, + )))); + dir_inode.0.write().fs = self_inode.fs.clone(); + dir_inode.0.write().inner.as_direntry_mut().unwrap().init( + Arc::downgrade(&dir_inode), + self_inode.inner.as_direntry().unwrap().this.clone(), + ); + dir_inode + } + InodeType::SymLink => { + let sym_inode = Arc::new(RamInode(RwLock::new(Inode_::new_symlink( + self_inode.fs.upgrade().unwrap().alloc_id(), + mode, + )))); + sym_inode.0.write().fs = self_inode.fs.clone(); + sym_inode + } + _ => { + panic!("unsupported inode type"); + } + }; + new_inode.0.write().this = Arc::downgrade(&new_inode); + self_inode + .inner + .as_direntry_mut() + .unwrap() + .append_entry(name, new_inode.clone()); + Ok(new_inode) + } + + fn readdir(&self, ctx: &mut DirentWriterContext) -> 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 + .inner + .as_direntry() + .unwrap() + .iterate_entries(ctx)?; + Ok(total_written_len) + } + + fn link(&self, old: &Arc, name: &str) -> Result<()> { + if self.0.read().metadata.type_ != InodeType::Dir { + return_errno_with_message!(Errno::ENOTDIR, "self is not dir"); + } + let old = old + .downcast_ref::() + .ok_or(Error::new(Errno::EXDEV))?; + if old.0.read().metadata.type_ == InodeType::Dir { + return_errno_with_message!(Errno::EPERM, "old is a dir"); + } + let mut self_inode = self.0.write(); + if self_inode.inner.as_direntry().unwrap().contains_entry(name) { + return_errno_with_message!(Errno::EEXIST, "entry exist"); + } + + self_inode + .inner + .as_direntry_mut() + .unwrap() + .append_entry(name, old.0.read().this.upgrade().unwrap()); + Ok(()) + } + + fn unlink(&self, name: &str) -> Result<()> { + if self.0.read().metadata.type_ != InodeType::Dir { + return_errno_with_message!(Errno::ENOTDIR, "self is not dir"); + } + if name == "." || name == ".." { + return_errno_with_message!(Errno::EISDIR, "unlink . or .."); + } + let mut self_inode = self.0.write(); + let self_dir = self_inode.inner.as_direntry_mut().unwrap(); + let (idx, other) = self_dir.get_entry(name).ok_or(Error::new(Errno::ENOENT))?; + let other_inode = other.0.read(); + if other_inode.metadata.type_ == InodeType::Dir + && !other_inode.inner.as_direntry().unwrap().is_empty_children() + { + return_errno_with_message!(Errno::ENOTEMPTY, "dir not empty"); + } + self_dir.remove_entry(idx); + Ok(()) + } + + fn lookup(&self, name: &str) -> Result> { + if self.0.read().metadata.type_ != InodeType::Dir { + return_errno_with_message!(Errno::ENOTDIR, "self is not dir"); + } + + let (_, inode) = self + .0 + .read() + .inner + .as_direntry() + .unwrap() + .get_entry(name) + .ok_or(Error::new(Errno::ENOENT))?; + Ok(inode as _) + } + + fn rename(&self, old_name: &str, target: &Arc, new_name: &str) -> Result<()> { + if self.0.read().metadata.type_ != InodeType::Dir { + return_errno_with_message!(Errno::ENOTDIR, "self is not dir"); + } + let target = target + .downcast_ref::() + .ok_or(Error::new(Errno::EXDEV))?; + if target.0.read().metadata.type_ != InodeType::Dir { + return_errno_with_message!(Errno::ENOTDIR, "target is not dir"); + } + if old_name == "." || old_name == ".." { + return_errno_with_message!(Errno::EISDIR, "old_name is . or .."); + } + if new_name == "." || new_name == ".." { + return_errno_with_message!(Errno::EISDIR, "new_name is . or .."); + } + let src_inode = self.lookup(old_name)?; + if src_inode.metadata().ino == target.metadata().ino { + return_errno_with_message!(Errno::EINVAL, "target is a descendant of old"); + } + if let Ok(dst_inode) = target.lookup(new_name) { + if src_inode.metadata().ino == dst_inode.metadata().ino { + return Ok(()); + } + match (src_inode.metadata().type_, dst_inode.metadata().type_) { + (InodeType::Dir, InodeType::Dir) => { + let dst_inode = dst_inode.downcast_ref::().unwrap(); + if !dst_inode + .0 + .read() + .inner + .as_direntry() + .unwrap() + .is_empty_children() + { + return_errno_with_message!(Errno::ENOTEMPTY, "dir not empty"); + } + } + (InodeType::Dir, _) => { + return_errno_with_message!(Errno::ENOTDIR, "old is not dir"); + } + (_, InodeType::Dir) => { + return_errno_with_message!(Errno::EISDIR, "new is dir"); + } + _ => {} + } + } + if self.metadata().ino == target.metadata().ino { + let mut self_inode = self.0.write(); + let self_dir = self_inode.inner.as_direntry_mut().unwrap(); + let (idx, _) = self_dir + .get_entry(old_name) + .ok_or(Error::new(Errno::ENOENT))?; + self_dir.modify_entry(idx, new_name); + } else { + let (mut self_inode, mut target_inode) = write_lock_two_inodes(self, target); + let self_dir = self_inode.inner.as_direntry_mut().unwrap(); + let (idx, src_inode) = self_dir + .get_entry(old_name) + .ok_or(Error::new(Errno::ENOENT))?; + self_dir.remove_entry(idx); + target_inode + .inner + .as_direntry_mut() + .unwrap() + .append_entry(new_name, src_inode.clone()); + drop(self_inode); + drop(target_inode); + if src_inode.metadata().type_ == InodeType::Dir { + src_inode + .0 + .write() + .inner + .as_direntry_mut() + .unwrap() + .set_parent(target.0.read().this.clone()); + } + } + Ok(()) + } + + fn read_link(&self) -> Result { + if self.0.read().metadata.type_ != InodeType::SymLink { + return_errno_with_message!(Errno::EINVAL, "self is not symlink"); + } + let self_inode = self.0.read(); + let link = self_inode.inner.as_symlink().unwrap(); + Ok(String::from(link)) + } + + fn write_link(&self, target: &str) -> Result<()> { + if self.0.read().metadata.type_ != InodeType::SymLink { + return_errno_with_message!(Errno::EINVAL, "self is not symlink"); + } + let mut self_inode = self.0.write(); + let link = self_inode.inner.as_symlink_mut().unwrap(); + *link = Str256::from(target); + Ok(()) + } + + fn metadata(&self) -> Metadata { + self.0.read().metadata.clone() + } + + fn sync(&self) -> Result<()> { + // do nothing + Ok(()) + } + + fn fs(&self) -> Arc { + Weak::upgrade(&self.0.read().fs).unwrap() + } + + fn ioctl(&self, cmd: &IoctlCmd) -> Result<()> { + return_errno!(Errno::ENOSYS); + } + + fn as_any_ref(&self) -> &dyn Any { + self + } +} + +fn write_lock_two_inodes<'a>( + this: &'a RamInode, + other: &'a RamInode, +) -> (RwLockWriteGuard<'a, Inode_>, RwLockWriteGuard<'a, Inode_>) { + if this.0.read().metadata.ino < other.0.read().metadata.ino { + let this = this.0.write(); + let other = other.0.write(); + (this, other) + } else { + let other = other.0.write(); + let this = this.0.write(); + (this, other) + } +} diff --git a/src/services/libs/jinux-std/src/fs/ramfs/mod.rs b/src/services/libs/jinux-std/src/fs/ramfs/mod.rs new file mode 100644 index 000000000..18a8e77ea --- /dev/null +++ b/src/services/libs/jinux-std/src/fs/ramfs/mod.rs @@ -0,0 +1,10 @@ +//! Ramfs based on PageCache + +pub use fs::RamFS; + +mod fs; + +const RAMFS_MAGIC: usize = 0x0102_1994; +const BLOCK_SIZE: usize = 4096; +const NAME_MAX: usize = 255; +const ROOT_INO: usize = 1; 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 4d7c9ec74..d2041b679 100644 --- a/src/services/libs/jinux-std/src/fs/utils/fs.rs +++ b/src/services/libs/jinux-std/src/fs/utils/fs.rs @@ -18,6 +18,24 @@ pub struct SuperBlock { pub flags: usize, } +impl SuperBlock { + pub fn new(magic: usize, block_size: usize, name_len: usize) -> Self { + Self { + magic, + bsize: block_size, + blocks: 0, + bfree: 0, + bavail: 0, + files: 0, + ffree: 0, + fsid: 0, + namelen: 255, + frsize: block_size, + flags: 0, + } + } +} + pub trait FileSystem: Sync + Send { fn sync(&self) -> 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 75ac64cf6..d3ac45c2d 100644 --- a/src/services/libs/jinux-std/src/fs/utils/inode.rs +++ b/src/services/libs/jinux-std/src/fs/utils/inode.rs @@ -91,6 +91,65 @@ pub struct Metadata { pub rdev: usize, } +impl Metadata { + pub fn new_dir(ino: usize, mode: InodeMode) -> Self { + Self { + dev: 0, + ino, + size: 2, + blk_size: 0, + blocks: 0, + atime: Timespec { sec: 0, nsec: 0 }, + mtime: Timespec { sec: 0, nsec: 0 }, + ctime: Timespec { sec: 0, nsec: 0 }, + type_: InodeType::Dir, + mode, + nlinks: 2, + uid: 0, + gid: 0, + rdev: 0, + } + } + + pub fn new_file(ino: usize, mode: InodeMode) -> Self { + Self { + dev: 0, + ino, + size: 0, + blk_size: 0, + blocks: 0, + atime: Timespec { sec: 0, nsec: 0 }, + mtime: Timespec { sec: 0, nsec: 0 }, + ctime: Timespec { sec: 0, nsec: 0 }, + type_: InodeType::File, + mode, + nlinks: 1, + uid: 0, + gid: 0, + rdev: 0, + } + } + + pub fn new_synlink(ino: usize, mode: InodeMode) -> Self { + Self { + dev: 0, + ino, + size: 0, + blk_size: 0, + blocks: 0, + atime: Timespec { sec: 0, nsec: 0 }, + mtime: Timespec { sec: 0, nsec: 0 }, + ctime: Timespec { sec: 0, nsec: 0 }, + type_: InodeType::SymLink, + mode, + nlinks: 1, + uid: 0, + gid: 0, + rdev: 0, + } + } +} + #[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] pub struct Timespec { pub sec: i64, diff --git a/src/services/libs/jinux-std/src/lib.rs b/src/services/libs/jinux-std/src/lib.rs index 7f2b28685..a83a11efc 100644 --- a/src/services/libs/jinux-std/src/lib.rs +++ b/src/services/libs/jinux-std/src/lib.rs @@ -15,6 +15,7 @@ // We should find a proper method to replace this feature with min_specialization, which is a sound feature. #![feature(specialization)] #![feature(fn_traits)] +#![feature(linked_list_remove)] use crate::{ prelude::*,