From 6c0b8d618b577490fe9d6a3062eecb090b2b5e0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=88=CE=BB=CE=BB=CE=B5=CE=BD=20=CE=95=CE=BC=CE=AF=CE=BB?= =?UTF-8?q?=CE=B9=CE=B1=20=CE=86=CE=BD=CE=BD=CE=B1=20Zscheile?= Date: Sat, 10 Jan 2026 15:51:02 +0100 Subject: [PATCH] refactor(fs): refactor path traversal handling --- src/fs/fuse.rs | 161 ++++++++++++-------- src/fs/mem.rs | 392 +++++++++++++++--------------------------------- src/fs/mod.rs | 217 +++++++++++++++------------ src/fs/uhyve.rs | 67 +++++---- 4 files changed, 377 insertions(+), 460 deletions(-) diff --git a/src/fs/fuse.rs b/src/fs/fuse.rs index 74e011b3a2..9bbdc19cab 100644 --- a/src/fs/fuse.rs +++ b/src/fs/fuse.rs @@ -29,10 +29,10 @@ use crate::fs::{ self, AccessPermission, DirectoryEntry, FileAttr, NodeKind, ObjectInterface, OpenOption, SeekWhence, VfsNode, }; +use crate::io; use crate::mm::device_alloc::DeviceAlloc; use crate::syscalls::Dirent64; use crate::time::{time_t, timespec}; -use crate::{arch, io}; // response out layout eg @ https://github.com/zargony/fuse-rs/blob/bf6d1cf03f3277e35b580f3c7b9999255d72ecf3/src/ll/request.rs#L44 // op in/out sizes/layout: https://github.com/hanwen/go-fuse/blob/204b45dba899dfa147235c255908236d5fde2d32/fuse/opcode.go#L439 @@ -1093,61 +1093,78 @@ impl ObjectInterface for FuseDirectoryHandle { } } -#[derive(Debug)] +#[derive(Clone, Debug)] +// The `original_prefix` is the one the directory was originally initialized with, +// `prefix` is the current prefix (normally, `prefix` has `original_prefix` as its prefix). +// This distinction is used for symlink resolution and directory traversal. pub(crate) struct FuseDirectory { - prefix: Option, - attr: FileAttr, + original_prefix: Arc, + prefix: String, } impl FuseDirectory { pub fn new(prefix: Option) -> Self { - let microseconds = arch::kernel::systemtime::now_micros(); - let t = timespec::from_usec(microseconds as i64); - FuseDirectory { - prefix, - attr: FileAttr { - st_mode: AccessPermission::from_bits(0o777).unwrap() | AccessPermission::S_IFDIR, - st_atim: t, - st_mtim: t, - st_ctim: t, - ..Default::default() - }, + original_prefix: Arc::from(prefix.as_deref().unwrap_or_default()), + prefix: prefix.unwrap_or_default(), } } - fn traversal_path(&self, components: &[&str]) -> CString { - let prefix_deref = self.prefix.as_deref(); - let components_with_prefix = prefix_deref.iter().chain(components.iter().rev()); - let path: String = components_with_prefix - .flat_map(|component| ["/", component]) - .collect(); - if path.is_empty() { - CString::new("/").unwrap() - } else { - CString::new(path).unwrap() + fn traversal_this(&self) -> CString { + let mut path = String::new(); + path.push('/'); + path.push_str(&self.prefix); + CString::new(path).unwrap() + } + + fn traversal_path(&self, component: &str) -> CString { + let mut path = String::new(); + if !self.prefix.is_empty() { + path.push('/'); + path.push_str(&self.prefix); } + path.push('/'); + path.push_str(component); + CString::new(path).unwrap() } } +#[async_trait] impl VfsNode for FuseDirectory { /// Returns the node type fn get_kind(&self) -> NodeKind { NodeKind::Directory } - fn get_file_attributes(&self) -> io::Result { - Ok(self.attr) - } - fn get_object(&self) -> io::Result>> { Ok(Arc::new(async_lock::RwLock::new(FuseDirectoryHandle::new( - self.prefix.clone(), + if self.prefix.is_empty() { + None + } else { + Some(self.prefix.clone()) + }, )))) } - fn traverse_readdir(&self, components: &mut Vec<&str>) -> io::Result> { - let path = self.traversal_path(components); + fn dup(&self) -> Box { + Box::new(self.clone()) + } + + async fn traverse_once(&self, component: &str) -> io::Result> { + let mut prefix = self.prefix.clone(); + if !prefix.is_empty() { + prefix.push('/'); + } + prefix.push_str(component); + + Ok(Box::new(Self { + original_prefix: Arc::clone(&self.original_prefix), + prefix, + })) + } + + async fn readdir(&self) -> io::Result> { + let path = self.traversal_this(); debug!("FUSE opendir: {path:#?}"); @@ -1226,36 +1243,54 @@ impl VfsNode for FuseDirectory { Ok(entries) } - fn traverse_stat(&self, components: &mut Vec<&str>) -> io::Result { - let path = self.traversal_path(components); + async fn stat(&self) -> io::Result { + let mut prefix = alloc::borrow::Cow::Borrowed(&self.prefix); - debug!("FUSE stat: {path:#?}"); + loop { + let mut path = String::new(); + path.push('/'); + path.push_str(&prefix); + let path = CString::new(path).unwrap(); - // Is there a better way to implement this? - let (cmd, rsp_payload_len) = ops::Lookup::create(path); - let rsp = get_filesystem_driver() - .unwrap() - .lock() - .send_command(cmd, rsp_payload_len)?; + debug!("FUSE stat: {path:#?}"); - if rsp.headers.out_header.error != 0 { - return Err(Errno::try_from(-rsp.headers.out_header.error).unwrap()); - } + // Is there a better way to implement this? + let (cmd, rsp_payload_len) = ops::Lookup::create(path); + let rsp = get_filesystem_driver() + .unwrap() + .lock() + .send_command(cmd, rsp_payload_len)?; - let entry_out = rsp.headers.op_header; - let attr = entry_out.attr; + if rsp.headers.out_header.error != 0 { + break Err(Errno::try_from(-rsp.headers.out_header.error).unwrap()); + } - if attr.mode & S_IFMT != S_IFLNK { - return Ok(FileAttr::from(attr)); - } + let entry_out = rsp.headers.op_header; + let attr = entry_out.attr; + + if attr.mode & S_IFMT != S_IFLNK { + break Ok(FileAttr::from(attr)); + } - let path = readlink(entry_out.nodeid)?; - let mut components: Vec<&str> = path.split('/').collect(); - self.traverse_stat(&mut components) + let path = readlink(entry_out.nodeid)?; + + // rewind and re-traverse + let mut new_prefix: String = (*self.original_prefix).to_owned(); + if !path.starts_with('/') { + new_prefix.push('/'); + } + + // handle infinite recursion + new_prefix.push_str(&path); + if new_prefix == *prefix { + break Err(Errno::Loop); + } + prefix = alloc::borrow::Cow::Owned(new_prefix); + } } - fn traverse_lstat(&self, components: &mut Vec<&str>) -> io::Result { - let path = self.traversal_path(components); + async fn lstat(&self) -> io::Result { + let path = self.traversal_this(); debug!("FUSE lstat: {path:#?}"); @@ -1267,13 +1302,13 @@ impl VfsNode for FuseDirectory { Ok(FileAttr::from(rsp.headers.op_header.attr)) } - fn traverse_open( + async fn open( &self, - components: &mut Vec<&str>, + component: &str, opt: OpenOption, mode: AccessPermission, ) -> io::Result>> { - let path = self.traversal_path(components); + let path = self.traversal_path(component); debug!("FUSE open: {path:#?}, {opt:?} {mode:?}"); @@ -1345,8 +1380,8 @@ impl VfsNode for FuseDirectory { } } - fn traverse_unlink(&self, components: &mut Vec<&str>) -> io::Result<()> { - let path = self.traversal_path(components); + async fn unlink(&self, component: &str) -> io::Result<()> { + let path = self.traversal_path(component); let (cmd, rsp_payload_len) = ops::Unlink::create(path); let rsp = get_filesystem_driver() @@ -1358,8 +1393,8 @@ impl VfsNode for FuseDirectory { Ok(()) } - fn traverse_rmdir(&self, components: &mut Vec<&str>) -> io::Result<()> { - let path = self.traversal_path(components); + async fn rmdir(&self, component: &str) -> io::Result<()> { + let path = self.traversal_path(component); let (cmd, rsp_payload_len) = ops::Rmdir::create(path); let rsp = get_filesystem_driver() @@ -1371,8 +1406,8 @@ impl VfsNode for FuseDirectory { Ok(()) } - fn traverse_mkdir(&self, components: &mut Vec<&str>, mode: AccessPermission) -> io::Result<()> { - let path = self.traversal_path(components); + async fn mkdir(&self, component: &str, mode: AccessPermission) -> io::Result<()> { + let path = self.traversal_path(component); let (cmd, rsp_payload_len) = ops::Mkdir::create(path, mode.bits()); let rsp = get_filesystem_driver() diff --git a/src/fs/mem.rs b/src/fs/mem.rs index 16305cfa64..f6b562aa11 100644 --- a/src/fs/mem.rs +++ b/src/fs/mem.rs @@ -3,6 +3,7 @@ use alloc::borrow::ToOwned; use alloc::boxed::Box; use alloc::collections::BTreeMap; +use alloc::collections::btree_map::Entry; use alloc::string::String; use alloc::sync::Arc; use alloc::vec::Vec; @@ -14,7 +15,6 @@ use async_lock::{Mutex, RwLock}; use async_trait::async_trait; use crate::errno::Errno; -use crate::executor::block_on; use crate::fd::{AccessPermission, ObjectInterface, OpenOption, PollEvent}; use crate::fs::{DirectoryEntry, FileAttr, FileType, NodeKind, SeekWhence, VfsNode}; use crate::syscalls::Dirent64; @@ -261,11 +261,12 @@ impl RamFileInterface { } } -#[derive(Debug)] +#[derive(Clone, Debug)] pub(crate) struct RomFile { data: Arc, } +#[async_trait] impl VfsNode for RomFile { fn get_kind(&self) -> NodeKind { NodeKind::File @@ -277,24 +278,20 @@ impl VfsNode for RomFile { )))) } - fn get_file_attributes(&self) -> io::Result { - block_on(async { Ok(*self.data.attr.read().await) }, None) + fn dup(&self) -> Box { + Box::new(self.clone()) } - fn traverse_lstat(&self, components: &mut Vec<&str>) -> io::Result { - if components.is_empty() { - self.get_file_attributes() - } else { - Err(Errno::Badf) - } + async fn traverse_once(&self, _component: &str) -> io::Result> { + Err(Errno::Badf) } - fn traverse_stat(&self, components: &mut Vec<&str>) -> io::Result { - if components.is_empty() { - self.get_file_attributes() - } else { - Err(Errno::Badf) - } + async fn lstat(&self) -> io::Result { + Ok(*self.data.attr.read().await) + } + + async fn stat(&self) -> io::Result { + Ok(*self.data.attr.read().await) } } @@ -317,11 +314,12 @@ impl RomFile { } } -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub(crate) struct RamFile { data: Arc>, } +#[async_trait] impl VfsNode for RamFile { fn get_kind(&self) -> NodeKind { NodeKind::File @@ -333,24 +331,20 @@ impl VfsNode for RamFile { )))) } - fn get_file_attributes(&self) -> io::Result { - block_on(async { Ok(self.data.read().await.attr) }, None) + fn dup(&self) -> Box { + Box::new(self.clone()) } - fn traverse_lstat(&self, components: &mut Vec<&str>) -> io::Result { - if components.is_empty() { - self.get_file_attributes() - } else { - Err(Errno::Badf) - } + async fn traverse_once(&self, _component: &str) -> io::Result> { + Err(Errno::Badf) } - fn traverse_stat(&self, components: &mut Vec<&str>) -> io::Result { - if components.is_empty() { - self.get_file_attributes() - } else { - Err(Errno::Badf) - } + async fn lstat(&self) -> io::Result { + Ok(self.data.read().await.attr) + } + + async fn stat(&self) -> io::Result { + Ok(self.data.read().await.attr) } } @@ -374,17 +368,12 @@ impl RamFile { pub struct MemDirectoryInterface { /// Directory entries - inner: - Arc>>>, + inner: Arc>>>, read_idx: Mutex, } impl MemDirectoryInterface { - pub fn new( - inner: Arc< - RwLock>>, - >, - ) -> Self { + pub fn new(inner: Arc>>>) -> Self { Self { inner, read_idx: Mutex::new(0), @@ -455,8 +444,7 @@ impl ObjectInterface for MemDirectoryInterface { #[derive(Debug)] pub(crate) struct MemDirectory { - inner: - Arc>>>, + inner: Arc>>>, attr: FileAttr, } @@ -476,48 +464,9 @@ impl MemDirectory { }, } } - - async fn async_traverse_open( - &self, - components: &mut Vec<&str>, - opt: OpenOption, - mode: AccessPermission, - ) -> io::Result>> { - if let Some(component) = components.pop() { - if components.is_empty() { - let mut guard = self.inner.write().await; - if let Some(file) = guard.get(component) { - if opt.contains(OpenOption::O_DIRECTORY) - && file.get_kind() != NodeKind::Directory - { - return Err(Errno::Notdir); - } - - if file.get_kind() == NodeKind::File || file.get_kind() == NodeKind::Directory { - return file.get_object(); - } else { - return Err(Errno::Noent); - } - } else if opt.contains(OpenOption::O_CREAT) { - let file = Box::new(RamFile::new(mode)); - guard.insert(component.to_owned(), file.clone()); - return Ok(Arc::new(RwLock::new(RamFileInterface::new( - file.data.clone(), - )))); - } else { - return Err(Errno::Noent); - } - } - - if let Some(directory) = self.inner.read().await.get(component) { - return directory.traverse_open(components, opt, mode); - } - } - - Err(Errno::Noent) - } } +#[async_trait] impl VfsNode for MemDirectory { fn get_kind(&self) -> NodeKind { NodeKind::Directory @@ -529,210 +478,117 @@ impl VfsNode for MemDirectory { )))) } - fn get_file_attributes(&self) -> io::Result { + fn dup(&self) -> Box { + Box::new(MemDirectory { + inner: Arc::clone(&self.inner), + attr: self.attr, + }) + } + + async fn traverse_once(&self, component: &str) -> io::Result> { + if let Some(directory) = self.inner.read().await.get(component) { + Ok(directory.dup()) + } else { + Err(Errno::Badf) + } + } + + async fn mkdir(&self, component: &str, mode: AccessPermission) -> io::Result<()> { + self.inner + .write() + .await + .insert(component.to_owned(), Box::new(MemDirectory::new(mode))); + Ok(()) + } + + async fn rmdir(&self, component: &str) -> io::Result<()> { + let mut guard = self.inner.write().await; + let obj = guard.remove(component).ok_or(Errno::Noent)?; + if obj.get_kind() == NodeKind::Directory { + Ok(()) + } else { + guard.insert(component.to_owned(), obj); + Err(Errno::Notdir) + } + } + + async fn unlink(&self, component: &str) -> io::Result<()> { + let mut guard = self.inner.write().await; + let obj = guard.remove(component).ok_or(Errno::Noent)?; + if obj.get_kind() == NodeKind::File { + Ok(()) + } else { + guard.insert(component.to_owned(), obj); + Err(Errno::Isdir) + } + } + + async fn readdir(&self) -> io::Result> { + Ok(self + .inner + .read() + .await + .keys() + .map(|name| DirectoryEntry::new(name.clone())) + .collect()) + } + + async fn lstat(&self) -> io::Result { Ok(self.attr) } - fn traverse_mkdir(&self, components: &mut Vec<&str>, mode: AccessPermission) -> io::Result<()> { - block_on( - async { - if let Some(component) = components.pop() { - if let Some(directory) = self.inner.read().await.get(component) { - return directory.traverse_mkdir(components, mode); - } - - if components.is_empty() { - self.inner - .write() - .await - .insert(component.to_owned(), Box::new(MemDirectory::new(mode))); - return Ok(()); - } - } - - Err(Errno::Badf) - }, - None, - ) - } - - fn traverse_rmdir(&self, components: &mut Vec<&str>) -> io::Result<()> { - block_on( - async { - if let Some(component) = components.pop() { - if components.is_empty() { - let mut guard = self.inner.write().await; - - let obj = guard.remove(component).ok_or(Errno::Noent)?; - if obj.get_kind() == NodeKind::Directory { - return Ok(()); - } else { - guard.insert(component.to_owned(), obj); - return Err(Errno::Notdir); - } - } else if let Some(directory) = self.inner.read().await.get(component) { - return directory.traverse_rmdir(components); - } - } - - Err(Errno::Badf) - }, - None, - ) - } - - fn traverse_unlink(&self, components: &mut Vec<&str>) -> io::Result<()> { - block_on( - async { - if let Some(component) = components.pop() { - if components.is_empty() { - let mut guard = self.inner.write().await; - - let obj = guard.remove(component).ok_or(Errno::Noent)?; - if obj.get_kind() == NodeKind::File { - return Ok(()); - } else { - guard.insert(component.to_owned(), obj); - return Err(Errno::Isdir); - } - } else if let Some(directory) = self.inner.read().await.get(component) { - return directory.traverse_unlink(components); - } - } - - Err(Errno::Badf) - }, - None, - ) - } - - fn traverse_readdir(&self, components: &mut Vec<&str>) -> io::Result> { - block_on( - async { - if let Some(component) = components.pop() { - if let Some(directory) = self.inner.read().await.get(component) { - directory.traverse_readdir(components) - } else { - Err(Errno::Badf) - } - } else { - let mut entries: Vec = Vec::new(); - for name in self.inner.read().await.keys() { - entries.push(DirectoryEntry::new(name.clone())); - } - - Ok(entries) - } - }, - None, - ) - } - - fn traverse_lstat(&self, components: &mut Vec<&str>) -> io::Result { - block_on( - async { - if let Some(component) = components.pop() { - if components.is_empty() - && let Some(node) = self.inner.read().await.get(component) - { - return node.get_file_attributes(); - } - - if let Some(directory) = self.inner.read().await.get(component) { - directory.traverse_lstat(components) - } else { - Err(Errno::Badf) - } - } else { - Err(Errno::Nosys) - } - }, - None, - ) - } - - fn traverse_stat(&self, components: &mut Vec<&str>) -> io::Result { - block_on( - async { - if let Some(component) = components.pop() { - if components.is_empty() - && let Some(node) = self.inner.read().await.get(component) - { - return node.get_file_attributes(); - } - - if let Some(directory) = self.inner.read().await.get(component) { - directory.traverse_stat(components) - } else { - Err(Errno::Badf) - } - } else { - Err(Errno::Nosys) - } - }, - None, - ) + async fn stat(&self) -> io::Result { + Ok(self.attr) } - fn traverse_mount( - &self, - components: &mut Vec<&str>, - obj: Box, - ) -> io::Result<()> { - block_on( - async { - if let Some(component) = components.pop() { - if let Some(directory) = self.inner.read().await.get(component) { - return directory.traverse_mount(components, obj); - } - - if components.is_empty() { - self.inner.write().await.insert(component.to_owned(), obj); - return Ok(()); - } - } - - Err(Errno::Badf) - }, - None, - ) + async fn mount(&self, component: &str, obj: Box) -> io::Result<()> { + let mut guard = self.inner.write().await; + match guard.entry(component.to_owned()) { + Entry::Vacant(vac) => { + vac.insert(obj); + Ok(()) + } + Entry::Occupied(_) => Err(Errno::Badf), + } } - fn traverse_open( + async fn open( &self, - components: &mut Vec<&str>, + component: &str, opt: OpenOption, mode: AccessPermission, ) -> io::Result>> { - block_on(self.async_traverse_open(components, opt, mode), None) + let mut guard = self.inner.write().await; + if let Some(file) = guard.get(component) { + if opt.contains(OpenOption::O_DIRECTORY) && file.get_kind() != NodeKind::Directory { + Err(Errno::Notdir) + } else if file.get_kind() == NodeKind::File || file.get_kind() == NodeKind::Directory { + file.get_object() + } else { + Err(Errno::Noent) + } + } else if opt.contains(OpenOption::O_CREAT) { + let file = Box::new(RamFile::new(mode)); + guard.insert(component.to_owned(), file.clone()); + Ok(Arc::new(RwLock::new(RamFileInterface::new( + file.data.clone(), + )))) + } else { + Err(Errno::Noent) + } } - fn traverse_create_file( + async fn create_file( &self, - components: &mut Vec<&str>, + component: &str, data: &'static [u8], mode: AccessPermission, ) -> io::Result<()> { - block_on( - async { - if let Some(component) = components.pop() { - if components.is_empty() { - let file = RomFile::new(data, mode); - self.inner - .write() - .await - .insert(component.to_owned(), Box::new(file)); - return Ok(()); - } - - if let Some(directory) = self.inner.read().await.get(component) { - return directory.traverse_create_file(components, data, mode); - } - } - - Err(Errno::Noent) - }, - None, - ) + let file = RomFile::new(data, mode); + self.inner + .write() + .await + .insert(component.to_owned(), Box::new(file)); + Ok(()) } } diff --git a/src/fs/mod.rs b/src/fs/mod.rs index ba3642eb46..c007d004a9 100644 --- a/src/fs/mod.rs +++ b/src/fs/mod.rs @@ -50,77 +50,73 @@ pub(crate) enum NodeKind { } /// VfsNode represents an internal node of the ramdisk. -pub(crate) trait VfsNode: core::fmt::Debug { +#[async_trait] +pub(crate) trait VfsNode: core::fmt::Debug + Send + Sync { /// Determines the current node type fn get_kind(&self) -> NodeKind; - /// determines the current file attribute - fn get_file_attributes(&self) -> io::Result { + /// Determine the syscall interface + fn get_object(&self) -> io::Result>> { Err(Errno::Nosys) } - /// Determine the syscall interface - fn get_object(&self) -> io::Result>> { + /// Duplicate an object, if possible + fn dup(&self) -> Box; + + /// Traverse into a subdirectory or file + async fn traverse_once(&self, _component: &str) -> io::Result> { Err(Errno::Nosys) } - /// Helper function to create a new directory node - fn traverse_mkdir( - &self, - _components: &mut Vec<&str>, - _mode: AccessPermission, - ) -> io::Result<()> { + /// Create a new directory node + async fn mkdir(&self, _component: &str, _mode: AccessPermission) -> io::Result<()> { Err(Errno::Nosys) } - /// Helper function to delete a directory node - fn traverse_rmdir(&self, _components: &mut Vec<&str>) -> io::Result<()> { + /// Delete a directory node + async fn rmdir(&self, _component: &str) -> io::Result<()> { Err(Errno::Nosys) } - /// Helper function to remove the specified file - fn traverse_unlink(&self, _components: &mut Vec<&str>) -> io::Result<()> { + /// Remove the specified file + async fn unlink(&self, _component: &str) -> io::Result<()> { Err(Errno::Nosys) } - /// Helper function to open a directory - fn traverse_readdir(&self, _components: &mut Vec<&str>) -> io::Result> { + /// Open a directory + async fn readdir(&self) -> io::Result> { Err(Errno::Nosys) } - /// Helper function to get file status - fn traverse_lstat(&self, _components: &mut Vec<&str>) -> io::Result { + /// Get file status + async fn lstat(&self) -> io::Result { Err(Errno::Nosys) } - /// Helper function to get file status - fn traverse_stat(&self, _components: &mut Vec<&str>) -> io::Result { + /// Get file status + async fn stat(&self) -> io::Result { Err(Errno::Nosys) } - /// Helper function to mount a file system - fn traverse_mount( - &self, - _components: &mut Vec<&str>, - _obj: Box, - ) -> io::Result<()> { + /// Mount a file system + async fn mount(&self, _component: &str, _obj: Box) -> io::Result<()> { Err(Errno::Nosys) } - /// Helper function to open a file - fn traverse_open( + /// Open a file + async fn open( &self, - _components: &mut Vec<&str>, + _component: &str, _option: OpenOption, _mode: AccessPermission, ) -> io::Result>> { Err(Errno::Nosys) } - /// Helper function to create a read-only file - fn traverse_create_file( + /// Create a read-only file + async fn create_file( &self, - _components: &mut Vec<&str>, + _component: &str, _data: &'static [u8], _mode: AccessPermission, ) -> io::Result<()> { @@ -157,6 +153,40 @@ impl Filesystem { } } + /// Traverses a path except for the final component + async fn traverse_prefinal<'a>( + &self, + path: &'a str, + ) -> io::Result<(Box, &'a str)> { + let mut components = path.split('/'); + components.next().ok_or(Errno::Nosys)?; + let mut node = self.root.dup(); + + while let Some(component) = components.next() { + let mut tmp = components.clone(); + if tmp.next().is_none() { + return Ok((node, component)); + } + + node = node.traverse_once(component).await?; + } + + Err(Errno::Noent) + } + + /// Traverses a path, fully + async fn traverse_fully(&self, path: &str) -> io::Result> { + let mut components = path.split('/'); + components.next().ok_or(Errno::Nosys)?; + let mut node = self.root.dup(); + + for component in components { + node = node.traverse_once(component).await?; + } + + Ok(node) + } + /// Tries to open file at given path. pub fn open( &self, @@ -165,45 +195,49 @@ impl Filesystem { mode: AccessPermission, ) -> io::Result>> { debug!("Open file {path} with {opt:?}"); - let mut components: Vec<&str> = path.split('/').collect(); - - components.reverse(); - components.pop(); - - self.root.traverse_open(&mut components, opt, mode) + block_on( + async { + let (dir, component) = self.traverse_prefinal(path).await?; + dir.open(component, opt, mode).await + }, + None, + ) } /// Unlinks a file given by path pub fn unlink(&self, path: &str) -> io::Result<()> { debug!("Unlinking file {path}"); - let mut components: Vec<&str> = path.split('/').collect(); - - components.reverse(); - components.pop(); - - self.root.traverse_unlink(&mut components) + block_on( + async { + let (dir, component) = self.traverse_prefinal(path).await?; + dir.unlink(component).await + }, + None, + ) } /// Remove directory given by path pub fn rmdir(&self, path: &str) -> io::Result<()> { debug!("Removing directory {path}"); - let mut components: Vec<&str> = path.split('/').collect(); - - components.reverse(); - components.pop(); - - self.root.traverse_rmdir(&mut components) + block_on( + async { + let (dir, component) = self.traverse_prefinal(path).await?; + dir.rmdir(component).await + }, + None, + ) } /// Create directory given by path pub fn mkdir(&self, path: &str, mode: AccessPermission) -> io::Result<()> { debug!("Create directory {path}"); - let mut components: Vec<&str> = path.split('/').collect(); - - components.reverse(); - components.pop(); - - self.root.traverse_mkdir(&mut components, mode) + block_on( + async { + let (dir, component) = self.traverse_prefinal(path).await?; + dir.mkdir(component, mode).await + }, + None, + ) } pub fn opendir(&self, path: &str) -> io::Result>> { @@ -215,55 +249,40 @@ impl Filesystem { /// List given directory pub fn readdir(&self, path: &str) -> io::Result> { - if path.trim() == "/" { - let mut components: Vec<&str> = Vec::new(); - self.root.traverse_readdir(&mut components) - } else { - let mut components: Vec<&str> = path.split('/').collect(); - - components.reverse(); - components.pop(); - - self.root.traverse_readdir(&mut components) - } + block_on( + async { self.traverse_fully(path).await?.readdir().await }, + None, + ) } /// stat pub fn stat(&self, path: &str) -> io::Result { debug!("Getting stats {path}"); - - let mut components: Vec<&str> = path.split('/').collect(); - components.reverse(); - components.pop(); - - self.root.traverse_stat(&mut components) + block_on( + async { self.traverse_fully(path).await?.stat().await }, + None, + ) } /// lstat pub fn lstat(&self, path: &str) -> io::Result { debug!("Getting lstats {path}"); - - let mut components: Vec<&str> = path.split('/').collect(); - components.reverse(); - components.pop(); - - self.root.traverse_lstat(&mut components) + block_on( + async { self.traverse_fully(path).await?.lstat().await }, + None, + ) } /// Create new backing-fs at mountpoint mntpath - pub fn mount( - &self, - path: &str, - obj: Box, - ) -> io::Result<()> { + pub fn mount(&self, path: &str, obj: Box) -> io::Result<()> { debug!("Mounting {path}"); - - let mut components: Vec<&str> = path.split('/').collect(); - - components.reverse(); - components.pop(); - - self.root.traverse_mount(&mut components, obj) + block_on( + async { + let (dir, component) = self.traverse_prefinal(path).await?; + dir.mount(component, obj).await + }, + None, + ) } /// Create read-only file @@ -274,13 +293,13 @@ impl Filesystem { mode: AccessPermission, ) -> io::Result<()> { debug!("Create read-only file {path}"); - - let mut components: Vec<&str> = path.split('/').collect(); - - components.reverse(); - components.pop(); - - self.root.traverse_create_file(&mut components, data, mode) + block_on( + async { + let (dir, component) = self.traverse_prefinal(path).await?; + dir.create_file(component, data, mode).await + }, + None, + ) } } diff --git a/src/fs/uhyve.rs b/src/fs/uhyve.rs index 91108e1ba7..992871c85a 100644 --- a/src/fs/uhyve.rs +++ b/src/fs/uhyve.rs @@ -3,7 +3,6 @@ use alloc::boxed::Box; use alloc::ffi::CString; use alloc::string::String; use alloc::sync::Arc; -use alloc::vec::Vec; use async_lock::Mutex; use async_trait::async_trait; @@ -127,53 +126,65 @@ impl Clone for UhyveFileHandle { } } -#[derive(Debug)] +#[derive(Clone, Debug)] +// Unlike src/fs/fuse.rs, we do not prepend / before $prefix +// TODO: rename to `UhyveEntry` pub(crate) struct UhyveDirectory { - prefix: Option, + prefix: String, } impl UhyveDirectory { - pub const fn new(prefix: Option) -> Self { - UhyveDirectory { prefix } + pub fn new(prefix: Option) -> Self { + UhyveDirectory { + prefix: prefix.unwrap_or_default(), + } } - fn traversal_path(&self, components: &[&str]) -> CString { - let prefix_deref = self.prefix.as_deref(); - let components_with_prefix = prefix_deref.iter().chain(components.iter().rev()); - // Unlike src/fs/fuse.rs, we skip the first element here so as to not prepend / before /root - let path: String = components_with_prefix - .flat_map(|component| ["/", component]) - .skip(1) - .collect(); - if path.is_empty() { - CString::new("/").unwrap() - } else { - CString::new(path).unwrap() + fn traversal_path(&self, component: &str) -> CString { + let mut path = self.prefix.clone(); + if !self.prefix.is_empty() { + path.push('/'); } + path.push_str(component); + CString::new(path).unwrap() } } +#[async_trait] impl VfsNode for UhyveDirectory { /// Returns the node type fn get_kind(&self) -> NodeKind { NodeKind::Directory } - fn traverse_stat(&self, _components: &mut Vec<&str>) -> io::Result { + fn dup(&self) -> Box { + Box::new(self.clone()) + } + + async fn traverse_once(&self, component: &str) -> io::Result> { + let mut prefix = self.prefix.clone(); + if !prefix.is_empty() { + prefix.push('/'); + } + prefix.push_str(component); + Ok(Box::new(Self { prefix })) + } + + async fn stat(&self) -> io::Result { Err(Errno::Nosys) } - fn traverse_lstat(&self, _components: &mut Vec<&str>) -> io::Result { + async fn lstat(&self) -> io::Result { Err(Errno::Nosys) } - fn traverse_open( + async fn open( &self, - components: &mut Vec<&str>, + component: &str, opt: OpenOption, mode: AccessPermission, ) -> io::Result>> { - let path = self.traversal_path(components); + let path = self.traversal_path(component); let mut open_params = OpenParams { name: GuestPhysAddr::new( @@ -196,8 +207,8 @@ impl VfsNode for UhyveDirectory { } } - fn traverse_unlink(&self, components: &mut Vec<&str>) -> io::Result<()> { - let path = self.traversal_path(components); + async fn unlink(&self, component: &str) -> io::Result<()> { + let path = self.traversal_path(component); let mut unlink_params = UnlinkParams { name: GuestPhysAddr::new( @@ -216,15 +227,11 @@ impl VfsNode for UhyveDirectory { } } - fn traverse_rmdir(&self, _components: &mut Vec<&str>) -> io::Result<()> { + async fn rmdir(&self, _component: &str) -> io::Result<()> { Err(Errno::Nosys) } - fn traverse_mkdir( - &self, - _components: &mut Vec<&str>, - _mode: AccessPermission, - ) -> io::Result<()> { + async fn mkdir(&self, _component: &str, _mode: AccessPermission) -> io::Result<()> { Err(Errno::Nosys) } }