osdev5/fs/vfs/src/node.rs

554 lines
17 KiB
Rust

use crate::{Ioctx, File, FileRef, Filesystem};
use alloc::{borrow::ToOwned, boxed::Box, rc::Rc, string::String, vec::Vec};
use core::cell::{RefCell, RefMut};
use core::fmt;
use libsys::{
error::Errno,
ioctl::IoctlCmd,
stat::{AccessMode, FileMode, OpenFlags, Stat},
};
/// Convenience type alias for [Rc<Vnode>]
pub type VnodeRef = Rc<Vnode>;
/// List of possible vnode types
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum VnodeKind {
/// Node is a directory with create/lookup/remove operations
Directory,
/// Node is a regular file
Regular,
/// Node is a character device
Char,
/// Node is a block device
Block,
}
pub(crate) struct TreeNode {
parent: Option<VnodeRef>,
children: Vec<VnodeRef>,
}
/// File property cache struct
pub struct VnodeProps {
pub mode: FileMode,
}
/// Virtual filesystem node struct, generalizes access to
/// underlying real filesystems
pub struct Vnode {
name: String,
tree: RefCell<TreeNode>,
props: RefCell<VnodeProps>,
kind: VnodeKind,
flags: u32,
target: RefCell<Option<VnodeRef>>,
fs: RefCell<Option<Rc<dyn Filesystem>>>,
data: RefCell<Option<Box<dyn VnodeImpl>>>,
}
/// Interface for "inode" of a real filesystem
pub trait VnodeImpl {
// Directory-only operations
/// Creates a new vnode, sets it up, attaches it (in real FS) to `at` with `name` and
/// returns it
fn create(&mut self, at: VnodeRef, name: &str, kind: VnodeKind) -> Result<VnodeRef, Errno>;
/// Removes the filesystem inode from its parent by erasing its directory entry
fn remove(&mut self, at: VnodeRef, name: &str) -> Result<(), Errno>;
/// Looks up a corresponding directory entry for `name`. If present, loads its inode from
/// storage medium and returns a new vnode associated with it.
fn lookup(&mut self, at: VnodeRef, name: &str) -> Result<VnodeRef, Errno>;
/// Opens a vnode for access. Returns initial file position.
fn open(&mut self, node: VnodeRef, opts: OpenFlags) -> Result<usize, Errno>;
/// Closes a vnode
fn close(&mut self, node: VnodeRef) -> Result<(), Errno>;
/// Changes file's underlying storage size
fn truncate(&mut self, node: VnodeRef, size: usize) -> Result<(), Errno>;
/// Reads `data.len()` bytes into the buffer from file offset `pos`
fn read(&mut self, node: VnodeRef, pos: usize, data: &mut [u8]) -> Result<usize, Errno>;
/// Writes `data.len()` bytes from the buffer to file offset `pos`.
/// Resizes the file storage if necessary.
fn write(&mut self, node: VnodeRef, pos: usize, data: &[u8]) -> Result<usize, Errno>;
/// Retrieves file status
fn stat(&mut self, node: VnodeRef, stat: &mut Stat) -> Result<(), Errno>;
/// Reports the size of this filesystem object in bytes
fn size(&mut self, node: VnodeRef) -> Result<usize, Errno>;
fn is_ready(&mut self, node: VnodeRef, write: bool) -> Result<bool, Errno>;
/// Performs filetype-specific request
fn ioctl(
&mut self,
node: VnodeRef,
cmd: IoctlCmd,
ptr: usize,
len: usize,
) -> Result<usize, Errno>;
}
impl Vnode {
/// If set, allows [File] structures associated with a [Vnode] to
/// be seeked to arbitrary offsets
pub const SEEKABLE: u32 = 1 << 0;
/// Constructs a new [Vnode], wrapping it in [Rc]. The resulting node
/// then needs to have [Vnode::set_data()] called on it to be usable.
pub fn new(name: &str, kind: VnodeKind, flags: u32) -> VnodeRef {
Rc::new(Self {
name: name.to_owned(),
kind,
flags,
props: RefCell::new(VnodeProps {
mode: FileMode::empty(),
}),
tree: RefCell::new(TreeNode {
parent: None,
children: Vec::new(),
}),
target: RefCell::new(None),
fs: RefCell::new(None),
data: RefCell::new(None),
})
}
/// Returns [Vnode]'s path element name
pub fn name(&self) -> &str {
&self.name
}
/// Returns a borrowed reference to cached file properties
pub fn props_mut(&self) -> RefMut<VnodeProps> {
self.props.borrow_mut()
}
/// Sets an associated [VnodeImpl] for the [Vnode]
pub fn set_data(&self, data: Box<dyn VnodeImpl>) {
*self.data.borrow_mut() = Some(data);
}
/// Sets an associated [Filesystem] for the [Vnode]
pub fn set_fs(&self, fs: Rc<dyn Filesystem>) {
*self.fs.borrow_mut() = Some(fs);
}
/// Returns a reference to the associated [VnodeImpl]
pub fn data(&self) -> RefMut<Option<Box<dyn VnodeImpl>>> {
self.data.borrow_mut()
}
/// Returns the associated [Fileystem]
pub fn fs(&self) -> Option<Rc<dyn Filesystem>> {
self.fs.borrow().clone()
}
/// Returns `true` if the vnode represents a directory
pub fn is_directory(&self) -> bool {
self.kind == VnodeKind::Directory
}
/// Returns `true` if the vnode allows arbitrary seeking
pub fn is_seekable(&self) -> bool {
self.flags & Self::SEEKABLE != 0
}
/// Returns kind of the vnode
#[inline(always)]
pub const fn kind(&self) -> VnodeKind {
self.kind
}
// Tree operations
/// Attaches `child` vnode to `self` in in-memory tree. NOTE: does not
/// actually perform any real filesystem operations. Used to build
/// hierarchies for in-memory or volatile filesystems.
pub fn attach(self: &VnodeRef, child: VnodeRef) {
let parent_clone = self.clone();
let mut parent_borrow = self.tree.borrow_mut();
assert!(child
.tree
.borrow_mut()
.parent
.replace(parent_clone)
.is_none());
parent_borrow.children.push(child);
}
fn detach(self: &VnodeRef) {
let mut self_borrow = self.tree.borrow_mut();
let parent = self_borrow.parent.take().unwrap();
let mut parent_borrow = parent.tree.borrow_mut();
let index = parent_borrow
.children
.iter()
.position(|it| Rc::ptr_eq(it, self))
.unwrap();
parent_borrow.children.remove(index);
}
/// Attaches some filesystem's root directory node at another directory
pub fn mount(self: &VnodeRef, root: VnodeRef) -> Result<(), Errno> {
if !self.is_directory() {
return Err(Errno::NotADirectory);
}
if !root.is_directory() {
return Err(Errno::NotADirectory);
}
if self.target.borrow().is_some() {
return Err(Errno::Busy);
}
let mut child_borrow = root.tree.borrow_mut();
if child_borrow.parent.is_some() {
return Err(Errno::Busy);
}
child_borrow.parent = Some(self.clone());
*self.target.borrow_mut() = Some(root.clone());
Ok(())
}
/// Returns this vnode's parent or itself if it has none
pub fn parent(self: &VnodeRef) -> VnodeRef {
self.tree.borrow().parent.as_ref().unwrap_or(self).clone()
}
/// Returns this vnode's mount target (for directories)
pub fn target(self: &VnodeRef) -> Option<VnodeRef> {
self.target.borrow().clone()
}
/// Looks up a child `name` in in-memory tree cache
pub fn lookup(self: &VnodeRef, name: &str) -> Option<VnodeRef> {
assert!(self.is_directory());
self.tree
.borrow()
.children
.iter()
.find(|e| e.name == name)
.cloned()
}
/// Looks up a child `name` in `self`. Will first try looking up a cached
/// vnode and will load it from disk if it's missing.
pub fn lookup_or_load(self: &VnodeRef, name: &str) -> Result<VnodeRef, Errno> {
if let Some(node) = self.lookup(name) {
return Ok(node);
}
if let Some(ref mut data) = *self.data() {
let vnode = data.lookup(self.clone(), name)?;
if let Some(fs) = self.fs() {
vnode.set_fs(fs);
}
self.attach(vnode.clone());
Ok(vnode)
} else {
Err(Errno::DoesNotExist)
}
}
/// Creates a new node `name` in `self`
pub fn create(
self: &VnodeRef,
name: &str,
mode: FileMode,
kind: VnodeKind,
) -> Result<VnodeRef, Errno> {
if self.kind != VnodeKind::Directory {
return Err(Errno::NotADirectory);
}
if name.contains('/') {
return Err(Errno::InvalidArgument);
}
match self.lookup_or_load(name) {
Err(Errno::DoesNotExist) => {}
Ok(_) => return Err(Errno::AlreadyExists),
e => return e,
};
if let Some(ref mut data) = *self.data() {
let vnode = data.create(self.clone(), name, kind)?;
if let Some(fs) = self.fs() {
vnode.set_fs(fs);
}
vnode.props.borrow_mut().mode = mode;
self.attach(vnode.clone());
Ok(vnode)
} else {
Err(Errno::NotImplemented)
}
}
/// Removes a directory entry `name` from `self`
pub fn unlink(self: &VnodeRef, name: &str) -> Result<(), Errno> {
if self.kind != VnodeKind::Directory {
return Err(Errno::NotADirectory);
}
if name.contains('/') {
return Err(Errno::InvalidArgument);
}
if let Some(ref mut data) = *self.data() {
let vnode = self.lookup(name).ok_or(Errno::DoesNotExist)?;
data.remove(self.clone(), name)?;
vnode.detach();
Ok(())
} else {
Err(Errno::NotImplemented)
}
}
/// Opens a vnode for access
pub fn open(self: &VnodeRef, flags: OpenFlags) -> Result<FileRef, Errno> {
if self.kind == VnodeKind::Directory {
return Err(Errno::IsADirectory);
}
let mut open_flags = 0;
match flags & OpenFlags::O_ACCESS {
OpenFlags::O_RDONLY => open_flags |= File::READ,
OpenFlags::O_WRONLY => open_flags |= File::WRITE,
OpenFlags::O_RDWR => open_flags |= File::READ | File::WRITE,
_ => unimplemented!(),
}
if flags.contains(OpenFlags::O_CLOEXEC) {
open_flags |= File::CLOEXEC;
}
if let Some(ref mut data) = *self.data() {
let pos = data.open(self.clone(), flags)?;
Ok(File::normal(self.clone(), pos, open_flags))
} else {
Err(Errno::NotImplemented)
}
}
/// Closes a vnode
pub fn close(self: &VnodeRef) -> Result<(), Errno> {
if let Some(ref mut data) = *self.data() {
data.close(self.clone())
} else {
Err(Errno::NotImplemented)
}
}
/// Reads data from offset `pos` into `buf`
pub fn read(self: &VnodeRef, pos: usize, buf: &mut [u8]) -> Result<usize, Errno> {
if self.kind == VnodeKind::Directory {
return Err(Errno::IsADirectory);
}
if let Some(ref mut data) = *self.data() {
data.read(self.clone(), pos, buf)
} else {
Err(Errno::NotImplemented)
}
}
/// Writes data from `buf` to offset `pos`
pub fn write(self: &VnodeRef, pos: usize, buf: &[u8]) -> Result<usize, Errno> {
if self.kind == VnodeKind::Directory {
return Err(Errno::IsADirectory);
}
if let Some(ref mut data) = *self.data() {
data.write(self.clone(), pos, buf)
} else {
Err(Errno::NotImplemented)
}
}
/// Resizes the vnode data
pub fn truncate(self: &VnodeRef, size: usize) -> Result<(), Errno> {
if self.kind != VnodeKind::Regular {
return Err(Errno::IsADirectory);
}
if let Some(ref mut data) = *self.data() {
data.truncate(self.clone(), size)
} else {
Err(Errno::NotImplemented)
}
}
/// Returns current vnode data size
pub fn size(self: &VnodeRef) -> Result<usize, Errno> {
if let Some(ref mut data) = *self.data() {
data.size(self.clone())
} else {
Err(Errno::NotImplemented)
}
}
/// Reports file status
pub fn stat(self: &VnodeRef, stat: &mut Stat) -> Result<(), Errno> {
if let Some(ref mut data) = *self.data() {
data.stat(self.clone(), stat)
} else {
Err(Errno::NotImplemented)
}
}
/// Performs node-specific requests
pub fn ioctl(self: &VnodeRef, cmd: IoctlCmd, ptr: usize, len: usize) -> Result<usize, Errno> {
if let Some(ref mut data) = *self.data() {
data.ioctl(self.clone(), cmd, ptr, len)
} else {
Err(Errno::NotImplemented)
}
}
pub fn is_ready(self: &VnodeRef, write: bool) -> Result<bool, Errno> {
if let Some(ref mut data) = *self.data() {
data.is_ready(self.clone(), write)
} else {
Err(Errno::NotImplemented)
}
}
pub fn check_access(&self, ioctx: &Ioctx, access: AccessMode) -> Result<(), Errno> {
let props = self.props.borrow();
let mode = props.mode;
if access.contains(AccessMode::F_OK) {
if access.intersects(AccessMode::R_OK | AccessMode::W_OK | AccessMode::X_OK) {
return Err(Errno::InvalidArgument);
}
return Ok(());
} else {
if access.contains(AccessMode::F_OK) {
return Err(Errno::InvalidArgument);
}
// Check user
if access.contains(AccessMode::R_OK) && !mode.contains(FileMode::USER_READ) {
return Err(Errno::PermissionDenied);
}
if access.contains(AccessMode::W_OK) && !mode.contains(FileMode::USER_WRITE) {
return Err(Errno::PermissionDenied);
}
if access.contains(AccessMode::X_OK) && !mode.contains(FileMode::USER_EXEC) {
return Err(Errno::PermissionDenied);
}
// TODO check group
// TODO check other
return Ok(());
}
}
}
impl fmt::Debug for Vnode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Vnode({:?})", self.name)
}
}
#[cfg(test)]
mod tests {
use super::*;
use libsys::{ioctl::IoctlCmd, stat::OpenFlags, stat::Stat};
pub struct DummyInode;
#[auto_inode]
impl VnodeImpl for DummyInode {
fn create(
&mut self,
_at: VnodeRef,
name: &str,
kind: VnodeKind,
) -> Result<VnodeRef, Errno> {
let node = Vnode::new(name, kind, 0);
node.set_data(Box::new(DummyInode {}));
Ok(node)
}
fn remove(&mut self, _at: VnodeRef, _name: &str) -> Result<(), Errno> {
Ok(())
}
fn lookup(&mut self, _at: VnodeRef, _name: &str) -> Result<VnodeRef, Errno> {
Err(Errno::DoesNotExist)
}
}
#[test]
fn test_parent() {
let root = Vnode::new("", VnodeKind::Directory, 0);
let node = Vnode::new("dir0", VnodeKind::Directory, 0);
root.attach(node.clone());
assert!(Rc::ptr_eq(&root.parent(), &root));
assert!(Rc::ptr_eq(&node.parent(), &root));
}
#[test]
fn test_mkdir_unlink() {
let root = Vnode::new("", VnodeKind::Directory, 0);
root.set_data(Box::new(DummyInode {}));
let node = root
.create("test", FileMode::default_dir(), VnodeKind::Directory)
.unwrap();
assert_eq!(
root.create("test", FileMode::default_dir(), VnodeKind::Directory)
.unwrap_err(),
Errno::AlreadyExists
);
assert_eq!(node.props.borrow().mode, FileMode::default_dir());
assert!(Rc::ptr_eq(&node, &root.lookup("test").unwrap()));
assert!(node.data.borrow().is_some());
root.unlink("test").unwrap();
assert!(root.lookup("test").is_none());
}
#[test]
fn test_lookup_attach_detach() {
let root = Vnode::new("", VnodeKind::Directory, 0);
let dir0 = Vnode::new("dir0", VnodeKind::Directory, 0);
let dir1 = Vnode::new("dir1", VnodeKind::Directory, 0);
root.attach(dir0.clone());
root.attach(dir1.clone());
assert!(Rc::ptr_eq(&dir0, &root.lookup("dir0").unwrap()));
assert!(Rc::ptr_eq(&dir1, &root.lookup("dir1").unwrap()));
assert!(Rc::ptr_eq(
&root,
dir0.tree.borrow().parent.as_ref().unwrap()
));
assert!(Rc::ptr_eq(
&root,
dir1.tree.borrow().parent.as_ref().unwrap()
));
assert!(root.lookup("dir2").is_none());
dir0.detach();
assert!(Rc::ptr_eq(&dir1, &root.lookup("dir1").unwrap()));
assert!(Rc::ptr_eq(
&root,
dir1.tree.borrow().parent.as_ref().unwrap()
));
assert!(dir0.tree.borrow().parent.is_none());
assert!(root.lookup("dir0").is_none());
assert!(root.lookup("dir2").is_none());
}
}