Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 98 additions & 63 deletions src/fs/fuse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<String>,
attr: FileAttr,
original_prefix: Arc<str>,
prefix: String,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be a good idea to instead only store the part in prefix that is actually "beyond" original_prefix.

}

impl FuseDirectory {
pub fn new(prefix: Option<String>) -> 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<FileAttr> {
Ok(self.attr)
}

fn get_object(&self) -> io::Result<Arc<async_lock::RwLock<dyn ObjectInterface>>> {
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<Vec<DirectoryEntry>> {
let path = self.traversal_path(components);
fn dup(&self) -> Box<dyn VfsNode> {
Box::new(self.clone())
}

async fn traverse_once(&self, component: &str) -> io::Result<Box<dyn VfsNode>> {
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<Vec<DirectoryEntry>> {
let path = self.traversal_this();

debug!("FUSE opendir: {path:#?}");

Expand Down Expand Up @@ -1226,36 +1243,54 @@ impl VfsNode for FuseDirectory {
Ok(entries)
}

fn traverse_stat(&self, components: &mut Vec<&str>) -> io::Result<FileAttr> {
let path = self.traversal_path(components);
async fn stat(&self) -> io::Result<FileAttr> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to change this method from a recursion to a loop to avoid having to allocate a Box for the .stat (because otherwise calling .await on it inside of the function would lead to an infinitely-sized closure).

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<FileAttr> {
let path = self.traversal_path(components);
async fn lstat(&self) -> io::Result<FileAttr> {
let path = self.traversal_this();

debug!("FUSE lstat: {path:#?}");

Expand All @@ -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<Arc<async_lock::RwLock<dyn ObjectInterface>>> {
let path = self.traversal_path(components);
let path = self.traversal_path(component);

debug!("FUSE open: {path:#?}, {opt:?} {mode:?}");

Expand Down Expand Up @@ -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()
Expand All @@ -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()
Expand All @@ -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()
Expand Down
Loading