From 74cd9daed788411167cc880ac3a690688fcdcfa7 Mon Sep 17 00:00:00 2001 From: Mark Poliakov Date: Thu, 20 Jul 2023 11:59:53 +0300 Subject: [PATCH] proc: add mount/unmount system calls --- lib/vfs/src/debug.rs | 23 +++++++ lib/vfs/src/ioctx.rs | 101 ++++++++++++++++++++++------- lib/vfs/src/lib.rs | 7 ++ lib/vfs/src/node.rs | 79 ++++++++++++++++++++-- src/debug.rs | 5 ++ src/fs/mod.rs | 16 ++++- src/main.rs | 29 +-------- src/mem/phys/mod.rs | 4 +- src/syscall/arg.rs | 32 +++++++++ src/{syscall.rs => syscall/mod.rs} | 54 ++++++++++----- 10 files changed, 271 insertions(+), 79 deletions(-) create mode 100644 lib/vfs/src/debug.rs create mode 100644 src/syscall/arg.rs rename src/{syscall.rs => syscall/mod.rs} (76%) diff --git a/lib/vfs/src/debug.rs b/lib/vfs/src/debug.rs new file mode 100644 index 00000000..677c9f9e --- /dev/null +++ b/lib/vfs/src/debug.rs @@ -0,0 +1,23 @@ +use core::fmt; + +pub(crate) static mut DEBUG_HOOK: Option<&'static dyn Fn(fmt::Arguments)> = None; + +#[macro_export] +macro_rules! debug_raw { + ($($arg:tt)+) => { + $crate::debug::_debug_print(format_args!($($arg)+)) + }; +} + +#[macro_export] +macro_rules! debugln { + ($($arg:tt)+) => { + debug_raw!("[VFS] {}\n", format_args!($($arg)+)) + }; +} + +pub(crate) fn _debug_print(args: fmt::Arguments) { + if let Some(hook) = unsafe { DEBUG_HOOK.as_mut() } { + hook(args); + } +} diff --git a/lib/vfs/src/ioctx.rs b/lib/vfs/src/ioctx.rs index 007e302d..f47a4b94 100644 --- a/lib/vfs/src/ioctx.rs +++ b/lib/vfs/src/ioctx.rs @@ -15,7 +15,13 @@ impl IoContext { } } - fn _find(&self, mut at: VnodeRef, path: &str, follow: bool) -> Result { + fn _find( + &self, + mut at: VnodeRef, + path: &str, + follow: bool, + follow_mount: bool, + ) -> Result { let mut element; let mut rest = path; @@ -35,18 +41,44 @@ impl IoContext { } } - // TODO resolve link target + if follow || follow_mount { + while let Some(target) = at.target() { + assert!(at.is_mountpoint()); + if at.is_mountpoint() && !follow_mount { + break; + } + + debugln!("resolve parent: {:?} -> {:?}", at, target); + at = target; + } + } + + if !at.is_directory() { + return Err(Error::NotADirectory); + } if element.is_empty() && rest.is_empty() { return Ok(at); } - let node = at.lookup_or_load(element)?; + let mut node = at.lookup_or_load(element)?; + + if follow || follow_mount { + while let Some(target) = node.target() { + assert!(node.is_mountpoint()); + if node.is_mountpoint() && !follow_mount { + break; + } + + debugln!("resolve node: {:?} -> {:?}", node, target); + node = target; + } + } if rest.is_empty() { Ok(node) } else { - self._find(node, rest, follow) + self._find(node, rest, follow, follow_mount) } } @@ -55,7 +87,10 @@ impl IoContext { at: Option, mut path: &str, follow: bool, + follow_mount: bool, ) -> Result { + debugln!("_find {:?} in {:?}", path, at); + let at = if path.starts_with('/') { path = path.trim_start_matches('/'); self.root.clone() @@ -65,7 +100,7 @@ impl IoContext { self.cwd.clone() }; - self._find(at, path, follow) + self._find(at, path, follow, follow_mount) } pub fn open( @@ -74,7 +109,7 @@ impl IoContext { path: &str, opts: OpenFlags, ) -> Result { - let node = match self.find(at.clone(), path, true) { + let node = match self.find(at.clone(), path, true, true) { Err(Error::DoesNotExist) => { // TODO check for create option return Err(Error::DoesNotExist); @@ -84,11 +119,15 @@ impl IoContext { node.open(opts) } + + pub fn root(&self) -> &VnodeRef { + &self.root + } } #[cfg(test)] mod tests { - use abi::error::Error; + use yggdrasil_abi::error::Error; use crate::{node::VnodeRef, IoContext}; use std::fmt; @@ -115,7 +154,7 @@ mod tests { impl fmt::Debug for DumpNode<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.node.dump(f, 0) + self.node.dump(f, 0, true) } } @@ -137,79 +176,93 @@ mod tests { // Absolute lookups assert_eq!( - ctx.find(None, "/file1.txt", false).unwrap().name(), + ctx.find(None, "/file1.txt", false, false).unwrap().name(), "file1.txt" ); assert_eq!( - ctx.find(None, "/file3.txt", false).unwrap_err(), + ctx.find(None, "/file3.txt", false, false).unwrap_err(), Error::DoesNotExist ); assert_eq!( - ctx.find(None, "/dir1/file3.txt", false).unwrap().name(), + ctx.find(None, "/dir1/file3.txt", false, false) + .unwrap() + .name(), "file3.txt" ); // Non-absolute lookups from root assert_eq!( - ctx.find(None, "file1.txt", false).unwrap().name(), + ctx.find(None, "file1.txt", false, false).unwrap().name(), "file1.txt" ); assert_eq!( - ctx.find(None, "dir1/file3.txt", false).unwrap().name(), + ctx.find(None, "dir1/file3.txt", false, false) + .unwrap() + .name(), "file3.txt" ); // Absolute lookups from non-root - let cwd = ctx.find(None, "/dir1", false).unwrap(); + let cwd = ctx.find(None, "/dir1", false, false).unwrap(); assert_eq!( - ctx.find(Some(cwd.clone()), "/file1.txt", false) + ctx.find(Some(cwd.clone()), "/file1.txt", false, false) .unwrap() .name(), "file1.txt" ); assert_eq!( - ctx.find(Some(cwd.clone()), "/dir1/file3.txt", false) + ctx.find(Some(cwd.clone()), "/dir1/file3.txt", false, false) .unwrap() .name(), "file3.txt" ); assert_eq!( - ctx.find(Some(cwd.clone()), "/file3.txt", false) + ctx.find(Some(cwd.clone()), "/file3.txt", false, false) .unwrap_err(), Error::DoesNotExist ); assert_eq!( - ctx.find(Some(cwd.clone()), "/dir2", false).unwrap_err(), + ctx.find(Some(cwd.clone()), "/dir2", false, false) + .unwrap_err(), Error::DoesNotExist ); // Non-absolute lookups in non-root assert_eq!( - ctx.find(Some(cwd.clone()), "file3.txt", false) + ctx.find(Some(cwd.clone()), "file3.txt", false, false) .unwrap() .name(), "file3.txt" ); assert_eq!( - ctx.find(Some(cwd.clone()), "././file3.txt", false) + ctx.find(Some(cwd.clone()), "././file3.txt", false, false) .unwrap() .name(), "file3.txt" ); assert_eq!( - ctx.find(Some(cwd.clone()), "../dir1/file3.txt", false) + ctx.find(Some(cwd.clone()), "../dir1/file3.txt", false, false) .unwrap() .name(), "file3.txt" ); assert_eq!( - ctx.find(Some(cwd.clone()), ".", false).unwrap().name(), + ctx.find(Some(cwd.clone()), ".", false, false) + .unwrap() + .name(), "dir1" ); - assert_eq!(ctx.find(Some(cwd.clone()), "..", false).unwrap().name(), ""); assert_eq!( - ctx.find(Some(cwd.clone()), "../..", false).unwrap().name(), + ctx.find(Some(cwd.clone()), "..", false, false) + .unwrap() + .name(), + "" + ); + assert_eq!( + ctx.find(Some(cwd.clone()), "../..", false, false) + .unwrap() + .name(), "" ); } diff --git a/lib/vfs/src/lib.rs b/lib/vfs/src/lib.rs index ce7a02f6..78f9b245 100644 --- a/lib/vfs/src/lib.rs +++ b/lib/vfs/src/lib.rs @@ -7,6 +7,9 @@ extern crate alloc; #[cfg(test)] extern crate std; +#[macro_use] +pub(crate) mod debug; + pub(crate) mod block; pub(crate) mod char; pub(crate) mod file; @@ -62,3 +65,7 @@ fn default_read_exact(this: &mut R, mut buf: &mut [u8]) -> Res Ok(()) } } + +pub unsafe fn init_debug_hook(hook: &'static dyn Fn(core::fmt::Arguments)) { + debug::DEBUG_HOOK.replace(hook); +} diff --git a/lib/vfs/src/node.rs b/lib/vfs/src/node.rs index 8c695531..b9396808 100644 --- a/lib/vfs/src/node.rs +++ b/lib/vfs/src/node.rs @@ -42,6 +42,7 @@ pub struct Vnode { kind: VnodeKind, data: RefCell>>, fs: RefCell>>, + target: RefCell>, } pub trait VnodeImpl { @@ -69,6 +70,7 @@ impl Vnode { kind, data: RefCell::new(None), fs: RefCell::new(None), + target: RefCell::new(None), }) } @@ -82,6 +84,11 @@ impl Vnode { self.kind } + #[inline] + pub fn target(&self) -> Option { + self.target.borrow().clone() + } + #[inline] pub fn data(&self) -> RefMut>> { self.data.borrow_mut() @@ -92,6 +99,11 @@ impl Vnode { self.fs.borrow().clone() } + #[inline] + pub fn set_target(&self, target: Option) { + self.target.replace(target); + } + pub fn parent(self: &VnodeRef) -> VnodeRef { match &self.tree.borrow().parent { Some(parent) => parent.upgrade().unwrap(), @@ -112,6 +124,11 @@ impl Vnode { self.kind == VnodeKind::Directory } + #[inline] + pub fn is_mountpoint(&self) -> bool { + self.is_directory() && self.target.borrow().is_some() + } + // Cache tree operations pub fn add_child(self: &VnodeRef, child: VnodeRef) { let parent_weak = Rc::downgrade(self); @@ -126,9 +143,11 @@ impl Vnode { parent_borrow.children.push(child); } - pub fn dump(&self, f: &mut fmt::Formatter<'_>, depth: usize) -> fmt::Result { - for _ in 0..depth { - f.write_str(" ")?; + pub fn dump(&self, f: &mut fmt::Formatter<'_>, depth: usize, indent: bool) -> fmt::Result { + if indent { + for _ in 0..depth { + f.write_str(" ")?; + } } write!(f, "{:?}", self.name)?; @@ -136,12 +155,20 @@ impl Vnode { match self.kind { VnodeKind::Directory => { let tree = self.tree.borrow(); + let target = self.target(); + + if let Some(target) = target { + assert_eq!(tree.children.len(), 0); + f.write_str(" -> ")?; + return target.dump(f, depth, false); + } + if tree.children.is_empty() { f.write_str(" []")?; } else { f.write_str(" [\n")?; for child in tree.children.iter() { - child.dump(f, depth + 1)?; + child.dump(f, depth + 1, true)?; f.write_str("\n")?; } for _ in 0..depth { @@ -265,6 +292,48 @@ impl Vnode { todo!(); } } + + pub fn mount(self: &VnodeRef, fs_root: VnodeRef) -> Result<(), Error> { + if !self.is_directory() { + return Err(Error::NotADirectory); + } + if !fs_root.is_directory() { + todo!("Filesystem root is not a directory"); + } + if self.target.borrow().is_some() { + todo!("Target mountpoint is busy"); + } + + { + let mut child_borrow = fs_root.tree.borrow_mut(); + if child_borrow.parent.is_some() { + todo!("Filesystem is already mounted somewhere else"); + } + child_borrow.parent = Some(Rc::downgrade(self)); + } + self.target.replace(Some(fs_root)); + + Ok(()) + } + + pub fn unmount_target(self: &VnodeRef) -> Result<(), Error> { + if !self.is_directory() { + return Err(Error::NotADirectory); + } + let Some(fs_root) = self.target.take() else { + todo!(); + }; + + { + let mut target_borrow = fs_root.tree.borrow_mut(); + let Some(parent) = target_borrow.parent.take() else { + todo!() + }; + assert!(Rc::ptr_eq(self, &parent.upgrade().unwrap())); + } + + Ok(()) + } } impl fmt::Debug for Vnode { @@ -288,6 +357,6 @@ impl VnodeDump { impl fmt::Debug for VnodeDump { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.node.dump(f, 0) + self.node.dump(f, 0, true) } } diff --git a/src/debug.rs b/src/debug.rs index 67ed93ea..a514a0c1 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -128,6 +128,11 @@ pub fn init() { DEBUG_PRINTER.init(IrqSafeSpinlock::new(DebugPrinter { sink: PLATFORM.primary_serial().unwrap(), })); + unsafe { + vfs::init_debug_hook(&move |args| { + debug_internal(args, LogLevel::Debug); + }); + } } #[doc = "hide"] diff --git a/src/fs/mod.rs b/src/fs/mod.rs index 5998278b..14a99e67 100644 --- a/src/fs/mod.rs +++ b/src/fs/mod.rs @@ -1,7 +1,13 @@ //! Filesystem implementations +use vfs::VnodeRef; +use yggdrasil_abi::{error::Error, io::MountOptions}; + use crate::util::OneTimeInit; +pub mod devfs; +pub mod tar; + pub struct Initrd { pub phys_page_start: usize, pub phys_page_len: usize, @@ -10,5 +16,11 @@ pub struct Initrd { pub static INITRD_DATA: OneTimeInit = OneTimeInit::new(); -pub mod devfs; -pub mod tar; +pub fn create_filesystem(options: &MountOptions) -> Result { + let fs_name = options.filesystem.unwrap(); + + match fs_name { + "devfs" => Ok(devfs::root().clone()), + _ => todo!(), + } +} diff --git a/src/main.rs b/src/main.rs index f966da6b..14a05aa7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,8 +15,6 @@ extern crate yggdrasil_abi as abi; -use core::ops::DerefMut; - use abi::{ error::Error, io::{OpenFlags, RawFd}, @@ -67,7 +65,7 @@ pub fn kernel_main() { let tty_node = devfs_root.lookup("ttyS0").unwrap(); let ioctx = IoContext::new(root); - let node = ioctx.find(None, "/init", false).unwrap(); + let node = ioctx.find(None, "/init", true, true).unwrap(); let file = node.open(OpenFlags::new().read()).unwrap(); { @@ -83,30 +81,5 @@ pub fn kernel_main() { user_init.enqueue_somewhere(); } - // static USER_PROGRAM: &[u8] = include_bytes!(concat!( - // "../../target/aarch64-unknown-yggdrasil/", - // env!("PROFILE"), - // "/test_program" - // )); - - // let ioctx = IoContext::new(devfs_root.clone()); - - // // Spawn a test user task - // let proc = - // proc::exec::create_from_memory(USER_PROGRAM, &["user-program", "argument 1", "argument 2"]); - - // match proc { - // Ok(proc) => { - // // Setup I/O for the process - // // let mut io = proc.io.lock(); - // // io.set_file(RawFd::STDOUT, todo!()).unwrap(); - - // proc.enqueue_somewhere(); - // } - // Err(err) => { - // warnln!("Failed to create user process: {:?}", err); - // } - // }; - Process::current().exit(0); } diff --git a/src/mem/phys/mod.rs b/src/mem/phys/mod.rs index 4d9b666d..1a2dc4ab 100644 --- a/src/mem/phys/mod.rs +++ b/src/mem/phys/mod.rs @@ -53,12 +53,12 @@ impl PhysicalMemoryRegion { } /// Returns an address range covered by the region - pub const fn range(&self) -> Range { + pub fn range(&self) -> Range { self.base..self.end() } /// Provides an iterator over the pages in the region - pub const fn pages(&self) -> StepBy> { + pub fn pages(&self) -> StepBy> { self.range().step_by(0x1000) } } diff --git a/src/syscall/arg.rs b/src/syscall/arg.rs new file mode 100644 index 00000000..f7dc3e52 --- /dev/null +++ b/src/syscall/arg.rs @@ -0,0 +1,32 @@ +use core::alloc::Layout; + +use yggdrasil_abi::error::Error; + +pub(super) fn arg_buffer_ref<'a>(base: usize, len: usize) -> Result<&'a [u8], Error> { + if base + len > crate::mem::KERNEL_VIRT_OFFSET { + panic!("Invalid argument"); + } + Ok(unsafe { core::slice::from_raw_parts(base as *const u8, len) }) +} + +pub(super) fn arg_buffer_mut<'a>(base: usize, len: usize) -> Result<&'a mut [u8], Error> { + if base + len > crate::mem::KERNEL_VIRT_OFFSET { + panic!("Invalid argument"); + } + Ok(unsafe { core::slice::from_raw_parts_mut(base as *mut u8, len) }) +} + +pub(super) fn arg_user_str<'a>(base: usize, len: usize) -> Result<&'a str, Error> { + let slice = arg_buffer_ref(base, len)?; + Ok(core::str::from_utf8(slice).unwrap()) +} + +pub(super) fn arg_user_ref<'a, T: Sized>(addr: usize) -> Result<&'a T, Error> { + let layout = Layout::new::(); + if addr % layout.align() != 0 { + todo!("Misaligned argument"); + } + // TODO check that addr actually points to mapped (and user-accessible) memory + let value = unsafe { core::mem::transmute::<_, &'a T>(addr) }; + Ok(value) +} diff --git a/src/syscall.rs b/src/syscall/mod.rs similarity index 76% rename from src/syscall.rs rename to src/syscall/mod.rs index 1596b592..746f37e8 100644 --- a/src/syscall.rs +++ b/src/syscall/mod.rs @@ -7,31 +7,17 @@ use abi::{ syscall::SyscallFunction, }; use vfs::{Read, Write}; +use yggdrasil_abi::io::{MountOptions, UnmountOptions}; use crate::{ + fs, mem::table::{PageAttributes, VirtualMemoryManager}, proc::wait, task::process::Process, }; -fn arg_buffer_ref<'a>(base: usize, len: usize) -> Result<&'a [u8], Error> { - if base + len > crate::mem::KERNEL_VIRT_OFFSET { - panic!("Invalid argument"); - } - Ok(unsafe { core::slice::from_raw_parts(base as *const u8, len) }) -} - -fn arg_buffer_mut<'a>(base: usize, len: usize) -> Result<&'a mut [u8], Error> { - if base + len > crate::mem::KERNEL_VIRT_OFFSET { - panic!("Invalid argument"); - } - Ok(unsafe { core::slice::from_raw_parts_mut(base as *mut u8, len) }) -} - -fn arg_user_str<'a>(base: usize, len: usize) -> Result<&'a str, Error> { - let slice = arg_buffer_ref(base, len)?; - Ok(core::str::from_utf8(slice).unwrap()) -} +mod arg; +use arg::*; fn syscall_handler(func: SyscallFunction, args: &[u64]) -> Result { match func { @@ -132,6 +118,38 @@ fn syscall_handler(func: SyscallFunction, args: &[u64]) -> Result io.close_file(fd)?; Ok(0) } + SyscallFunction::Mount => { + let options = arg_user_ref::(args[0] as usize)?; + + let proc = Process::current(); + let mut io = proc.io.lock(); + + let target_node = io.ioctx().find(None, options.target, true, false)?; + if !target_node.is_directory() { + return Err(Error::NotADirectory); + } + + let fs_root = fs::create_filesystem(options)?; + + target_node.mount(fs_root)?; + + debugln!("{:?}", vfs::VnodeDump::new(io.ioctx().root().clone())); + + Ok(0) + } + SyscallFunction::Unmount => { + let options = arg_user_ref::(args[0] as usize)?; + + let proc = Process::current(); + let mut io = proc.io.lock(); + + let mountpoint = io.ioctx().find(None, options.mountpoint, true, false)?; + mountpoint.unmount_target()?; + + debugln!("{:?}", vfs::VnodeDump::new(io.ioctx().root().clone())); + + Ok(0) + } } }