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 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<VnodeRef>,
mut path: &str,
follow: bool,
follow_mount: bool,
) -> Result<VnodeRef, Error> {
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<FileRef, Error> {
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(),
""
);
}

View File

@ -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<R: Read + ?Sized>(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);
}

View File

@ -42,6 +42,7 @@ pub struct Vnode {
kind: VnodeKind,
data: RefCell<Option<Box<dyn VnodeImpl>>>,
fs: RefCell<Option<Rc<dyn Filesystem>>>,
target: RefCell<Option<VnodeRef>>,
}
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<VnodeRef> {
self.target.borrow().clone()
}
#[inline]
pub fn data(&self) -> RefMut<Option<Box<dyn VnodeImpl>>> {
self.data.borrow_mut()
@ -92,6 +99,11 @@ impl Vnode {
self.fs.borrow().clone()
}
#[inline]
pub fn set_target(&self, target: Option<VnodeRef>) {
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,22 +143,32 @@ impl Vnode {
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 {
f.write_str(" ")?;
}
}
write!(f, "{:?}", self.name)?;
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)
}
}

View File

@ -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"]

View File

@ -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<Initrd> = OneTimeInit::new();
pub mod devfs;
pub mod tar;
pub fn create_filesystem(options: &MountOptions) -> Result<VnodeRef, Error> {
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;
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);
}

View File

@ -53,12 +53,12 @@ impl PhysicalMemoryRegion {
}
/// 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()
}
/// 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)
}
}

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,
};
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<usize, Error> {
match func {
@ -132,6 +118,38 @@ fn syscall_handler(func: SyscallFunction, args: &[u64]) -> Result<usize, Error>
io.close_file(fd)?;
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)
}
}
}