proc: add mount/unmount system calls

This commit is contained in:
Mark Poliakov 2023-07-20 11:59:53 +03:00
parent aa6b2ac469
commit 74cd9daed7
10 changed files with 271 additions and 79 deletions

23
lib/vfs/src/debug.rs Normal file
View File

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

View File

@ -15,7 +15,13 @@ impl IoContext {
} }
} }
fn _find(&self, mut at: VnodeRef, path: &str, follow: bool) -> Result<VnodeRef, Error> { fn _find(
&self,
mut at: VnodeRef,
path: &str,
follow: bool,
follow_mount: bool,
) -> Result<VnodeRef, Error> {
let mut element; let mut element;
let mut rest = path; 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() { if element.is_empty() && rest.is_empty() {
return Ok(at); 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() { if rest.is_empty() {
Ok(node) Ok(node)
} else { } else {
self._find(node, rest, follow) self._find(node, rest, follow, follow_mount)
} }
} }
@ -55,7 +87,10 @@ impl IoContext {
at: Option<VnodeRef>, at: Option<VnodeRef>,
mut path: &str, mut path: &str,
follow: bool, follow: bool,
follow_mount: bool,
) -> Result<VnodeRef, Error> { ) -> Result<VnodeRef, Error> {
debugln!("_find {:?} in {:?}", path, at);
let at = if path.starts_with('/') { let at = if path.starts_with('/') {
path = path.trim_start_matches('/'); path = path.trim_start_matches('/');
self.root.clone() self.root.clone()
@ -65,7 +100,7 @@ impl IoContext {
self.cwd.clone() self.cwd.clone()
}; };
self._find(at, path, follow) self._find(at, path, follow, follow_mount)
} }
pub fn open( pub fn open(
@ -74,7 +109,7 @@ impl IoContext {
path: &str, path: &str,
opts: OpenFlags, opts: OpenFlags,
) -> Result<FileRef, Error> { ) -> Result<FileRef, Error> {
let node = match self.find(at.clone(), path, true) { let node = match self.find(at.clone(), path, true, true) {
Err(Error::DoesNotExist) => { Err(Error::DoesNotExist) => {
// TODO check for create option // TODO check for create option
return Err(Error::DoesNotExist); return Err(Error::DoesNotExist);
@ -84,11 +119,15 @@ impl IoContext {
node.open(opts) node.open(opts)
} }
pub fn root(&self) -> &VnodeRef {
&self.root
}
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use abi::error::Error; use yggdrasil_abi::error::Error;
use crate::{node::VnodeRef, IoContext}; use crate::{node::VnodeRef, IoContext};
use std::fmt; use std::fmt;
@ -115,7 +154,7 @@ mod tests {
impl fmt::Debug for DumpNode<'_> { impl fmt::Debug for DumpNode<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 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 // Absolute lookups
assert_eq!( assert_eq!(
ctx.find(None, "/file1.txt", false).unwrap().name(), ctx.find(None, "/file1.txt", false, false).unwrap().name(),
"file1.txt" "file1.txt"
); );
assert_eq!( assert_eq!(
ctx.find(None, "/file3.txt", false).unwrap_err(), ctx.find(None, "/file3.txt", false, false).unwrap_err(),
Error::DoesNotExist Error::DoesNotExist
); );
assert_eq!( assert_eq!(
ctx.find(None, "/dir1/file3.txt", false).unwrap().name(), ctx.find(None, "/dir1/file3.txt", false, false)
.unwrap()
.name(),
"file3.txt" "file3.txt"
); );
// Non-absolute lookups from root // Non-absolute lookups from root
assert_eq!( assert_eq!(
ctx.find(None, "file1.txt", false).unwrap().name(), ctx.find(None, "file1.txt", false, false).unwrap().name(),
"file1.txt" "file1.txt"
); );
assert_eq!( assert_eq!(
ctx.find(None, "dir1/file3.txt", false).unwrap().name(), ctx.find(None, "dir1/file3.txt", false, false)
.unwrap()
.name(),
"file3.txt" "file3.txt"
); );
// Absolute lookups from non-root // Absolute lookups from non-root
let cwd = ctx.find(None, "/dir1", false).unwrap(); let cwd = ctx.find(None, "/dir1", false, false).unwrap();
assert_eq!( assert_eq!(
ctx.find(Some(cwd.clone()), "/file1.txt", false) ctx.find(Some(cwd.clone()), "/file1.txt", false, false)
.unwrap() .unwrap()
.name(), .name(),
"file1.txt" "file1.txt"
); );
assert_eq!( assert_eq!(
ctx.find(Some(cwd.clone()), "/dir1/file3.txt", false) ctx.find(Some(cwd.clone()), "/dir1/file3.txt", false, false)
.unwrap() .unwrap()
.name(), .name(),
"file3.txt" "file3.txt"
); );
assert_eq!( assert_eq!(
ctx.find(Some(cwd.clone()), "/file3.txt", false) ctx.find(Some(cwd.clone()), "/file3.txt", false, false)
.unwrap_err(), .unwrap_err(),
Error::DoesNotExist Error::DoesNotExist
); );
assert_eq!( assert_eq!(
ctx.find(Some(cwd.clone()), "/dir2", false).unwrap_err(), ctx.find(Some(cwd.clone()), "/dir2", false, false)
.unwrap_err(),
Error::DoesNotExist Error::DoesNotExist
); );
// Non-absolute lookups in non-root // Non-absolute lookups in non-root
assert_eq!( assert_eq!(
ctx.find(Some(cwd.clone()), "file3.txt", false) ctx.find(Some(cwd.clone()), "file3.txt", false, false)
.unwrap() .unwrap()
.name(), .name(),
"file3.txt" "file3.txt"
); );
assert_eq!( assert_eq!(
ctx.find(Some(cwd.clone()), "././file3.txt", false) ctx.find(Some(cwd.clone()), "././file3.txt", false, false)
.unwrap() .unwrap()
.name(), .name(),
"file3.txt" "file3.txt"
); );
assert_eq!( assert_eq!(
ctx.find(Some(cwd.clone()), "../dir1/file3.txt", false) ctx.find(Some(cwd.clone()), "../dir1/file3.txt", false, false)
.unwrap() .unwrap()
.name(), .name(),
"file3.txt" "file3.txt"
); );
assert_eq!( assert_eq!(
ctx.find(Some(cwd.clone()), ".", false).unwrap().name(), ctx.find(Some(cwd.clone()), ".", false, false)
.unwrap()
.name(),
"dir1" "dir1"
); );
assert_eq!(ctx.find(Some(cwd.clone()), "..", false).unwrap().name(), "");
assert_eq!( 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(),
"" ""
); );
} }

View File

@ -7,6 +7,9 @@ extern crate alloc;
#[cfg(test)] #[cfg(test)]
extern crate std; extern crate std;
#[macro_use]
pub(crate) mod debug;
pub(crate) mod block; pub(crate) mod block;
pub(crate) mod char; pub(crate) mod char;
pub(crate) mod file; pub(crate) mod file;
@ -62,3 +65,7 @@ fn default_read_exact<R: Read + ?Sized>(this: &mut R, mut buf: &mut [u8]) -> Res
Ok(()) Ok(())
} }
} }
pub unsafe fn init_debug_hook(hook: &'static dyn Fn(core::fmt::Arguments)) {
debug::DEBUG_HOOK.replace(hook);
}

View File

@ -42,6 +42,7 @@ pub struct Vnode {
kind: VnodeKind, kind: VnodeKind,
data: RefCell<Option<Box<dyn VnodeImpl>>>, data: RefCell<Option<Box<dyn VnodeImpl>>>,
fs: RefCell<Option<Rc<dyn Filesystem>>>, fs: RefCell<Option<Rc<dyn Filesystem>>>,
target: RefCell<Option<VnodeRef>>,
} }
pub trait VnodeImpl { pub trait VnodeImpl {
@ -69,6 +70,7 @@ impl Vnode {
kind, kind,
data: RefCell::new(None), data: RefCell::new(None),
fs: RefCell::new(None), fs: RefCell::new(None),
target: RefCell::new(None),
}) })
} }
@ -82,6 +84,11 @@ impl Vnode {
self.kind self.kind
} }
#[inline]
pub fn target(&self) -> Option<VnodeRef> {
self.target.borrow().clone()
}
#[inline] #[inline]
pub fn data(&self) -> RefMut<Option<Box<dyn VnodeImpl>>> { pub fn data(&self) -> RefMut<Option<Box<dyn VnodeImpl>>> {
self.data.borrow_mut() self.data.borrow_mut()
@ -92,6 +99,11 @@ impl Vnode {
self.fs.borrow().clone() self.fs.borrow().clone()
} }
#[inline]
pub fn set_target(&self, target: Option<VnodeRef>) {
self.target.replace(target);
}
pub fn parent(self: &VnodeRef) -> VnodeRef { pub fn parent(self: &VnodeRef) -> VnodeRef {
match &self.tree.borrow().parent { match &self.tree.borrow().parent {
Some(parent) => parent.upgrade().unwrap(), Some(parent) => parent.upgrade().unwrap(),
@ -112,6 +124,11 @@ impl Vnode {
self.kind == VnodeKind::Directory self.kind == VnodeKind::Directory
} }
#[inline]
pub fn is_mountpoint(&self) -> bool {
self.is_directory() && self.target.borrow().is_some()
}
// Cache tree operations // Cache tree operations
pub fn add_child(self: &VnodeRef, child: VnodeRef) { pub fn add_child(self: &VnodeRef, child: VnodeRef) {
let parent_weak = Rc::downgrade(self); let parent_weak = Rc::downgrade(self);
@ -126,22 +143,32 @@ impl Vnode {
parent_borrow.children.push(child); parent_borrow.children.push(child);
} }
pub fn dump(&self, f: &mut fmt::Formatter<'_>, depth: usize) -> fmt::Result { pub fn dump(&self, f: &mut fmt::Formatter<'_>, depth: usize, indent: bool) -> fmt::Result {
if indent {
for _ in 0..depth { for _ in 0..depth {
f.write_str(" ")?; f.write_str(" ")?;
} }
}
write!(f, "{:?}", self.name)?; write!(f, "{:?}", self.name)?;
match self.kind { match self.kind {
VnodeKind::Directory => { VnodeKind::Directory => {
let tree = self.tree.borrow(); 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() { if tree.children.is_empty() {
f.write_str(" []")?; f.write_str(" []")?;
} else { } else {
f.write_str(" [\n")?; f.write_str(" [\n")?;
for child in tree.children.iter() { for child in tree.children.iter() {
child.dump(f, depth + 1)?; child.dump(f, depth + 1, true)?;
f.write_str("\n")?; f.write_str("\n")?;
} }
for _ in 0..depth { for _ in 0..depth {
@ -265,6 +292,48 @@ impl Vnode {
todo!(); 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 { impl fmt::Debug for Vnode {
@ -288,6 +357,6 @@ impl VnodeDump {
impl fmt::Debug for VnodeDump { impl fmt::Debug for VnodeDump {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.node.dump(f, 0) self.node.dump(f, 0, true)
} }
} }

View File

@ -128,6 +128,11 @@ pub fn init() {
DEBUG_PRINTER.init(IrqSafeSpinlock::new(DebugPrinter { DEBUG_PRINTER.init(IrqSafeSpinlock::new(DebugPrinter {
sink: PLATFORM.primary_serial().unwrap(), sink: PLATFORM.primary_serial().unwrap(),
})); }));
unsafe {
vfs::init_debug_hook(&move |args| {
debug_internal(args, LogLevel::Debug);
});
}
} }
#[doc = "hide"] #[doc = "hide"]

View File

@ -1,7 +1,13 @@
//! Filesystem implementations //! Filesystem implementations
use vfs::VnodeRef;
use yggdrasil_abi::{error::Error, io::MountOptions};
use crate::util::OneTimeInit; use crate::util::OneTimeInit;
pub mod devfs;
pub mod tar;
pub struct Initrd { pub struct Initrd {
pub phys_page_start: usize, pub phys_page_start: usize,
pub phys_page_len: usize, pub phys_page_len: usize,
@ -10,5 +16,11 @@ pub struct Initrd {
pub static INITRD_DATA: OneTimeInit<Initrd> = OneTimeInit::new(); pub static INITRD_DATA: OneTimeInit<Initrd> = OneTimeInit::new();
pub mod devfs; pub fn create_filesystem(options: &MountOptions) -> Result<VnodeRef, Error> {
pub mod tar; let fs_name = options.filesystem.unwrap();
match fs_name {
"devfs" => Ok(devfs::root().clone()),
_ => todo!(),
}
}

View File

@ -15,8 +15,6 @@
extern crate yggdrasil_abi as abi; extern crate yggdrasil_abi as abi;
use core::ops::DerefMut;
use abi::{ use abi::{
error::Error, error::Error,
io::{OpenFlags, RawFd}, io::{OpenFlags, RawFd},
@ -67,7 +65,7 @@ pub fn kernel_main() {
let tty_node = devfs_root.lookup("ttyS0").unwrap(); let tty_node = devfs_root.lookup("ttyS0").unwrap();
let ioctx = IoContext::new(root); 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(); let file = node.open(OpenFlags::new().read()).unwrap();
{ {
@ -83,30 +81,5 @@ pub fn kernel_main() {
user_init.enqueue_somewhere(); 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); Process::current().exit(0);
} }

View File

@ -53,12 +53,12 @@ impl PhysicalMemoryRegion {
} }
/// Returns an address range covered by the region /// Returns an address range covered by the region
pub const fn range(&self) -> Range<usize> { pub fn range(&self) -> Range<usize> {
self.base..self.end() self.base..self.end()
} }
/// Provides an iterator over the pages in the region /// Provides an iterator over the pages in the region
pub const fn pages(&self) -> StepBy<Range<usize>> { pub fn pages(&self) -> StepBy<Range<usize>> {
self.range().step_by(0x1000) self.range().step_by(0x1000)
} }
} }

32
src/syscall/arg.rs Normal file
View File

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

View File

@ -7,31 +7,17 @@ use abi::{
syscall::SyscallFunction, syscall::SyscallFunction,
}; };
use vfs::{Read, Write}; use vfs::{Read, Write};
use yggdrasil_abi::io::{MountOptions, UnmountOptions};
use crate::{ use crate::{
fs,
mem::table::{PageAttributes, VirtualMemoryManager}, mem::table::{PageAttributes, VirtualMemoryManager},
proc::wait, proc::wait,
task::process::Process, task::process::Process,
}; };
fn arg_buffer_ref<'a>(base: usize, len: usize) -> Result<&'a [u8], Error> { mod arg;
if base + len > crate::mem::KERNEL_VIRT_OFFSET { use arg::*;
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())
}
fn syscall_handler(func: SyscallFunction, args: &[u64]) -> Result<usize, Error> { fn syscall_handler(func: SyscallFunction, args: &[u64]) -> Result<usize, Error> {
match func { match func {
@ -132,6 +118,38 @@ fn syscall_handler(func: SyscallFunction, args: &[u64]) -> Result<usize, Error>
io.close_file(fd)?; io.close_file(fd)?;
Ok(0) Ok(0)
} }
SyscallFunction::Mount => {
let options = arg_user_ref::<MountOptions>(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::<UnmountOptions>(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)
}
} }
} }