mirror of
https://github.com/asterinas/asterinas.git
synced 2025-06-09 05:16:47 +00:00
Merge pull request #57 from liqinggd/dev-pagecache
Add page cache manager as the pager for vmo
This commit is contained in:
commit
6651a642d3
42
src/Cargo.lock
generated
42
src/Cargo.lock
generated
@ -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"
|
||||
|
@ -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"
|
||||
|
380
src/services/libs/jinux-std/src/fs/fs_resolver.rs
Normal file
380
src/services/libs/jinux-std/src/fs/fs_resolver.rs
Normal file
@ -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<dyn FileSystem> = RamFS::new();
|
||||
}
|
||||
|
||||
pub struct FsResolver {
|
||||
root: Arc<Dentry>,
|
||||
cwd: Arc<Dentry>,
|
||||
}
|
||||
|
||||
impl Clone for FsResolver {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
root: self.root.clone(),
|
||||
cwd: self.cwd.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FsResolver {
|
||||
pub fn new() -> Result<Self> {
|
||||
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<Dentry> {
|
||||
&self.root
|
||||
}
|
||||
|
||||
/// Get the current working directory
|
||||
pub fn cwd(&self) -> &Arc<Dentry> {
|
||||
&self.cwd
|
||||
}
|
||||
|
||||
/// Set the current working directory.
|
||||
pub fn set_cwd(&mut self, dentry: Arc<Dentry>) {
|
||||
self.cwd = dentry;
|
||||
}
|
||||
|
||||
/// Open or create a file inode handler.
|
||||
pub fn open(&self, path: &FsPath, flags: u32, mode: u16) -> Result<InodeHandle> {
|
||||
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<Arc<Dentry>> {
|
||||
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<Dentry>, 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<Dentry>,
|
||||
relative_path: &str,
|
||||
follow_tail_link: bool,
|
||||
) -> Result<Arc<Dentry>> {
|
||||
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<Arc<Dentry>> {
|
||||
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<Self> {
|
||||
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<FsPath> {
|
||||
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)
|
||||
}
|
@ -6,11 +6,11 @@ use super::*;
|
||||
|
||||
impl InodeHandle<Rights> {
|
||||
pub fn new(
|
||||
inode: Arc<dyn Inode>,
|
||||
dentry: Arc<Dentry>,
|
||||
access_mode: AccessMode,
|
||||
status_flags: StatusFlags,
|
||||
) -> Result<Self> {
|
||||
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<Rights> {
|
||||
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),
|
||||
|
@ -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<R = Rights>(Arc<InodeHandle_>, R);
|
||||
|
||||
struct InodeHandle_ {
|
||||
inode: Arc<dyn Inode>,
|
||||
dentry: Arc<Dentry>,
|
||||
offset: Mutex<usize>,
|
||||
access_mode: AccessMode,
|
||||
status_flags: Mutex<StatusFlags>,
|
||||
@ -22,15 +23,20 @@ struct InodeHandle_ {
|
||||
impl InodeHandle_ {
|
||||
pub fn read(&self, buf: &mut [u8]) -> Result<usize> {
|
||||
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<usize> {
|
||||
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<usize> {
|
||||
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<R> InodeHandle<R> {
|
||||
pub fn set_status_flags(&self, new_status_flags: StatusFlags) {
|
||||
self.0.set_status_flags(new_status_flags)
|
||||
}
|
||||
|
||||
pub fn dentry(&self) -> &Arc<Dentry> {
|
||||
&self.0.dentry
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
568
src/services/libs/jinux-std/src/fs/ramfs/fs.rs
Normal file
568
src/services/libs/jinux-std/src/fs/ramfs/fs.rs
Normal file
@ -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<SuperBlock>,
|
||||
root: Arc<RamInode>,
|
||||
inode_allocator: AtomicUsize,
|
||||
}
|
||||
|
||||
impl RamFS {
|
||||
pub fn new() -> Arc<Self> {
|
||||
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<dyn Inode> {
|
||||
self.root.clone()
|
||||
}
|
||||
|
||||
fn sb(&self) -> SuperBlock {
|
||||
self.metadata.read().clone()
|
||||
}
|
||||
}
|
||||
|
||||
struct RamInode(RwLock<Inode_>);
|
||||
|
||||
struct Inode_ {
|
||||
inner: Inner,
|
||||
metadata: Metadata,
|
||||
this: Weak<RamInode>,
|
||||
fs: Weak<RamFS>,
|
||||
}
|
||||
|
||||
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<RamInode>)>,
|
||||
this: Weak<RamInode>,
|
||||
parent: Weak<RamInode>,
|
||||
}
|
||||
|
||||
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<RamInode>, parent: Weak<RamInode>) {
|
||||
self.this = this;
|
||||
self.set_parent(parent);
|
||||
}
|
||||
|
||||
fn set_parent(&mut self, parent: Weak<RamInode>) {
|
||||
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<RamInode>)> {
|
||||
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<RamInode>) {
|
||||
self.children.push_back((Str256::from(name), inode))
|
||||
}
|
||||
|
||||
fn remove_entry(&mut self, idx: usize) -> (Str256, Arc<RamInode>) {
|
||||
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<usize> {
|
||||
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<str> 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<usize> {
|
||||
return_errno_with_message!(Errno::EOPNOTSUPP, "direct read is not supported");
|
||||
}
|
||||
|
||||
fn write_at(&self, offset: usize, buf: &[u8]) -> Result<usize> {
|
||||
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<Arc<dyn Inode>> {
|
||||
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<usize> {
|
||||
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<dyn Inode>, 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::<RamInode>()
|
||||
.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<Arc<dyn Inode>> {
|
||||
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<dyn Inode>, 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::<RamInode>()
|
||||
.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::<RamInode>().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<String> {
|
||||
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<dyn FileSystem> {
|
||||
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)
|
||||
}
|
||||
}
|
10
src/services/libs/jinux-std/src/fs/ramfs/mod.rs
Normal file
10
src/services/libs/jinux-std/src/fs/ramfs/mod.rs
Normal file
@ -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;
|
@ -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<Self> {
|
||||
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<Rights> for AccessMode {
|
||||
fn from(rights: Rights) -> AccessMode {
|
||||
if rights.contains(Rights::READ) && rights.contains(Rights::WRITE) {
|
||||
|
23
src/services/libs/jinux-std/src/fs/utils/creation_flags.rs
Normal file
23
src/services/libs/jinux-std/src/fs/utils/creation_flags.rs
Normal file
@ -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;
|
||||
}
|
||||
}
|
127
src/services/libs/jinux-std/src/fs/utils/dentry_cache.rs
Normal file
127
src/services/libs/jinux-std/src/fs/utils/dentry_cache.rs
Normal file
@ -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<Dentry_>,
|
||||
inode: VfsInode,
|
||||
}
|
||||
|
||||
struct Dentry_ {
|
||||
name: String,
|
||||
this: Weak<Dentry>,
|
||||
parent: Option<Weak<Dentry>>,
|
||||
children: BTreeMap<String, Arc<Dentry>>,
|
||||
}
|
||||
|
||||
impl Dentry_ {
|
||||
pub fn new(name: &str, parent: Option<Weak<Dentry>>) -> 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<Self> {
|
||||
let root = Self::new("/", inode, None);
|
||||
root
|
||||
}
|
||||
|
||||
/// Internal constructor
|
||||
fn new(name: &str, inode: VfsInode, parent: Option<Weak<Dentry>>) -> Arc<Self> {
|
||||
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<Arc<Self>> {
|
||||
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<Dentry> {
|
||||
self.inner.read().this.upgrade().unwrap()
|
||||
}
|
||||
|
||||
fn parent(&self) -> Option<Arc<Dentry>> {
|
||||
self.inner
|
||||
.read()
|
||||
.parent
|
||||
.as_ref()
|
||||
.map(|p| p.upgrade().unwrap())
|
||||
}
|
||||
|
||||
pub fn get(&self, name: &str) -> Result<Arc<Dentry>> {
|
||||
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
|
||||
}
|
||||
}
|
@ -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<()>;
|
||||
|
||||
|
@ -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<usize>;
|
||||
|
||||
fn write_at(&self, offset: usize, buf: &[u8]) -> Result<usize>;
|
||||
|
@ -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;
|
||||
|
131
src/services/libs/jinux-std/src/fs/utils/page_cache.rs
Normal file
131
src/services/libs/jinux-std/src/fs/utils/page_cache.rs
Normal file
@ -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<LruCache<usize, Page>>,
|
||||
backed_inode: Weak<dyn Inode>,
|
||||
}
|
||||
|
||||
impl PageCacheManager {
|
||||
pub fn new(inode: &Weak<dyn Inode>) -> Self {
|
||||
Self {
|
||||
pages: Mutex::new(LruCache::unbounded()),
|
||||
backed_inode: inode.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Pager for PageCacheManager {
|
||||
fn commit_page(&self, offset: usize) -> Result<VmFrame> {
|
||||
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<Self> {
|
||||
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<Self> {
|
||||
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,
|
||||
}
|
30
src/services/libs/jinux-std/src/fs/vfs_inode.rs
Normal file
30
src/services/libs/jinux-std/src/fs/vfs_inode.rs
Normal file
@ -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<dyn Inode>,
|
||||
pages: Vmo,
|
||||
}
|
||||
|
||||
impl VfsInode {
|
||||
pub fn new(raw_inode: Arc<dyn Inode>) -> Result<Self> {
|
||||
let page_cache_manager = Arc::new(PageCacheManager::new(&Arc::downgrade(&raw_inode)));
|
||||
let pages = VmoOptions::<Rights>::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<dyn Inode> {
|
||||
&self.raw_inode
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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<usize>, write_page: bool) -> Result<VmFrameVec> {
|
||||
let page_idx_range = get_page_idx_range(&range);
|
||||
fn ensure_all_pages_exist(&self, range: &Range<usize>, write_page: bool) -> Result<VmFrameVec> {
|
||||
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(())
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user