Add ramfs

This commit is contained in:
LI Qing 2023-01-05 16:55:58 +08:00
parent 4a3b0576b2
commit 93dfaa1cd8
6 changed files with 657 additions and 0 deletions

View File

@ -6,6 +6,7 @@ pub mod file_table;
pub mod inode_handle;
pub mod ioctl;
pub mod poll;
pub mod ramfs;
pub mod stat;
pub mod stdio;
pub mod utils;

View 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)
}
}

View 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;

View File

@ -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<()>;

View File

@ -91,6 +91,65 @@ pub struct Metadata {
pub rdev: usize,
}
impl Metadata {
pub fn new_dir(ino: usize, mode: InodeMode) -> Self {
Self {
dev: 0,
ino,
size: 2,
blk_size: 0,
blocks: 0,
atime: Timespec { sec: 0, nsec: 0 },
mtime: Timespec { sec: 0, nsec: 0 },
ctime: Timespec { sec: 0, nsec: 0 },
type_: InodeType::Dir,
mode,
nlinks: 2,
uid: 0,
gid: 0,
rdev: 0,
}
}
pub fn new_file(ino: usize, mode: InodeMode) -> Self {
Self {
dev: 0,
ino,
size: 0,
blk_size: 0,
blocks: 0,
atime: Timespec { sec: 0, nsec: 0 },
mtime: Timespec { sec: 0, nsec: 0 },
ctime: Timespec { sec: 0, nsec: 0 },
type_: InodeType::File,
mode,
nlinks: 1,
uid: 0,
gid: 0,
rdev: 0,
}
}
pub fn new_synlink(ino: usize, mode: InodeMode) -> Self {
Self {
dev: 0,
ino,
size: 0,
blk_size: 0,
blocks: 0,
atime: Timespec { sec: 0, nsec: 0 },
mtime: Timespec { sec: 0, nsec: 0 },
ctime: Timespec { sec: 0, nsec: 0 },
type_: InodeType::SymLink,
mode,
nlinks: 1,
uid: 0,
gid: 0,
rdev: 0,
}
}
}
#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
pub struct Timespec {
pub sec: i64,

View File

@ -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::*,