diff --git a/src/Cargo.lock b/src/Cargo.lock index a3906fb4f..37d09fe8c 100644 --- a/src/Cargo.lock +++ b/src/Cargo.lock @@ -13,6 +13,17 @@ dependencies = [ "rsdp", ] +[[package]] +name = "ahash" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf6ccdb167abbf410dcb915cabd428929d7f6a04980b54a11f26a39f1c7f7107" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", +] + [[package]] name = "anyhow" version = "1.0.32" @@ -91,6 +102,15 @@ version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e63201c624b8c8883921b1a1accc8916c4fa9dbfb15d122b26e4dde945b86bbf" +[[package]] +name = "hashbrown" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ff8ae62cd3a9102e5637afc8452c55acf3844001bd5374e0b0bd7b6616c038" +dependencies = [ + "ahash", +] + [[package]] name = "itertools" version = "0.10.5" @@ -172,6 +192,7 @@ dependencies = [ "jinux-util", "jinux-virtio", "lazy_static", + "lru", "pod", "pod-derive", "ringbuffer", @@ -263,6 +284,21 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "lru" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e7d46de488603ffdd5f30afbc64fbba2378214a2c3a2fb83abf3d33126df17" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "once_cell" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" + [[package]] name = "pod" version = "0.1.0" @@ -429,6 +465,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372" +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + [[package]] name = "virtio-input-decoder" version = "0.1.4" diff --git a/src/services/libs/jinux-std/Cargo.toml b/src/services/libs/jinux-std/Cargo.toml index c4509b0b6..f1a8b7f65 100644 --- a/src/services/libs/jinux-std/Cargo.toml +++ b/src/services/libs/jinux-std/Cargo.toml @@ -27,6 +27,7 @@ ringbuffer = "0.10.0" spin = "0.9.4" vte = "0.10" +lru = "0.9.0" [dependencies.lazy_static] version = "1.0" diff --git a/src/services/libs/jinux-std/src/fs/fs_resolver.rs b/src/services/libs/jinux-std/src/fs/fs_resolver.rs new file mode 100644 index 000000000..0669344f0 --- /dev/null +++ b/src/services/libs/jinux-std/src/fs/fs_resolver.rs @@ -0,0 +1,380 @@ +use crate::prelude::*; +use alloc::str; +use alloc::string::String; + +use super::file::FileDescripter; +use super::inode_handle::InodeHandle; +use super::ramfs::RamFS; +use super::utils::{ + AccessMode, CreationFlags, Dentry, FileSystem, InodeMode, InodeType, StatusFlags, PATH_MAX, + SYMLINKS_MAX, +}; +use super::vfs_inode::VfsInode; + +lazy_static! { + static ref ROOT_FS: Arc = RamFS::new(); +} + +pub struct FsResolver { + root: Arc, + cwd: Arc, +} + +impl Clone for FsResolver { + fn clone(&self) -> Self { + Self { + root: self.root.clone(), + cwd: self.cwd.clone(), + } + } +} + +impl FsResolver { + pub fn new() -> Result { + let root = { + let root_inode = VfsInode::new(ROOT_FS.root_inode())?; + Dentry::new_root(root_inode) + }; + Ok(Self { + root: root.clone(), + cwd: root, + }) + } + + /// Get the root directory + pub fn root(&self) -> &Arc { + &self.root + } + + /// Get the current working directory + pub fn cwd(&self) -> &Arc { + &self.cwd + } + + /// Set the current working directory. + pub fn set_cwd(&mut self, dentry: Arc) { + self.cwd = dentry; + } + + /// Open or create a file inode handler. + pub fn open(&self, path: &FsPath, flags: u32, mode: u16) -> Result { + let creation_flags = CreationFlags::from_bits_truncate(flags); + let status_flags = StatusFlags::from_bits_truncate(flags); + let access_mode = AccessMode::from_u32(flags)?; + let inode_mode = InodeMode::from_bits_truncate(mode); + + let follow_tail_link = !creation_flags.contains(CreationFlags::O_NOFOLLOW); + let dentry = match self.lookup(path, follow_tail_link) { + Ok(dentry) => { + let inode = dentry.inode().raw_inode(); + if inode.metadata().type_ == InodeType::SymLink + && !status_flags.contains(StatusFlags::O_PATH) + { + return_errno_with_message!(Errno::ELOOP, "file is a symlink"); + } + if creation_flags.contains(CreationFlags::O_CREAT) + && creation_flags.contains(CreationFlags::O_EXCL) + { + return_errno_with_message!(Errno::EEXIST, "file exists"); + } + if creation_flags.contains(CreationFlags::O_DIRECTORY) + && inode.metadata().type_ != InodeType::Dir + { + return_errno_with_message!( + Errno::ENOTDIR, + "O_DIRECTORY is specified but file is not a directory" + ); + } + dentry + } + Err(e) + if e.error() == Errno::ENOENT + && creation_flags.contains(CreationFlags::O_CREAT) => + { + if creation_flags.contains(CreationFlags::O_DIRECTORY) { + return_errno_with_message!(Errno::ENOTDIR, "cannot create directory"); + } + let (dir_dentry, file_name) = self.lookup_dir_and_base_name(path)?; + if file_name.ends_with("/") { + return_errno_with_message!(Errno::EISDIR, "path refers to a directory"); + } + if !dir_dentry.inode().raw_inode().metadata().mode.is_writable() { + return_errno_with_message!(Errno::EPERM, "file cannot be created"); + } + let new_dentry = + dir_dentry.create_child(&file_name, InodeType::File, inode_mode)?; + new_dentry + } + Err(e) => return Err(e), + }; + + let inode_handle = InodeHandle::new(dentry, access_mode, status_flags)?; + Ok(inode_handle) + } + + /// Lookup dentry according to FsPath + pub fn lookup(&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)? + } + FsPathInner::CwdRelative(path) => { + self.lookup_from_parent(&self.cwd, path, follow_tail_link)? + } + FsPathInner::Cwd => self.cwd.clone(), + FsPathInner::FdRelative(fd, path) => { + let parent = self.lookup_dentry_from_fd(fd)?; + self.lookup_from_parent(&parent, path, follow_tail_link)? + } + FsPathInner::Fd(fd) => self.lookup_dentry_from_fd(fd)?, + }; + + Ok(dentry) + } + + /// Lookup the dir dentry and base file name of the giving path. + /// + /// If encounters symlinks, should deference them. + fn lookup_dir_and_base_name(&self, path: &FsPath) -> Result<(Arc, String)> { + let (mut dir_dentry, mut base_name) = match path.inner { + FsPathInner::Absolute(path) => { + let (dir, file_name) = split_path(path); + ( + self.lookup_from_parent(&self.root, dir.trim_start_matches('/'), true)?, + String::from(file_name), + ) + } + FsPathInner::CwdRelative(path) => { + let (dir, file_name) = split_path(path); + ( + self.lookup_from_parent(&self.cwd, path, true)?, + String::from(file_name), + ) + } + FsPathInner::FdRelative(fd, path) => { + let (dir, file_name) = split_path(path); + let parent = self.lookup_dentry_from_fd(fd)?; + ( + self.lookup_from_parent(&parent, path, true)?, + String::from(file_name), + ) + } + _ => return_errno!(Errno::ENOENT), + }; + + loop { + match dir_dentry.get(&base_name.trim_end_matches('/')) { + Ok(dentry) if dentry.inode().raw_inode().metadata().type_ == InodeType::SymLink => { + let link = { + let mut link = dentry.inode().raw_inode().read_link()?; + if link.is_empty() { + return_errno_with_message!(Errno::ENOENT, "invalid symlink"); + } + if base_name.ends_with("/") && !link.ends_with("/") { + link += "/"; + } + link + }; + let (dir, file_name) = split_path(&link); + if dir.starts_with("/") { + dir_dentry = + self.lookup_from_parent(&self.root, dir.trim_start_matches('/'), true)?; + base_name = String::from(file_name); + } else { + dir_dentry = self.lookup_from_parent(&dir_dentry, dir, true)?; + base_name = String::from(file_name); + } + } + _ => break, + } + } + + Ok((dir_dentry, base_name)) + } + + /// Lookup dentry from parent + /// + /// The length of `path` cannot exceed PATH_MAX. + /// If `path` ends with `/`, then the returned inode must be a directory inode. + /// + /// While looking up the dentry, symbolic links will be followed for + /// at most `SYMLINKS_MAX` times. + /// + /// If `follow_tail_link` is true and the trailing component is a symlink, + /// it will be followed. + /// Symlinks in earlier components of the path will always be followed. + fn lookup_from_parent( + &self, + parent: &Arc, + relative_path: &str, + follow_tail_link: bool, + ) -> Result> { + debug_assert!(!relative_path.starts_with("/")); + + if relative_path.len() > PATH_MAX { + return_errno_with_message!(Errno::ENAMETOOLONG, "path is too long"); + } + + // To handle symlinks + let mut link_path = String::new(); + let mut follows = 0; + + // Initialize the first dentry and the relative path + let (mut dentry, mut relative_path) = (parent.clone(), relative_path); + + while !relative_path.is_empty() { + let (next_name, path_remain, must_be_dir) = + if let Some((prefix, suffix)) = relative_path.split_once('/') { + let suffix = suffix.trim_start_matches('/'); + (prefix, suffix, true) + } else { + (relative_path, "", false) + }; + + // Iterate next dentry + let next_dentry = dentry.get(next_name)?; + let next_type = next_dentry.inode().raw_inode().metadata().type_; + let next_is_tail = path_remain.is_empty(); + + // If next inode is a symlink, follow symlinks at most `SYMLINKS_MAX` times. + if next_type == InodeType::SymLink && (follow_tail_link || !next_is_tail) { + if follows >= SYMLINKS_MAX { + return_errno_with_message!(Errno::ELOOP, "too many symlinks"); + } + let link_path_remain = { + let mut tmp_link_path = next_dentry.inode().raw_inode().read_link()?; + if tmp_link_path.is_empty() { + return_errno_with_message!(Errno::ENOENT, "empty symlink"); + } + if !path_remain.is_empty() { + tmp_link_path += "/"; + tmp_link_path += path_remain; + } else if must_be_dir { + tmp_link_path += "/"; + } + tmp_link_path + }; + + // Change the dentry and relative path according to symlink + if link_path_remain.starts_with("/") { + dentry = self.root.clone(); + } + link_path.clear(); + link_path.push_str(&link_path_remain.trim_start_matches('/')); + relative_path = &link_path; + follows += 1; + } else { + // If path ends with `/`, the inode must be a directory + if must_be_dir && next_type != InodeType::Dir { + return_errno_with_message!(Errno::ENOTDIR, "inode is not dir"); + } + dentry = next_dentry; + relative_path = path_remain; + } + } + + Ok(dentry) + } + + /// Lookup dentry from the giving fd + fn lookup_dentry_from_fd(&self, fd: FileDescripter) -> Result> { + let current = current!(); + let file_table = current.file_table().lock(); + let inode_handle = file_table + .get_file(fd)? + .as_inode_handle() + .ok_or(Error::with_message(Errno::EBADE, "not inode"))?; + Ok(inode_handle.dentry().clone()) + } +} + +pub const AT_FDCWD: FileDescripter = -100; + +pub struct FsPath<'a> { + inner: FsPathInner<'a>, +} + +#[derive(Debug)] +enum FsPathInner<'a> { + // absolute path + Absolute(&'a str), + // path is relative to Cwd + CwdRelative(&'a str), + // Cwd + Cwd, + // path is relative to DirFd + FdRelative(FileDescripter, &'a str), + // Fd + Fd(FileDescripter), +} + +impl<'a> FsPath<'a> { + pub fn new(dirfd: FileDescripter, path: &'a str) -> Result { + if path.len() > PATH_MAX { + return_errno_with_message!(Errno::ENAMETOOLONG, "path name too long"); + } + + let fs_path_inner = if path.starts_with("/") { + FsPathInner::Absolute(path) + } else if dirfd >= 0 { + if path.is_empty() { + FsPathInner::Fd(dirfd) + } else { + FsPathInner::FdRelative(dirfd, path) + } + } else if dirfd == AT_FDCWD { + if path.is_empty() { + FsPathInner::Cwd + } else { + FsPathInner::CwdRelative(path) + } + } else { + return_errno_with_message!(Errno::EINVAL, "invalid dirfd number"); + }; + + Ok(Self { + inner: fs_path_inner, + }) + } +} + +impl<'a> TryFrom<&'a str> for FsPath<'a> { + type Error = crate::error::Error; + + fn try_from(path: &'a str) -> Result { + if path.is_empty() { + return_errno_with_message!(Errno::ENOENT, "path is an empty string"); + } + FsPath::new(AT_FDCWD, path) + } +} + +/// Split a `path` to (`dir_path`, `file_name`). +/// +/// The `dir_path` must be a directory. +/// +/// The `file_name` is the last component. It can be suffixed by "/". +/// +/// Example: +/// +/// The path "/dir/file/" will be split to ("/dir", "file/"). +fn split_path(path: &str) -> (&str, &str) { + let file_name = path + .split_inclusive('/') + .filter(|&x| x != "/") + .last() + .unwrap_or("."); + + let mut split = path.trim_end_matches('/').rsplitn(2, '/'); + let dir_path = if split.next().unwrap().is_empty() { + "/" + } else { + let mut dir = split.next().unwrap_or(".").trim_end_matches('/'); + if dir.is_empty() { + dir = "/"; + } + dir + }; + + (dir_path, file_name) +} diff --git a/src/services/libs/jinux-std/src/fs/inode_handle/dyn_cap.rs b/src/services/libs/jinux-std/src/fs/inode_handle/dyn_cap.rs index 7b56a6f64..b3a18a2d8 100644 --- a/src/services/libs/jinux-std/src/fs/inode_handle/dyn_cap.rs +++ b/src/services/libs/jinux-std/src/fs/inode_handle/dyn_cap.rs @@ -6,11 +6,11 @@ use super::*; impl InodeHandle { pub fn new( - inode: Arc, + dentry: Arc, access_mode: AccessMode, status_flags: StatusFlags, ) -> Result { - let inode_info = inode.metadata(); + let inode_info = dentry.inode().raw_inode().metadata(); if access_mode.is_readable() && !inode_info.mode.is_readable() { return_errno_with_message!(Errno::EACCES, "File is not readable"); } @@ -21,7 +21,7 @@ impl InodeHandle { return_errno_with_message!(Errno::EISDIR, "Directory cannot open to write"); } let inner = Arc::new(InodeHandle_ { - inode, + dentry, offset: Mutex::new(0), access_mode, status_flags: Mutex::new(status_flags), diff --git a/src/services/libs/jinux-std/src/fs/inode_handle/mod.rs b/src/services/libs/jinux-std/src/fs/inode_handle/mod.rs index 5d59c8143..048c4d791 100644 --- a/src/services/libs/jinux-std/src/fs/inode_handle/mod.rs +++ b/src/services/libs/jinux-std/src/fs/inode_handle/mod.rs @@ -4,16 +4,17 @@ mod dyn_cap; mod static_cap; use super::utils::{ - AccessMode, DirentWriter, DirentWriterContext, Inode, InodeType, SeekFrom, StatusFlags, + AccessMode, Dentry, DirentWriter, DirentWriterContext, InodeType, SeekFrom, StatusFlags, }; use crate::prelude::*; use crate::rights::Rights; use alloc::sync::Arc; +use jinux_frame::vm::VmIo; pub struct InodeHandle(Arc, R); struct InodeHandle_ { - inode: Arc, + dentry: Arc, offset: Mutex, access_mode: AccessMode, status_flags: Mutex, @@ -22,15 +23,20 @@ struct InodeHandle_ { impl InodeHandle_ { pub fn read(&self, buf: &mut [u8]) -> Result { let mut offset = self.offset.lock(); - let file_size = self.inode.metadata().size; + let file_size = self.dentry.inode().raw_inode().metadata().size; let start = file_size.min(*offset); let end = file_size.min(*offset + buf.len()); let len = if self.status_flags.lock().contains(StatusFlags::O_DIRECT) { - self.inode.read_at(start, &mut buf[0..start - end])? + self.dentry + .inode() + .raw_inode() + .read_at(start, &mut buf[0..end - start])? } else { - self.inode.read_at(start, &mut buf[0..start - end])? - // TODO: use page cache - // self.inode.pages().read_at(start, buf[0..start - end])? + self.dentry + .inode() + .pages() + .read_bytes(start, &mut buf[0..end - start])?; + end - start }; *offset += len; @@ -39,20 +45,26 @@ impl InodeHandle_ { pub fn write(&self, buf: &[u8]) -> Result { let mut offset = self.offset.lock(); - let file_size = self.inode.metadata().size; + let file_size = self.dentry.inode().raw_inode().metadata().size; if self.status_flags.lock().contains(StatusFlags::O_APPEND) { *offset = file_size; } let len = if self.status_flags.lock().contains(StatusFlags::O_DIRECT) { - self.inode.write_at(*offset, buf)? + self.dentry.inode().raw_inode().write_at(*offset, buf)? } else { - self.inode.write_at(*offset, buf)? - // TODO: use page cache - // let len = self.inode.pages().write_at(*offset, buf)?; - // if offset + len > file_size { - // self.inode.resize(offset + len)?; - // } - // len + let pages = self.dentry.inode().pages(); + let should_expand_size = *offset + buf.len() > file_size; + if should_expand_size { + pages.resize(*offset + buf.len())?; + } + pages.write_bytes(*offset, buf)?; + if should_expand_size { + self.dentry + .inode() + .raw_inode() + .resize(*offset + buf.len())?; + } + buf.len() }; *offset += len; @@ -69,7 +81,7 @@ impl InodeHandle_ { off as i64 } SeekFrom::End(off /* as i64 */) => { - let file_size = self.inode.metadata().size as i64; + let file_size = self.dentry.inode().raw_inode().metadata().size as i64; assert!(file_size >= 0); file_size .checked_add(off) @@ -116,7 +128,11 @@ impl InodeHandle_ { pub fn readdir(&self, writer: &mut dyn DirentWriter) -> Result { let mut offset = self.offset.lock(); let mut dir_writer_ctx = DirentWriterContext::new(*offset, writer); - let written_size = self.inode.readdir(&mut dir_writer_ctx)?; + let written_size = self + .dentry + .inode() + .raw_inode() + .readdir(&mut dir_writer_ctx)?; *offset = dir_writer_ctx.pos(); Ok(written_size) } @@ -143,4 +159,8 @@ impl InodeHandle { pub fn set_status_flags(&self, new_status_flags: StatusFlags) { self.0.set_status_flags(new_status_flags) } + + pub fn dentry(&self) -> &Arc { + &self.0.dentry + } } diff --git a/src/services/libs/jinux-std/src/fs/mod.rs b/src/services/libs/jinux-std/src/fs/mod.rs index 00ff10abf..b52b3a6b6 100644 --- a/src/services/libs/jinux-std/src/fs/mod.rs +++ b/src/services/libs/jinux-std/src/fs/mod.rs @@ -3,9 +3,12 @@ pub mod fcntl; pub mod file; pub mod file_handle; pub mod file_table; +pub mod fs_resolver; pub mod inode_handle; pub mod ioctl; pub mod poll; +pub mod ramfs; pub mod stat; pub mod stdio; pub mod utils; +pub mod vfs_inode; 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/access_mode.rs b/src/services/libs/jinux-std/src/fs/utils/access_mode.rs index 4a1becd08..62c0e3eb9 100644 --- a/src/services/libs/jinux-std/src/fs/utils/access_mode.rs +++ b/src/services/libs/jinux-std/src/fs/utils/access_mode.rs @@ -1,3 +1,4 @@ +use crate::prelude::*; use crate::rights::Rights; #[allow(non_camel_case_types)] @@ -28,6 +29,21 @@ impl AccessMode { } } +impl AccessMode { + pub fn from_u32(flags: u32) -> Result { + let bits = (flags & 0b11) as u8; + if bits > Self::O_RDWR as u8 { + return_errno_with_message!(Errno::EINVAL, "invalid bits for access mode"); + } + Ok(match bits { + 0 => Self::O_RDONLY, + 1 => Self::O_WRONLY, + 2 => Self::O_RDWR, + _ => unreachable!(), + }) + } +} + impl From for AccessMode { fn from(rights: Rights) -> AccessMode { if rights.contains(Rights::READ) && rights.contains(Rights::WRITE) { diff --git a/src/services/libs/jinux-std/src/fs/utils/creation_flags.rs b/src/services/libs/jinux-std/src/fs/utils/creation_flags.rs new file mode 100644 index 000000000..dc6bfe1d5 --- /dev/null +++ b/src/services/libs/jinux-std/src/fs/utils/creation_flags.rs @@ -0,0 +1,23 @@ +use bitflags::bitflags; + +bitflags! { + pub struct CreationFlags: u32 { + /// create file if it does not exist + const O_CREAT = 1 << 6; + /// error if CREATE and the file exists + const O_EXCL = 1 << 7; + /// not become the process's controlling terminal + const O_NOCTTY = 1 << 8; + /// truncate file upon open + const O_TRUNC = 1 << 9; + /// file is a directory + const O_DIRECTORY = 1 << 16; + /// pathname is not a symbolic link + const O_NOFOLLOW = 1 << 17; + /// close on exec + const O_CLOEXEC = 1 << 19; + /// create an unnamed temporary regular file + /// O_TMPFILE is (_O_TMPFILE | O_DIRECTORY) + const _O_TMPFILE = 1 << 22; + } +} 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 new file mode 100644 index 000000000..412241a20 --- /dev/null +++ b/src/services/libs/jinux-std/src/fs/utils/dentry_cache.rs @@ -0,0 +1,127 @@ +use crate::prelude::*; +use alloc::string::String; + +use super::{InodeMode, InodeType, NAME_MAX}; +use crate::fs::vfs_inode::VfsInode; + +pub struct Dentry { + inner: RwLock, + inode: VfsInode, +} + +struct Dentry_ { + name: String, + this: Weak, + parent: Option>, + children: BTreeMap>, +} + +impl Dentry_ { + pub fn new(name: &str, parent: Option>) -> Self { + Self { + name: String::from(name), + this: Weak::default(), + parent, + children: BTreeMap::new(), + } + } +} + +impl Dentry { + /// Create a new dentry cache with root inode + pub fn new_root(inode: VfsInode) -> Arc { + let root = Self::new("/", inode, None); + root + } + + /// Internal constructor + fn new(name: &str, inode: VfsInode, parent: Option>) -> Arc { + let dentry = { + let inner = RwLock::new(Dentry_::new(name, parent)); + Arc::new(Self { inner, inode }) + }; + dentry.inner.write().this = Arc::downgrade(&dentry); + dentry + } + + fn name(&self) -> String { + self.inner.read().name.clone() + } + + pub fn inode(&self) -> &VfsInode { + &self.inode + } + + pub fn create_child(&self, name: &str, type_: InodeType, mode: InodeMode) -> Result> { + let mut inner = self.inner.write(); + let child = { + let inode = VfsInode::new(self.inode().raw_inode().mknod(name, type_, mode)?)?; + Dentry::new(name, inode, Some(inner.this.clone())) + }; + inner.children.insert(String::from(name), child.clone()); + Ok(child) + } + + fn this(&self) -> Arc { + self.inner.read().this.upgrade().unwrap() + } + + fn parent(&self) -> Option> { + self.inner + .read() + .parent + .as_ref() + .map(|p| p.upgrade().unwrap()) + } + + pub fn get(&self, name: &str) -> Result> { + if self.inode.raw_inode().metadata().type_ != InodeType::Dir { + return_errno!(Errno::ENOTDIR); + } + if name.len() > NAME_MAX { + return_errno!(Errno::ENAMETOOLONG); + } + + let dentry = match name { + "." => self.this(), + ".." => self.parent().unwrap_or(self.this()), + name => { + let mut inner = self.inner.write(); + if let Some(dentry) = inner.children.get(name) { + dentry.clone() + } else { + let inode = VfsInode::new(self.inode.raw_inode().lookup(name)?)?; + let dentry = Dentry::new(name, inode, Some(inner.this.clone())); + inner.children.insert(String::from(name), dentry.clone()); + dentry + } + } + }; + Ok(dentry) + } + + pub fn abs_path(&self) -> String { + let mut path = self.name(); + let mut dentry = self.this(); + + loop { + match dentry.parent() { + None => break, + Some(parent_dentry) => { + path = { + let parent_name = parent_dentry.name(); + if parent_name != "/" { + parent_name + "/" + &path + } else { + parent_name + &path + } + }; + dentry = parent_dentry; + } + } + } + + debug_assert!(path.starts_with("/")); + path + } +} 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 c8fb36313..d3ac45c2d 100644 --- a/src/services/libs/jinux-std/src/fs/utils/inode.rs +++ b/src/services/libs/jinux-std/src/fs/utils/inode.rs @@ -2,6 +2,7 @@ use alloc::string::String; use alloc::sync::Arc; use bitflags::bitflags; use core::any::Any; +use jinux_frame::vm::VmFrame; use super::{DirentWriterContext, FileSystem}; use crate::fs::ioctl::IoctlCmd; @@ -90,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, @@ -101,6 +161,10 @@ pub trait Inode: Any + Sync + Send { fn metadata(&self) -> Metadata; + fn read_page(&self, idx: usize, frame: &VmFrame) -> Result<()>; + + fn write_page(&self, idx: usize, frame: &VmFrame) -> Result<()>; + fn read_at(&self, offset: usize, buf: &mut [u8]) -> Result; fn write_at(&self, offset: usize, buf: &[u8]) -> 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 e744896dd..bf6cb42a1 100644 --- a/src/services/libs/jinux-std/src/fs/utils/mod.rs +++ b/src/services/libs/jinux-std/src/fs/utils/mod.rs @@ -1,15 +1,21 @@ //! VFS components pub use access_mode::AccessMode; +pub use creation_flags::CreationFlags; +pub use dentry_cache::Dentry; pub use dirent_writer::{DirentWriter, DirentWriterContext}; pub use fs::{FileSystem, SuperBlock}; pub use inode::{Inode, InodeMode, InodeType, Metadata, Timespec}; +pub use page_cache::PageCacheManager; pub use status_flags::StatusFlags; mod access_mode; +mod creation_flags; +mod dentry_cache; mod dirent_writer; mod fs; mod inode; +mod page_cache; mod status_flags; #[derive(Copy, PartialEq, Eq, Clone, Debug)] @@ -18,3 +24,12 @@ pub enum SeekFrom { End(i64), Current(i64), } + +/// Maximum bytes in a path +pub const PATH_MAX: usize = 4096; + +/// Maximum bytes in a file name +pub const NAME_MAX: usize = 255; + +/// The upper limit for resolving symbolic links +pub const SYMLINKS_MAX: usize = 40; diff --git a/src/services/libs/jinux-std/src/fs/utils/page_cache.rs b/src/services/libs/jinux-std/src/fs/utils/page_cache.rs new file mode 100644 index 000000000..4f154b6ce --- /dev/null +++ b/src/services/libs/jinux-std/src/fs/utils/page_cache.rs @@ -0,0 +1,131 @@ +use super::Inode; +use crate::prelude::*; +use crate::vm::vmo::Pager; +use jinux_frame::vm::{VmAllocOptions, VmFrame, VmFrameVec}; +use lru::LruCache; + +pub struct PageCacheManager { + pages: Mutex>, + backed_inode: Weak, +} + +impl PageCacheManager { + pub fn new(inode: &Weak) -> Self { + Self { + pages: Mutex::new(LruCache::unbounded()), + backed_inode: inode.clone(), + } + } +} + +impl Pager for PageCacheManager { + fn commit_page(&self, offset: usize) -> Result { + let page_idx = offset / PAGE_SIZE; + let mut pages = self.pages.lock(); + let frame = if let Some(page) = pages.get(&page_idx) { + page.frame() + } else { + let page = if offset < self.backed_inode.upgrade().unwrap().metadata().size { + let mut page = Page::alloc()?; + self.backed_inode + .upgrade() + .unwrap() + .read_page(page_idx, &page.frame())?; + page.set_state(PageState::UpToDate); + page + } else { + Page::alloc_zero()? + }; + let frame = page.frame(); + pages.put(page_idx, page); + frame + }; + Ok(frame) + } + + fn update_page(&self, offset: usize) -> Result<()> { + let page_idx = offset / PAGE_SIZE; + let mut pages = self.pages.lock(); + if let Some(page) = pages.get_mut(&page_idx) { + page.set_state(PageState::Dirty); + } else { + error!("page {} is not in page cache", page_idx); + panic!(); + } + Ok(()) + } + + fn decommit_page(&self, offset: usize) -> Result<()> { + let page_idx = offset / PAGE_SIZE; + let mut pages = self.pages.lock(); + if let Some(page) = pages.pop(&page_idx) { + match page.state() { + PageState::Dirty => self + .backed_inode + .upgrade() + .unwrap() + .write_page(page_idx, &page.frame())?, + _ => (), + } + } else { + warn!("page {} is not in page cache, do nothing", page_idx); + } + Ok(()) + } +} + +struct Page { + frame: VmFrame, + state: PageState, +} + +impl Page { + pub fn alloc() -> Result { + let frame = { + let vm_alloc_option = VmAllocOptions::new(1); + let mut frames = VmFrameVec::allocate(&vm_alloc_option)?; + frames.pop().unwrap() + }; + Ok(Self { + frame, + state: PageState::Uninit, + }) + } + + pub fn alloc_zero() -> Result { + let frame = { + let vm_alloc_option = VmAllocOptions::new(1); + let mut frames = VmFrameVec::allocate(&vm_alloc_option)?; + frames.zero(); + frames.pop().unwrap() + }; + Ok(Self { + frame, + state: PageState::Dirty, + }) + } + + pub fn frame(&self) -> VmFrame { + self.frame.clone() + } + + pub fn state(&self) -> &PageState { + &self.state + } + + pub fn set_state(&mut self, new_state: PageState) { + self.state = new_state; + } +} + +enum PageState { + /// `Uninit` indicates a new allocated page which content has not been initialized. + /// The page is available to write, not available to read. + Uninit, + /// `UpToDate` indicates a page which content is consistent with corresponding disk content. + /// The page is available to read and write. + UpToDate, + /// `Dirty` indicates a page which content has been updated and not written back to underlying disk. + /// The page is available to read and write. + Dirty, +} diff --git a/src/services/libs/jinux-std/src/fs/vfs_inode.rs b/src/services/libs/jinux-std/src/fs/vfs_inode.rs new file mode 100644 index 000000000..7618d71e8 --- /dev/null +++ b/src/services/libs/jinux-std/src/fs/vfs_inode.rs @@ -0,0 +1,30 @@ +use crate::prelude::*; + +use super::utils::{Inode, PageCacheManager}; +use crate::rights::Rights; +use crate::vm::vmo::{Vmo, VmoFlags, VmoOptions}; + +#[derive(Clone)] +pub struct VfsInode { + raw_inode: Arc, + pages: Vmo, +} + +impl VfsInode { + pub fn new(raw_inode: Arc) -> Result { + let page_cache_manager = Arc::new(PageCacheManager::new(&Arc::downgrade(&raw_inode))); + let pages = VmoOptions::::new(raw_inode.metadata().size) + .flags(VmoFlags::RESIZABLE) + .pager(page_cache_manager) + .alloc()?; + Ok(Self { raw_inode, pages }) + } + + pub fn pages(&self) -> &Vmo { + &self.pages + } + + pub fn raw_inode(&self) -> &Arc { + &self.raw_inode + } +} diff --git a/src/services/libs/jinux-std/src/lib.rs b/src/services/libs/jinux-std/src/lib.rs index 1c7691930..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::*, @@ -32,6 +33,7 @@ use crate::{ }; extern crate alloc; +extern crate lru; pub mod driver; pub mod error; diff --git a/src/services/libs/jinux-std/src/vm/vmo/mod.rs b/src/services/libs/jinux-std/src/vm/vmo/mod.rs index 310a14b68..d3a954431 100644 --- a/src/services/libs/jinux-std/src/vm/vmo/mod.rs +++ b/src/services/libs/jinux-std/src/vm/vmo/mod.rs @@ -265,14 +265,14 @@ impl Vmo_ { return_errno_with_message!(Errno::EINVAL, "read range exceeds vmo size"); } let read_range = offset..(offset + read_len); - let frames = self.ensure_all_pages_exist(read_range, false)?; + let frames = self.ensure_all_pages_exist(&read_range, false)?; let read_offset = offset % PAGE_SIZE; Ok(frames.read_bytes(read_offset, buf)?) } /// Ensure all pages inside range are backed up vm frames, returns the frames. - fn ensure_all_pages_exist(&self, range: Range, write_page: bool) -> Result { - let page_idx_range = get_page_idx_range(&range); + fn ensure_all_pages_exist(&self, range: &Range, write_page: bool) -> Result { + let page_idx_range = get_page_idx_range(range); let mut frames = VmFrameVec::empty(); for page_idx in page_idx_range { let mut page_frame = self.get_backup_frame(page_idx, write_page, true)?; @@ -382,9 +382,15 @@ impl Vmo_ { } let write_range = offset..(offset + write_len); - let frames = self.ensure_all_pages_exist(write_range, true)?; + let frames = self.ensure_all_pages_exist(&write_range, true)?; let write_offset = offset % PAGE_SIZE; frames.write_bytes(write_offset, buf)?; + if let Some(pager) = &self.inner.lock().pager { + let page_idx_range = get_page_idx_range(&write_range); + for page_idx in page_idx_range { + pager.update_page(page_idx * PAGE_SIZE)?; + } + } Ok(()) }