diff --git a/.vscode/settings.json b/.vscode/settings.json index 8a34a00..d80af9e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ { - "rust-analyzer.cargo.features": ["serial"] + "rust-analyzer.cargo.features": ["serial", "parallel", "async"] } diff --git a/Cargo.toml b/Cargo.toml index 22cc500..64f98dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "easy_fuser" -version = "0.4.0" +version = "0.5.0" edition = "2021" description = "A flexible and idiomatic Fuse implementation for Rust" license = "MIT" @@ -9,15 +9,14 @@ homepage = "https://github.com/Alogani/easy_fuser" repository = "https://github.com/Alogani/easy_fuser" [features] -default = [] +default = ["parallel"] serial = [] parallel = ["dep:threadpool"] -async = ["dep:async-trait", "dep:tokio"] -deadlock_detection = ["parallel", "dep:parking_lot"] - +async = [ "dep:tokio", "dep:async-trait"] [dependencies] # Core dependencies +easy_fuser_macro = { version = "0.1", path = "easy_fuser_macro"} log = "0.4" libc = "0.2" fuser = "0.15" @@ -25,17 +24,15 @@ bitflags = "2.6.0" # Parallel dependencies threadpool = { version = "1.8", optional = true } -# Parking lot is only used for deadlock_detection if feature set -parking_lot = { version = "0.12", features = ["deadlock_detection"], optional = true } # Async dependencies # easy_fuser_async_macro = { path = "./easy_fuser_async_macro", optional = true } tokio = { version = "1.42.0", features = ["full"], optional = true } -async-trait = { version = "0.1.83", optional = true } +async-trait = { version = "0.1.85", optional = true } [dev-dependencies] tempfile = "3.14" env_logger = "0.11" [package.metadata.docs.rs] -features = ["parallel"] \ No newline at end of file +features = ["serial", "parallel"] diff --git a/easy_fuser_macro/Cargo.toml b/easy_fuser_macro/Cargo.toml new file mode 100644 index 0000000..5c9763f --- /dev/null +++ b/easy_fuser_macro/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "easy_fuser_macro" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +syn = {version = "2.0", features = [ "full" ] } +quote = "1.0" +proc-macro2 = "1.0" diff --git a/easy_fuser_macro/src/fuse_driver.rs b/easy_fuser_macro/src/fuse_driver.rs new file mode 100644 index 0000000..eb77b80 --- /dev/null +++ b/easy_fuser_macro/src/fuse_driver.rs @@ -0,0 +1,1459 @@ +use proc_macro2::{Group, TokenStream, TokenTree}; +use quote::{format_ident, quote}; + +use crate::handler_type::HandlerType; + +fn wrap_handler_execution(handler_type: HandlerType, block: TokenStream) -> TokenStream { + match handler_type { + HandlerType::Async => quote! { + self.runtime.spawn(async move { + #block + }); + }, + HandlerType::Serial => block, + HandlerType::Parallel => quote! { + self.threadpool.execute(move || { + #block + }); + }, + } +} + +fn expand_macro_placeholders(handler_type: HandlerType, input: TokenStream) -> TokenStream { + let mut tokens = input.into_iter(); + let mut output = TokenStream::new(); + let mut function_name = String::new(); + let mut arg_names = Vec::new(); + + // Extract function name + while let Some(token) = tokens.next() { + match token { + TokenTree::Ident(ident) if ident == "fn" => { + if let Some(TokenTree::Ident(name)) = tokens.next() { + function_name = name.to_string(); + output.extend(vec![TokenTree::Ident(ident), TokenTree::Ident(name)]); + + // Extract argument names + if let Some(TokenTree::Group(group)) = tokens.next() { + let args = group.stream(); + let mut current_arg = String::new(); + for arg in args.into_iter() { + match arg { + TokenTree::Ident(ident) => { + if ident != "mut" { + current_arg = ident.to_string(); + } + } + TokenTree::Punct(p) if p.as_char() == ':' => { + if !current_arg.is_empty() && current_arg != "self" { + arg_names.push(current_arg.clone()); + } + current_arg.clear(); + } + TokenTree::Punct(p) if p.as_char() == ',' => { + current_arg.clear(); + } + _ => {} + } + } + output.extend(std::iter::once(TokenTree::Group(group))); + } + break; + } + } + _ => output.extend(std::iter::once(token)), + } + } + + if arg_names[0] != "req" { + panic!( + "Invalid function signature: expected 'req', found '{}'", + arg_names[0] + ); + } + arg_names[0] = String::from("&req"); + arg_names.pop(); // remove reply + let req_arg = quote!(&req); + let arg_idents: Vec<_> = arg_names + .iter() + .skip(1) // Skip the first 'req' argument + .map(|arg| { + match arg.as_ref() { + // parent and ino shall be converted from u64 to FileREsolver id + "parent" => format_ident!("parent_id"), + "ino" => format_ident!("ino_id"), + _ => format_ident!("{}", arg), + } + }) + .collect(); + let handler_args = quote! { + #req_arg, #(#arg_idents),* + }; + + // Expand the rest of the tokens + output.extend(expand_macro_tokens( + handler_type, + &function_name, + &handler_args, + false, + tokens, + )); + + output +} + +fn expand_macro_tokens( + handler_type: HandlerType, + function_name: &str, + handler_args: &TokenStream, + mut log_ino: bool, + mut tokens: impl Iterator, +) -> TokenStream { + let mut output = TokenStream::new(); + + while let Some(token) = tokens.next() { + match token { + TokenTree::Punct(punct) if punct.as_char() == '$' => { + if let Some(TokenTree::Ident(ident)) = tokens.next() { + let key = ident.to_string(); + let replacement = match key.as_str() { + "await" => match handler_type { + HandlerType::Async => quote!(.await), + _ => quote!(), + }, + "req" => quote!(let req = RequestInfo::from(req);), + "handler" => match handler_type { + HandlerType::Serial => quote!(let handler = &self.handler;), + _ => quote!(let handler = Arc::clone(&self.handler);), + }, + "resolver" => match handler_type { + HandlerType::Serial => quote!(let resolver = &self.resolver;), + _ => quote!(let resolver = Arc::clone(&self.resolver);), + }, + "ino_id" => { + log_ino = true; + quote!( + let log_ino: u64 = ino; + let ino_id = resolver.resolve_id(ino); + ) + } + "parent_id" => { + log_ino = true; + quote!( + let log_ino: u64 = parent; + let parent_id = resolver.resolve_id(parent); + ) + } + "fh" => quote!(let fh = unsafe { BorrowedFileHandle::from_raw(fh) };), + "wrap" => { + if let Some(TokenTree::Group(group)) = tokens.next() { + wrap_handler_execution( + handler_type, + expand_macro_tokens( + handler_type, + function_name, + handler_args, + log_ino, + group.stream().into_iter(), + ), + ) + } else { + panic!("Expected group after $wrap") + } + } + "handler_args" => handler_args.clone(), + "reply_attr" => reply_attr(), + "reply_entry" => reply_entry(), + "warn_error" => error_response(function_name, false, log_ino), + "info_error" => error_response(function_name, true, log_ino), + unknown => panic!("Unknown $ identifier: {}", unknown), + }; + output.extend(replacement); + } else { + panic!("Expected identifier after $"); + } + } + TokenTree::Group(group) => { + let content = expand_macro_tokens( + handler_type, + function_name, + handler_args, + log_ino, + group.stream().into_iter(), + ); + output.extend(std::iter::once(TokenTree::Group(Group::new( + group.delimiter(), + content, + )))); + } + _ => output.extend(std::iter::once(token)), + } + } + + output +} + +fn error_response(function_name: &str, is_info: bool, log_ino: bool) -> TokenStream { + let error_type = if is_info { quote!(info) } else { quote!(warn) }; + match log_ino { + true => quote! { + Err(e) => { + #error_type!(concat!(#function_name, ": ino {:x?}, [{}], {:?}"), log_ino, e, req); + reply.error(e.raw_error()); + return; + } + }, + false => quote! { + Err(e) => { + #error_type!(concat!(#function_name, ": [{}], {:?}"), e, req); + reply.error(e.raw_error()); + return; + } + }, + } +} + +fn reply_attr() -> TokenStream { + quote! { + Ok(file_attr) => { + let default_ttl = handler.get_default_ttl(); + let (fuse_attr, ttl, _) = file_attr.to_fuse(ino); + reply.attr(&ttl.unwrap_or(default_ttl), &fuse_attr); + }, + } +} + +fn reply_entry() -> TokenStream { + quote! { + Ok(metadata) => { + let default_ttl = handler.get_default_ttl(); + let (id, file_attr) = TId::extract_metadata(metadata); + let ino = resolver.lookup(parent, name, id, true); + let (fuse_attr, ttl, generation) = file_attr.to_fuse(ino); + reply.entry( + &ttl.unwrap_or(default_ttl), + &fuse_attr, + generation.unwrap_or(get_random_generation()), + ); + } + } +} + +fn readdir_impl(handler_type: HandlerType, is_extended_readdir: bool) -> TokenStream { + let fn_name = match is_extended_readdir { + true => quote!(readdirplus), + false => quote!(readdir), + }; + let fn_signature = match is_extended_readdir { + false => quote!(fn #fn_name( + &mut self, + req: &Request, + ino: u64, + fh: u64, + offset: i64, + mut reply: ReplyDirectory, + )), + true => quote!(fn #fn_name( + &mut self, + req: &Request, + ino: u64, + fh: u64, + offset: i64, + mut reply: ReplyDirectoryPlus, + )), + }; + + let extract_metadata = match is_extended_readdir { + true => quote!(TId::extract_metadata(item.1)), + false => quote!(TId::extract_minimal_metadata(item.1)), + }; + + let dirmap_entries_get = match (handler_type, is_extended_readdir) { + (HandlerType::Serial, false) => quote!(&self.dirmap_entries), + (HandlerType::Serial, true) => quote!(&self.dirmapplus_entries), + (_, false) => quote!(Arc::clone(&self.dirmap_entries)), + (_, true) => quote!(Arc::clone(&self.dirmapplus_entries)), + }; + + let dirmap_entries_borrow_mut = match handler_type { + HandlerType::Serial => quote!(dirmap_entries.borrow_mut()), + _ => quote!(dirmap_entries.lock().unwrap()), + }; + + let reply_add = match is_extended_readdir { + true => quote! { + // readdirplus: Add entries with extended attributes + let default_ttl = handler.get_default_ttl(); + while let Some((name, ino, file_attr)) = directory_entries.pop_front() { + let (fuse_attr, ttl, generation) = file_attr.to_fuse(ino); + if reply.add( + ino, + new_offset, + name, + &ttl.unwrap_or(default_ttl), + &fuse_attr, + generation.unwrap_or(get_random_generation()), + ) { + #dirmap_entries_borrow_mut + .insert((ino, new_offset), directory_entries); + break; + } + new_offset += 1; + } + reply.ok(); + }, + false => quote! { + // readdir: Add entries until buffer is full + while let Some((name, ino, kind)) = directory_entries.pop_front() { + if reply.add(ino, new_offset, kind, &name) { + #dirmap_entries_borrow_mut + .insert((ino, new_offset), directory_entries); + break; + } + new_offset += 1; + } + reply.ok(); + }, + }; + + expand_macro_placeholders( + handler_type, + quote! { + #fn_signature { + $req + $handler + $resolver + let dirmap_entries = #dirmap_entries_get; + $wrap { + $ino_id + $fh + + // Validate offset + if offset < 0 { + error!("readdir called with a negative offset"); + reply.error(ErrorKind::InvalidArgument.into()); + return; + } + + // ### Initialize directory deque + let mut directory_entries = match offset { + // First read: fetch children from handler + 0 => match handler.#fn_name(&req, ino_id, fh)$await { + Ok(children) => { + // Unpack and process children + let (child_list, attr_list): (Vec<_>, Vec<_>) = children + .into_iter() + .map(|item| { + let (child_id, child_attr) = #extract_metadata; + ((item.0, child_id), child_attr) + }) + .unzip(); + + // Add children to resolver and create iterator + resolver + .add_children( + ino, + child_list, + #is_extended_readdir, + ) + .into_iter() + .zip(attr_list.into_iter()) + .map(|((file_name, file_ino), file_attr)| { + (file_name, file_ino, file_attr) + }) + .collect() + } + $warn_error + }, + // Subsequent reads: retrieve saved iterator + _ => match { #dirmap_entries_borrow_mut.remove(&(ino, offset)) } { + Some(directory_entries) => directory_entries, + None => { + // Case when fuse tries to read again after the final item + reply.ok(); + return; + } + }, + }; + + let mut new_offset = offset + 1; + + #reply_add + } + } + }, + ) +} + +fn generate_fuse_operation_handlers(handler_type: HandlerType) -> Vec { + let mut result = Vec::new(); + result.push(expand_macro_placeholders( + handler_type, + quote! { + fn access(&mut self, req: &Request, ino: u64, mask: i32, reply: ReplyEmpty) { + $req + $handler + $resolver + $wrap { + $ino_id + let mask = AccessMask::from_bits_retain(mask); + match handler.access($handler_args)$await { + Ok(()) => reply.ok(), + $warn_error + } + } + } + }, + )); + result.push(expand_macro_placeholders( + handler_type, + quote! { + fn bmap(&mut self, req: &Request<'_>, ino: u64, blocksize: u32, idx: u64, reply: ReplyBmap) { + $req + $handler + $resolver + $wrap { + $ino_id + match handler.bmap($handler_args)$await { + Ok(block) => reply.bmap(block), + $warn_error + } + } + } + }, + )); + result.push(expand_macro_placeholders( + handler_type, + quote! { + fn copy_file_range( + &mut self, + req: &Request, + ino_in: u64, + fh_in: u64, + offset_in: i64, + ino_out: u64, + fh_out: u64, + offset_out: i64, + len: u64, + flags: u32, + reply: ReplyWrite, + ) { + $req + $handler + $resolver + $wrap { + let ino_in = resolver.resolve_id(ino_in); + let fh_in = unsafe { BorrowedFileHandle::from_raw(fh_in) }; + let ino_out = resolver.resolve_id(ino_out); + let fh_out = unsafe { BorrowedFileHandle::from_raw(fh_out) }; + match handler.copy_file_range($handler_args)$await { + Ok(bytes_written) => reply.written(bytes_written), + $warn_error + } + } + } + }, + )); + result.push(expand_macro_placeholders( + handler_type, + quote! { + fn create( + &mut self, + req: &Request, + parent: u64, + name: &OsStr, + mode: u32, + umask: u32, + flags: i32, + reply: ReplyCreate, + ) { + $req + $handler + $resolver + let name = name.to_owned(); + $wrap { + $parent_id + let name = name.as_ref(); + let flags = OpenFlags::from_bits_retain(flags); + match handler.create($handler_args)$await { + Ok((file_handle, metadata, response_flags)) => { + let default_ttl = handler.get_default_ttl(); + let (id, file_attr) = TId::extract_metadata(metadata); + let ino = resolver.lookup(parent, &name, id, true); + let (fuse_attr, ttl, generation) = file_attr.to_fuse(ino); + reply.created( + &ttl.unwrap_or(default_ttl), + &fuse_attr, + generation.unwrap_or(get_random_generation()), + file_handle.as_raw(), + response_flags.bits(), + ); + }, + $warn_error + } + } + } + }, + )); + result.push(expand_macro_placeholders( + handler_type, + quote! { + fn fallocate( + &mut self, + req: &Request, + ino: u64, + fh: u64, + offset: i64, + length: i64, + mode: i32, + reply: ReplyEmpty, + ) { + $req + $handler + $resolver + $wrap { + $ino_id + $fh + let mode = FallocateFlags::from_bits_retain(mode); + match handler.fallocate($handler_args)$await { + Ok(()) => reply.ok(), + $warn_error + } + } + } + }, + )); + result.push(expand_macro_placeholders( + handler_type, + quote!{ + fn flush(&mut self, req: &Request, ino: u64, fh: u64, lock_owner: u64, reply: ReplyEmpty) { + $req + $handler + $resolver + $wrap { + $ino_id + $fh + match handler.flush($handler_args)$await { + Ok(()) => reply.ok(), + $warn_error + } + } + } + }, + )); + result.push(expand_macro_placeholders( + handler_type, + quote! { + fn forget(&mut self, req: &Request, ino: u64, nlookup: u64) { + $req + let ino_id = self.resolver.resolve_id(ino); + self.handler.forget(&req, ino_id, nlookup); + self.resolver.forget(ino, nlookup); + } + }, + )); + result.push(expand_macro_placeholders( + handler_type, + quote!{ + fn fsync(&mut self, req: &Request, ino: u64, fh: u64, datasync: bool, reply: ReplyEmpty) { + $req + $handler + $resolver + $wrap { + $ino_id + $fh + match handler.fsync($handler_args)$await { + Ok(()) => reply.ok(), + $warn_error + } + } + } + }, + )); + result.push(expand_macro_placeholders( + handler_type, + quote!{ + fn fsyncdir(&mut self, req: &Request, ino: u64, fh: u64, datasync: bool, reply: ReplyEmpty) { + $req + $handler + $resolver + $wrap { + $ino_id + $fh + match handler.fsyncdir($handler_args)$await { + Ok(()) => reply.ok(), + $warn_error + } + } + } + }, + )); + result.push(expand_macro_placeholders( + handler_type, + quote! { + fn getattr(&mut self, req: &Request, ino: u64, fh: Option, reply: ReplyAttr) { + $req + $handler + $resolver + $wrap { + $ino_id + let fh = fh.map(|fh| unsafe { BorrowedFileHandle::from_raw(fh) }); + match handler.getattr($handler_args)$await { + $reply_attr + $warn_error + } + } + } + }, + )); + result.push(expand_macro_placeholders( + handler_type, + quote! { + fn getlk( + &mut self, + req: &Request<'_>, + ino: u64, + fh: u64, + lock_owner: u64, + start: u64, + end: u64, + typ: i32, + pid: u32, + reply: ReplyLock, + ) { + $req + $handler + $resolver + $wrap { + $ino_id + $fh + let lock_info = LockInfo { + start, + end, + lock_type: LockType::from_bits_retain(typ), + pid, + }; + match handler.getlk(&req, ino_id, fh, lock_owner, lock_info)$await { + Ok(lock_info) => reply.locked(lock_info.start, + lock_info.end, + lock_info.lock_type.bits(), + lock_info.pid, + ), + $warn_error + } + } + } + }, + )); + result.push(expand_macro_placeholders( + handler_type, + quote! { + fn getxattr(&mut self, req: &Request, ino: u64, name: &OsStr, size: u32, reply: ReplyXattr) { + $req + $handler + $resolver + let name = name.to_owned(); + $wrap { + $ino_id + let name = name.as_ref(); + match handler.getxattr($handler_args)$await { + Ok(xattr_data) => { + if size == 0 { + reply.size(xattr_data.len() as u32); + } else if size >= xattr_data.len() as u32 { + reply.data(&xattr_data); + } else { + reply.error(ErrorKind::ResultTooLarge.into()); + } + } + $warn_error + }; + }; + } + } + )); + result.push(expand_macro_placeholders( + handler_type, + quote! { + fn ioctl( + &mut self, + req: &Request<'_>, + ino: u64, + fh: u64, + flags: u32, + cmd: u32, + in_data: &[u8], + out_size: u32, + reply: ReplyIoctl, + ) { + $req + $handler + $resolver + let in_data = in_data.to_owned(); + $wrap { + $ino_id + $fh + let flags = IOCtlFlags::from_bits_retain(flags); + match handler.ioctl($handler_args)$await { + Ok((result, data)) => reply.ioctl(result, &data), + $warn_error + }; + }; + } + }, + )); + result.push(expand_macro_placeholders( + handler_type, + quote! { + fn link( + &mut self, + req: &Request, + ino: u64, + newparent: u64, + newname: &OsStr, + reply: ReplyEntry, + ) { + $req + $handler + $resolver + let parent = newparent; + let newname = newname.to_owned(); + $wrap { + $ino_id + let newparent = resolver.resolve_id(newparent); + let newname = newname.as_ref(); + let name = newname; + match handler.link($handler_args)$await { + $reply_entry + $warn_error + } + }; + } + }, + )); + result.push(expand_macro_placeholders( + handler_type, + quote! { + fn listxattr(&mut self, req: &Request, ino: u64, size: u32, reply: ReplyXattr) { + $req + $handler + $resolver + $wrap { + $ino_id + match handler.listxattr($handler_args)$await { + Ok(xattr_data) => { + if size == 0 { + reply.size(xattr_data.len() as u32); + } else if size >= xattr_data.len() as u32 { + reply.data(&xattr_data); + } else { + reply.error(ErrorKind::ResultTooLarge.into()); + } + } + $warn_error + }; + }; + } + }, + )); + result.push(expand_macro_placeholders( + handler_type, + quote! { + fn lookup(&mut self, req: &Request, parent: u64, name: &OsStr, reply: ReplyEntry) { + $req + $handler + $resolver + let name = name.to_owned(); + $wrap { + $parent_id + let name = name.as_ref(); + match handler.lookup($handler_args)$await { + $reply_entry + // Lookup is preemptivly done in normal situations, we don't need to log an error + // eg: before creating a file + $info_error + } + }; + } + }, + )); + result.push(expand_macro_placeholders( + handler_type, + quote! { + fn lseek( + &mut self, + req: &Request, + ino: u64, + fh: u64, + offset: i64, + whence: i32, + reply: ReplyLseek, + ) { + $req + $handler + $resolver + $wrap { + $ino_id + $fh + let seek = seek_from_raw(Some(whence), offset); + match handler.lseek( + &req, + ino_id, + fh, + seek, + )$await { + Ok(new_offset) => reply.offset(new_offset), + $warn_error + }; + } + } + }, + )); + result.push(expand_macro_placeholders( + handler_type, + quote! { + fn mkdir( + &mut self, + req: &Request, + parent: u64, + name: &OsStr, + mode: u32, + umask: u32, + reply: ReplyEntry, + ) { + $req + $handler + $resolver + let name = name.to_owned(); + $wrap { + $parent_id + let name = name.as_ref(); + match handler.mkdir($handler_args)$await { + $reply_entry + $warn_error + } + }; + } + }, + )); + result.push(expand_macro_placeholders( + handler_type, + quote! { + fn mknod( + &mut self, + req: &Request, + parent: u64, + name: &OsStr, + mode: u32, + umask: u32, + rdev: u32, + reply: ReplyEntry, + ) { + $req + $handler + $resolver + let name = name.to_owned(); + $wrap { + $parent_id + let name = name.as_ref(); + let rdev = DeviceType::from_rdev(rdev.try_into().unwrap()); + match handler.mknod($handler_args)$await { + $reply_entry + $warn_error + } + }; + } + }, + )); + result.push(expand_macro_placeholders( + handler_type, + quote! { + fn open(&mut self, req: &Request, ino: u64, flags: i32, reply: ReplyOpen) { + $req + $handler + $resolver + $wrap { + $ino_id + let flags = OpenFlags::from_bits_retain(flags); + match handler.open($handler_args)$await { + Ok((file_handle, response_flags)) => { + reply.opened(file_handle.as_raw(), response_flags.bits()) + } + $warn_error + }; + }; + } + }, + )); + result.push(expand_macro_placeholders( + handler_type, + quote! { + fn opendir(&mut self, req: &Request, ino: u64, flags: i32, reply: ReplyOpen) { + $req + $handler + $resolver + $wrap { + $ino_id + let flags = OpenFlags::from_bits_retain(flags); + match handler.opendir($handler_args)$await { + Ok((file_handle, response_flags)) => { + reply.opened(file_handle.as_raw(), response_flags.bits()) + } + $warn_error + }; + }; + } + }, + )); + result.push(expand_macro_placeholders( + handler_type, + quote! { + fn read( + &mut self, + req: &Request, + ino: u64, + fh: u64, + offset: i64, + size: u32, + flags: i32, + lock_owner: Option, + reply: ReplyData, + ) { + $req + $handler + $resolver + $wrap { + $ino_id + $fh + let offset = seek_from_raw(Some(match i32::try_from(offset) { + Ok(offset) => offset, + Err(_) => { + reply.error(ErrorKind::InputOutputError.into()); + return; + } + }), 0); + let flags = FUSEOpenFlags::from_bits_retain(flags); + match handler.read($handler_args)$await { + Ok(data_reply) => reply.data(&data_reply), + $warn_error + }; + }; + } + }, + )); + result.push(readdir_impl(handler_type, false)); + result.push(readdir_impl(handler_type, true)); + result.push(expand_macro_placeholders( + handler_type, + quote! { + fn readlink(&mut self, req: &Request, ino: u64, reply: ReplyData) { + $req + $handler + $resolver + $wrap { + $ino_id + match handler.readlink($handler_args)$await { + Ok(link) => reply.data(&link), + $warn_error + }; + }; + } + }, + )); + result.push(expand_macro_placeholders( + handler_type, + quote! { + fn release( + &mut self, + req: &Request, + ino: u64, + fh: u64, + flags: i32, + _lock_owner: Option, + _flush: bool, + reply: ReplyEmpty, + ) { + $req + $handler + $resolver + $wrap { + $ino_id + let fh = unsafe { OwnedFileHandle::from_raw(fh) }; + let flags = OpenFlags::from_bits_retain(flags); + match handler.release($handler_args)$await { + Ok(()) => reply.ok(), + $warn_error + }; + }; + } + }, + )); + result.push(expand_macro_placeholders(handler_type, quote! { + fn releasedir(&mut self, req: &Request, ino: u64, fh: u64, flags: i32, reply: ReplyEmpty) { + $req + $handler + $resolver + $wrap { + $ino_id + let fh = unsafe { OwnedFileHandle::from_raw(fh) }; + let flags = OpenFlags::from_bits_retain(flags); + match handler.releasedir($handler_args)$await { + Ok(()) => reply.ok(), + $warn_error + }; + }; + } + })); + result.push(expand_macro_placeholders( + handler_type, + quote! { + fn removexattr(&mut self, req: &Request, ino: u64, name: &OsStr, reply: ReplyEmpty) { + $req + $handler + $resolver + let name = name.to_owned(); + $wrap { + $ino_id + let name = name.as_ref(); + match handler.removexattr($handler_args)$await { + Ok(()) => reply.ok(), + $warn_error + }; + }; + } + }, + )); + result.push(expand_macro_placeholders( + handler_type, + quote! { + fn rename( + &mut self, + req: &Request, + parent: u64, + name: &OsStr, + newparent: u64, + newname: &OsStr, + flags: u32, + reply: ReplyEmpty, + ) { + $req + $handler + $resolver + let name = name.to_owned(); + let newname = newname.to_owned(); + $wrap { + let parent_id = resolver.resolve_id(parent); + let newparent_id = resolver.resolve_id(newparent); + let name = name.as_ref(); + let newname = newname.as_ref(); + let flags = RenameFlags::from_bits_retain(flags); + match handler.rename(&req, parent_id, name, newparent_id, newname, flags)$await { + Ok(()) => { + resolver.rename(parent, &name, newparent, &newname); + reply.ok() + } + $warn_error + } + }; + } + }, + )); + result.push(expand_macro_placeholders( + handler_type, + quote! { + fn rmdir(&mut self, req: &Request, parent: u64, name: &OsStr, reply: ReplyEmpty) { + $req + $handler + $resolver + let name = name.to_owned(); + $wrap { + $parent_id + let name = name.as_ref(); + match handler.rmdir($handler_args)$await { + Ok(()) => reply.ok(), + $warn_error + }; + }; + } + }, + )); + result.push(expand_macro_placeholders( + handler_type, + quote! { + fn setattr( + &mut self, + req: &Request, + ino: u64, + mode: Option, + uid: Option, + gid: Option, + size: Option, + atime: Option, + mtime: Option, + ctime: Option, + fh: Option, + crtime: Option, + chgtime: Option, + bkuptime: Option, + _flags: Option, + reply: ReplyAttr, + ) { + $req + $handler + $resolver + $wrap { + $ino_id + let attrs = SetAttrRequest { + mode, + uid, + gid, + size, + atime: atime, + mtime: mtime, + ctime: ctime, + crtime: crtime, + chgtime: chgtime, + bkuptime: bkuptime, + flags: None, + file_handle: fh.map(|fh| unsafe { BorrowedFileHandle::from_raw(fh) }), + }; + match handler.setattr(&req, ino_id, attrs)$await { + $reply_attr + $warn_error + } + } + } + }, + )); + result.push(expand_macro_placeholders( + handler_type, + quote! { + fn setlk( + &mut self, + req: &Request<'_>, + ino: u64, + fh: u64, + lock_owner: u64, + start: u64, + end: u64, + typ: i32, + pid: u32, + sleep: bool, + reply: ReplyEmpty, + ) { + $req + $handler + $resolver + $wrap { + $ino_id + $fh + let lock_info = LockInfo { + start, + end, + lock_type: LockType::from_bits_retain(typ), + pid, + }; + match handler.setlk( + &req, + ino_id, + fh, + lock_owner, + lock_info, + sleep, + )$await { + Ok(()) => reply.ok(), + $warn_error + }; + }; + } + }, + )); + result.push(expand_macro_placeholders( + handler_type, + quote! { + fn setxattr( + &mut self, + req: &Request, + ino: u64, + name: &OsStr, + value: &[u8], + flags: i32, + position: u32, + reply: ReplyEmpty, + ) { + $req + $handler + $resolver + let name = name.to_owned(); + let value = value.to_owned(); + $wrap { + $ino_id + let name = name.as_ref(); + let flags = FUSESetXAttrFlags::from_bits_retain(flags); + match handler.setxattr($handler_args)$await { + Ok(()) => reply.ok(), + $warn_error + }; + }; + } + }, + )); + result.push(expand_macro_placeholders( + handler_type, + quote! { + fn statfs(&mut self, req: &Request, ino: u64, reply: ReplyStatfs) { + $req + $handler + $resolver + $wrap { + $ino_id + match handler.statfs($handler_args)$await { + Ok(statfs) => reply.statfs( + statfs.total_blocks, + statfs.free_blocks, + statfs.available_blocks, + statfs.total_files, + statfs.free_files, + statfs.block_size, + statfs.max_filename_length, + statfs.fragment_size, + ), + $warn_error + }; + }; + } + }, + )); + result.push(expand_macro_placeholders( + handler_type, + quote! { + fn symlink( + &mut self, + req: &Request, + parent: u64, + link_name: &OsStr, + target: &Path, + reply: ReplyEntry, + ) { + $req + $handler + $resolver + let link_name = link_name.to_owned(); + let target = target.to_owned(); + $wrap { + $parent_id + let link_name = link_name.as_ref(); + let name = link_name; + let target = target.as_ref(); + match handler.symlink($handler_args)$await { + $reply_entry + $warn_error + } + }; + } + }, + )); + result.push(expand_macro_placeholders( + handler_type, + quote! { + fn write( + &mut self, + req: &Request, + ino: u64, + fh: u64, + offset: i64, + data: &[u8], + write_flags: u32, + flags: i32, + lock_owner: Option, + reply: ReplyWrite, + ) { + $req + $handler + $resolver + let data = data.to_owned(); + $wrap { + $ino_id + $fh + let offset = seek_from_raw(Some(match i32::try_from(offset) { + Ok(offset) => offset, + Err(_) => { + reply.error(ErrorKind::InputOutputError.into()); + return; + } + }), 0); + let write_flags = FUSEWriteFlags::from_bits_retain(write_flags); + let flags = OpenFlags::from_bits_retain(flags); + match handler.write($handler_args)$await { + Ok(bytes_written) => reply.written(bytes_written), + $warn_error + }; + }; + } + }, + )); + result.push(expand_macro_placeholders( + handler_type, + quote! { + fn unlink(&mut self, req: &Request, parent: u64, name: &OsStr, reply: ReplyEmpty) { + $req + $handler + $resolver + let name = name.to_owned(); + $wrap { + $parent_id + let name = name.as_ref(); + match handler.unlink($handler_args)$await { + Ok(()) => reply.ok(), + $warn_error + }; + }; + } + }, + )); + result +} + +fn generate_fuse_driver_struct(handler_type: HandlerType) -> TokenStream { + match handler_type { + HandlerType::Serial => quote! { + use std::cell::RefCell; + + pub(crate) struct FuseDriver + where + TId: FileIdType, + THandler: FuseHandler, + { + handler: THandler, + resolver: TId::Resolver, + dirmap_entries: RefCell>, + dirmapplus_entries: RefCell>, + } + + impl FuseDriver + where + TId: FileIdType, + THandler: FuseHandler, + { + /// num_thread is ignored in serial mode, it is kept for consistency with other modes + pub fn new(handler: THandler, _num_threads: usize) -> FuseDriver { + FuseDriver { + handler, + resolver: TId::Resolver::new(), + dirmap_entries: RefCell::new(HashMap::new()), + dirmapplus_entries: RefCell::new(HashMap::new()), + } + } + } + }, + HandlerType::Parallel => quote! { + use std::sync::{Arc, Mutex}; + use threadpool::ThreadPool; + + pub(crate) struct FuseDriver + where + TId: FileIdType, + THandler: FuseHandler, + { + handler: Arc, + resolver: Arc, + dirmap_entries: Arc>>, + dirmapplus_entries: Arc>>, + pub threadpool: ThreadPool, + } + + impl FuseDriver + where + TId: FileIdType, + THandler: FuseHandler, + { + pub fn new(handler: THandler, num_threads: usize) -> FuseDriver { + FuseDriver { + handler: Arc::new(handler), + resolver: Arc::new(TId::create_resolver()), + dirmap_entries: Arc::new(Mutex::new(HashMap::new())), + dirmapplus_entries: Arc::new(Mutex::new(HashMap::new())), + threadpool: ThreadPool::new(num_threads), + } + } + } + }, + HandlerType::Async => quote! { + use std::sync::{Arc, Mutex}; + use tokio::runtime::Runtime; + + pub(crate) struct FuseDriver + where + TId: FileIdType, + THandler: FuseHandler, + { + handler: Arc, + resolver: Arc, + dirmap_entries: Arc>>, + dirmapplus_entries: Arc>>, + pub runtime: Runtime, + } + + impl FuseDriver + where + TId: FileIdType, + THandler: FuseHandler, + { + pub fn new(handler: THandler, _num_threads: usize) -> FuseDriver { + FuseDriver { + handler: Arc::new(handler), + resolver: Arc::new(TId::create_resolver()), + dirmap_entries: Arc::new(Mutex::new(HashMap::new())), + dirmapplus_entries: Arc::new(Mutex::new(HashMap::new())), + runtime: Runtime::new().unwrap(), + } + } + } + }, + } +} + +fn get_dependencies() -> proc_macro2::TokenStream { + quote! { + use std::{ + collections::{HashMap, VecDeque}, + ffi::{OsStr, OsString}, + path::Path, + time::{Instant, SystemTime}, + }; + + use libc::c_int; + use log::{error, info, warn}; + + use fuser::{ + self, Filesystem, KernelConfig, ReplyAttr, ReplyBmap, ReplyCreate, ReplyData, ReplyDirectory, + ReplyDirectoryPlus, ReplyEmpty, ReplyEntry, ReplyIoctl, ReplyLock, ReplyLseek, ReplyOpen, + ReplyStatfs, ReplyWrite, ReplyXattr, Request, TimeOrNow, + }; + } +} + +pub(crate) fn generate_fuse_driver_implementation(handler_type: HandlerType) -> TokenStream { + let dependencies = get_dependencies(); + let fuse_driver_struct = generate_fuse_driver_struct(handler_type); + let fn_impls = generate_fuse_operation_handlers(handler_type); + quote! { + #dependencies + + type DirMapEntries = HashMap<(u64, i64), VecDeque<(OsString, u64, TAttr)>>; + + fn get_random_generation() -> u64 { + Instant::now().elapsed().as_nanos() as u64 + } + + #fuse_driver_struct + + impl fuser::Filesystem for FuseDriver + where + TId: FileIdType, + THandler: FuseHandler, + { + #(#fn_impls)* + } + } +} diff --git a/easy_fuser_macro/src/fuse_handler.rs b/easy_fuser_macro/src/fuse_handler.rs new file mode 100644 index 0000000..51515ca --- /dev/null +++ b/easy_fuser_macro/src/fuse_handler.rs @@ -0,0 +1,559 @@ +use quote::quote; +use syn::{parse_quote, TraitItemFn}; + +use crate::handler_type::HandlerType; + +fn get_function_defs() -> Vec { + let mut result = Vec::new(); + result.push(parse_quote! { + /// Check file access permissions + /// + /// This method is called for the access() system call. If the 'default_permissions' + /// mount option is given, this method is not called. This method is not called + /// under Linux kernel versions 2.4.x + fn access(&self, req: &RequestInfo, file_id: TId, mask: AccessMask) -> FuseResult<()>; + }); + result.push(parse_quote! { + /// Map block index within file to block index within device + /// + /// Note: This makes sense only for block device backed filesystems mounted + /// with the 'blkdev' option + fn bmap( + &self, + req: &RequestInfo, + file_id: TId, + blocksize: u32, + idx: u64, + ) -> FuseResult; + }); + result.push(parse_quote! { + /// Copy the specified range from the source inode to the destination inode + fn copy_file_range( + &self, + req: &RequestInfo, + file_in: TId, + file_handle_in: BorrowedFileHandle<'_>, + offset_in: i64, + file_out: TId, + file_handle_out: BorrowedFileHandle<'_>, + offset_out: i64, + len: u64, + flags: u32, // Not implemented yet in standard + ) -> FuseResult; + }); + result.push(parse_quote! { + /// Create and open a file + /// + /// If the file does not exist, first create it with the specified mode, and then + /// open it. Open flags (with the exception of O_NOCTTY) are available in flags. + /// If this method is not implemented or under Linux kernel versions earlier than + /// 2.6.15, the mknod() and open() methods will be called instead. + fn create( + &self, + req: &RequestInfo, + parent_id: TId, + name: &OsStr, + mode: u32, + umask: u32, + flags: OpenFlags, + ) -> FuseResult<(OwnedFileHandle, TId::Metadata, FUSEOpenResponseFlags)>; + }); + result.push(parse_quote! { + /// Preallocate or deallocate space to a file + fn fallocate( + &self, + req: &RequestInfo, + file_id: TId, + file_handle: BorrowedFileHandle<'_>, + offset: i64, + length: i64, + mode: FallocateFlags, + ) -> FuseResult<()>; + }); + result.push(parse_quote! { + /// Flush cached data for an open file + /// + /// Called on each close() of the opened file. Not guaranteed to be called after writes or at all. + /// Used for returning write errors or removing file locks. + fn flush( + &self, + req: &RequestInfo, + file_id: TId, + file_handle: BorrowedFileHandle<'_>, + lock_owner: u64, + ) -> FuseResult<()>; + }); + result.push(parse_quote! { + /// Release references to an inode, if the nlookup count reaches zero (to substract from the number of lookups). + fn forget(&self, req: &RequestInfo, file_id: TId, nlookup: u64); + }); + result.push(parse_quote! { + /// Synchronize file contents + /// + /// If datasync is true, only flush user data, not metadata. + fn fsync( + &self, + req: &RequestInfo, + file_id: TId, + file_handle: BorrowedFileHandle<'_>, + datasync: bool, + ) -> FuseResult<()>; + }); + result.push(parse_quote! { + /// Synchronize directory contents + /// + /// If the datasync parameter is true, then only the directory contents should + /// be flushed, not the metadata. The file_handle will contain the value set + /// by the opendir method, or will be undefined if the opendir method didn't + /// set any value. + fn fsyncdir( + &self, + req: &RequestInfo, + file_id: TId, + file_handle: BorrowedFileHandle<'_>, + datasync: bool, + ) -> FuseResult<()>; + }); + result.push(parse_quote! { + /// Modify file attributes + fn getattr( + &self, + req: &RequestInfo, + file_id: TId, + file_handle: Option>, + ) -> FuseResult; + }); + result.push(parse_quote! { + /// Test for a POSIX file lock. + fn getlk( + &self, + req: &RequestInfo, + file_id: TId, + file_handle: BorrowedFileHandle<'_>, + lock_owner: u64, + lock_info: LockInfo, + ) -> FuseResult; + }); + result.push(parse_quote! { + /// Get an extended attribute + fn getxattr( + &self, + req: &RequestInfo, + file_id: TId, + name: &OsStr, + size: u32, + ) -> FuseResult>; + }); + result.push(parse_quote! { + /// control device + fn ioctl( + &self, + req: &RequestInfo, + file_id: TId, + file_handle: BorrowedFileHandle<'_>, + flags: IOCtlFlags, + cmd: u32, + in_data: Vec, + out_size: u32, + ) -> FuseResult<(i32, Vec)>; + }); + result.push(parse_quote! { + /// Create a hard link. + fn link( + &self, + req: &RequestInfo, + file_id: TId, + newparent: TId, + newname: &OsStr, + ) -> FuseResult; + }); + result.push(parse_quote! { + /// List extended attribute names + fn listxattr(&self, req: &RequestInfo, file_id: TId, size: u32) -> FuseResult>; + }); + result.push(parse_quote! { + /// Retrieve file attributes for a directory entry by name and increment the lookup count associated with the inode. + fn lookup( + &self, + req: &RequestInfo, + parent_id: TId, + name: &OsStr, + ) -> FuseResult; + }); + result.push(parse_quote! { + /// Reposition read/write file offset + fn lseek( + &self, + req: &RequestInfo, + file_id: TId, + file_handle: BorrowedFileHandle<'_>, + seek: SeekFrom, + ) -> FuseResult; + }); + result.push(parse_quote! { + /// Create a new directory + fn mkdir( + &self, + req: &RequestInfo, + parent_id: TId, + name: &OsStr, + mode: u32, + umask: u32, + ) -> FuseResult; + }); + result.push(parse_quote! { + /// Create a new file node (regular file, device, FIFO, socket, etc) + fn mknod( + &self, + req: &RequestInfo, + parent_id: TId, + name: &OsStr, + mode: u32, + umask: u32, + rdev: DeviceType, + ) -> FuseResult; + }); + result.push(parse_quote! { + /// Open a file and return a file handle. + /// + /// Open flags (with the exception of O_CREAT, O_EXCL, O_NOCTTY and O_TRUNC) are available in flags. You may store an arbitrary file handle (pointer, index, etc) in file_handle response, and use this in other all other file operations (read, write, flush, release, fsync). Filesystem may also implement stateless file I/O and not store anything in fh. There are also some flags (direct_io, keep_cache) which the filesystem may set, to change the way the file is opened. See fuse_file_info structure in for more details. + fn open( + &self, + req: &RequestInfo, + file_id: TId, + flags: OpenFlags, + ) -> FuseResult<(OwnedFileHandle, FUSEOpenResponseFlags)>; + }); + result.push(parse_quote! { + /// Open a directory + /// + /// Allows storing a file handle for use in subsequent directory operations. + fn opendir( + &self, + req: &RequestInfo, + file_id: TId, + flags: OpenFlags, + ) -> FuseResult<(OwnedFileHandle, FUSEOpenResponseFlags)>; + }); + result.push(parse_quote! { + /// Read data from a file + /// + /// Read should send exactly the number of bytes requested except on EOF or error, otherwise the rest of the data will be substituted with zeroes. An exception to this is when the file has been opened in ‘direct_io’ mode, in which case the return value of the read system call will reflect the return value of this operation. fh will contain the value set by the open method, or will be undefined if the open method didn’t set any value. + /// + /// flags: these are the file flags, such as O_SYNC. Only supported with ABI >= 7.9 lock_owner: only supported with ABI >= 7.9 + fn read( + &self, + req: &RequestInfo, + file_id: TId, + file_handle: BorrowedFileHandle<'_>, + seek: SeekFrom, + size: u32, + flags: FUSEOpenFlags, + lock_owner: Option, + ) -> FuseResult>; + }); + result.push(parse_quote! { + /// Read directory contents + /// + /// Returns a list of directory entries with minimal metadata. + /// + /// Important: The returned file names (OsString) must not contain any slashes ('/'). + /// Including slashes in the file names will result in undefined behavior. + fn readdir( + &self, + req: &RequestInfo, + file_id: TId, + file_handle: BorrowedFileHandle<'_>, + ) -> FuseResult>; + }); + result.push(parse_quote! { + + /// Read the target of a symbolic link + fn readlink(&self, req: &RequestInfo, file_id: TId) -> FuseResult>; + }); + result.push(parse_quote! { + /// Release an open file + /// + /// Called when all file descriptors are closed and all memory mappings are unmapped. + /// Guaranteed to be called once for every open() call. + fn release( + &self, + req: &RequestInfo, + file_id: TId, + file_handle: OwnedFileHandle, + flags: OpenFlags, + lock_owner: Option, + flush: bool, + ) -> FuseResult<()>; + }); + result.push(parse_quote! { + /// Release an open directory + /// + /// This method is called exactly once for every successful opendir operation. + /// The file_handle parameter will contain the value set by the opendir method, + /// or will be undefined if the opendir method didn't set any value. + fn releasedir( + &self, + req: &RequestInfo, + file_id: TId, + file_handle: OwnedFileHandle, + flags: OpenFlags, + ) -> FuseResult<()>; + }); + result.push(parse_quote! { + /// Remove an extended attribute. + fn removexattr(&self, req: &RequestInfo, file_id: TId, name: &OsStr) -> FuseResult<()>; + }); + result.push(parse_quote! { + /// Rename a file or directory + fn rename( + &self, + req: &RequestInfo, + parent_id: TId, + name: &OsStr, + newparent: TId, + newname: &OsStr, + flags: RenameFlags, + ) -> FuseResult<()>; + }); + result.push(parse_quote! { + /// Remove a directory + fn rmdir(&self, req: &RequestInfo, parent_id: TId, name: &OsStr) -> FuseResult<()>; + }); + result.push(parse_quote! { + /// Set file attributes. + fn setattr( + &self, + req: &RequestInfo, + file_id: TId, + attrs: SetAttrRequest<'_>, + ) -> FuseResult; + }); + result.push(parse_quote! { + /// Acquire, modify or release a POSIX file lock + /// + /// For POSIX threads (NPTL) there's a 1-1 relation between pid and owner, but + /// otherwise this is not always the case. For checking lock ownership, 'fi->owner' + /// must be used. The l_pid field in 'struct flock' should only be used to fill + /// in this field in getlk(). + fn setlk( + &self, + req: &RequestInfo, + file_id: TId, + file_handle: BorrowedFileHandle<'_>, + lock_owner: u64, + lock_info: LockInfo, + sleep: bool, + ) -> FuseResult<()>; + }); + result.push(parse_quote! { + /// Set an extended attribute + fn setxattr( + &self, + req: &RequestInfo, + file_id: TId, + name: &OsStr, + value: Vec, + flags: FUSESetXAttrFlags, + position: u32, + ) -> FuseResult<()>; + }); + result.push(parse_quote! { + /// Get file system statistics + fn statfs(&self, req: &RequestInfo, file_id: TId) -> FuseResult; + }); + result.push(parse_quote! { + /// Create a symbolic link. + fn symlink( + &self, + req: &RequestInfo, + parent_id: TId, + link_name: &OsStr, + target: &Path, + ) -> FuseResult; + }); + result.push(parse_quote! { + /// Write data to a file + /// + /// Write should return exactly the number of bytes requested except on error. An exception to this is when the file has been opened in ‘direct_io’ mode, in which case the return value of the write system call will reflect the return value of this operation. fh will contain the value set by the open method, or will be undefined if the open method didn’t set any value. + /// + /// write_flags: will contain FUSE_WRITE_CACHE, if this write is from the page cache. If set, the pid, uid, gid, and fh may not match the value that would have been sent if write cachin is disabled flags: these are the file flags, such as O_SYNC. Only supported with ABI >= 7.9 lock_owner: only supported with ABI >= 7.9 + fn write( + &self, + req: &RequestInfo, + file_id: TId, + file_handle: BorrowedFileHandle<'_>, + seek: SeekFrom, + data: Vec, + write_flags: FUSEWriteFlags, + flags: OpenFlags, + lock_owner: Option, + ) -> FuseResult; + }); + result.push(parse_quote! { + /// Remove a file + fn unlink(&self, req: &RequestInfo, parent_id: TId, name: &OsStr) -> FuseResult<()>; + }); + result +} + +fn generate_readdir_plus(handler_type: HandlerType) -> proc_macro2::TokenStream { + match handler_type { + HandlerType::Async => quote! { + /// Read directory contents with full file attributes + /// + /// Default implementation combines readdir and lookup operations. + /// + /// Important: The returned file names (OsString) must not contain any slashes ('/'). + /// Including slashes in the file names will result in undefined behavior. + async fn readdirplus( + &self, + req: &RequestInfo, + file_id: TId, + file_handle: BorrowedFileHandle<'_>, + ) -> FuseResult> { + let readdir_result = self.readdir(req, file_id.clone(), file_handle).await?; + let mut result = Vec::with_capacity(readdir_result.len()); + for (name, _) in readdir_result.into_iter() { + let metadata = self.lookup(req, file_id.clone(), &name).await?; + result.push((name, metadata)); + } + Ok(result) + } + }, + _ => quote! { + /// Read directory contents with full file attributes + /// + /// Default implementation combines readdir and lookup operations. + /// + /// Important: The returned file names (OsString) must not contain any slashes ('/'). + /// Including slashes in the file names will result in undefined behavior. + fn readdirplus( + &self, + req: &RequestInfo, + file_id: TId, + file_handle: BorrowedFileHandle<'_>, + ) -> FuseResult> { + let readdir_result = self.readdir(req, file_id.clone(), file_handle)?; + let mut result = Vec::with_capacity(readdir_result.len()); + for (name, _) in readdir_result.into_iter() { + let metadata = self.lookup(req, file_id.clone(), &name)?; + result.push((name, metadata)); + } + Ok(result) + } + }, + } +} + +fn generate_function_impls(handler_type: HandlerType) -> Vec { + let function_defs = get_function_defs(); + + let function_impls = function_defs.into_iter().map(|func| { + let func_name = &func.sig.ident; + let args = &func.sig.inputs; + let return_type = &func.sig.output; + let attrs = &func.attrs; + + // Extract argument names without types and self + let arg_names = args.iter().filter_map(|arg| { + if let syn::FnArg::Typed(pat_type) = arg { + if let syn::Pat::Ident(pat_ident) = &*pat_type.pat { + Some(pat_ident.ident.clone()) + } else { + None + } + } else { + None + } + }); + + match handler_type { + HandlerType::Async => quote! { + #(#attrs)* + async fn #func_name(#args) #return_type { + self.get_inner().#func_name(#(#arg_names),*).await + } + }, + _ => quote! { + #(#attrs)* + fn #func_name(#args) #return_type { + self.get_inner().#func_name(#(#arg_names),*) + } + }, + } + }); + + function_impls.collect() +} + +fn get_common_functions() -> proc_macro2::TokenStream { + quote! { + /// Delegate unprovided methods to another FuseHandler, enabling composition + fn get_inner(&self) -> &dyn FuseHandler; + + /// Provide a default Time-To-Live for file metadata + /// + /// Can be overriden for each FileAttributes returned. + fn get_default_ttl(&self) -> Duration { + Duration::from_secs(1) + } + + /// Initialize the filesystem and configure kernel connection + fn init(&self, req: &RequestInfo, config: &mut KernelConfig) -> FuseResult<()> { + self.get_inner().init(req, config) + } + + /// Perform cleanup operations on filesystem exit + fn destroy(&self) { + self.get_inner().destroy(); + } + } +} + +fn get_dependencies() -> proc_macro2::TokenStream { + quote! { + use std::ffi::{OsStr, OsString}; + use std::path::Path; + use std::time::Duration; + } +} + +pub(crate) fn generate_fuse_handler_trait(handler_type: HandlerType) -> proc_macro2::TokenStream { + let dependencies = get_dependencies(); + let common_functions = get_common_functions(); + let function_impls = generate_function_impls(handler_type); + let readdirplus_fn = generate_readdir_plus(handler_type); + + match handler_type { + HandlerType::Async => quote! { + use async_trait::async_trait; + #dependencies + + #[async_trait] + pub trait FuseHandler: 'static + Send + Sync { + #common_functions + #(#function_impls)* + #readdirplus_fn + } + }, + HandlerType::Parallel => quote! { + #dependencies + + pub trait FuseHandler: 'static + Send + Sync { + #common_functions + #(#function_impls)* + #readdirplus_fn + } + }, + HandlerType::Serial => quote! { + #dependencies + + pub trait FuseHandler: 'static { + #common_functions + #(#function_impls)* + #readdirplus_fn + } + }, + } +} diff --git a/easy_fuser_macro/src/handler_type.rs b/easy_fuser_macro/src/handler_type.rs new file mode 100644 index 0000000..e2881ef --- /dev/null +++ b/easy_fuser_macro/src/handler_type.rs @@ -0,0 +1,19 @@ +extern crate proc_macro; + +use syn::LitStr; + +#[derive(Clone, Copy)] +pub(crate) enum HandlerType { + Async, + Serial, + Parallel, +} + +pub(crate) fn parse_handler_type(input: LitStr) -> HandlerType { + match input.value().as_str() { + "async" => HandlerType::Async, + "serial" => HandlerType::Serial, + "parallel" => HandlerType::Parallel, + _ => panic!("Invalid handler type. Use 'async', 'serial', or 'parallel'"), + } +} diff --git a/easy_fuser_macro/src/lib.rs b/easy_fuser_macro/src/lib.rs new file mode 100644 index 0000000..90eb04f --- /dev/null +++ b/easy_fuser_macro/src/lib.rs @@ -0,0 +1,32 @@ +extern crate proc_macro; + +use proc_macro::TokenStream; +use syn::{parse_macro_input, LitStr}; + +mod handler_type; +use handler_type::parse_handler_type; + +mod fuse_driver; +use fuse_driver::generate_fuse_driver_implementation; +mod fuse_handler; +use fuse_handler::generate_fuse_handler_trait; + +#[proc_macro] +pub fn implement_fuse_handler(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as LitStr); + let handler_type = parse_handler_type(input); + + let trait_impl = generate_fuse_handler_trait(handler_type); + + trait_impl.into() +} + +#[proc_macro] +pub fn implement_fuse_driver(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as LitStr); + let handler_type = parse_handler_type(input); + + let struct_impl = generate_fuse_driver_implementation(handler_type); + + struct_impl.into() +} diff --git a/src/core.rs b/src/core.rs deleted file mode 100644 index 52f3f1e..0000000 --- a/src/core.rs +++ /dev/null @@ -1,8 +0,0 @@ -mod fuse_driver; -mod fuse_driver_types; -mod inode_mapping; -mod macros; -mod thread_mode; - -pub(crate) use fuse_driver_types::FuseDriver; -pub(crate) use inode_mapping::{InodeResolvable, ROOT_INO}; diff --git a/src/core/fuse_driver.rs b/src/core/fuse_driver.rs deleted file mode 100644 index 24a8b18..0000000 --- a/src/core/fuse_driver.rs +++ /dev/null @@ -1,1021 +0,0 @@ -use std::{ - ffi::OsStr, - path::Path, - time::{Instant, SystemTime}, -}; - -use libc::c_int; -use log::{error, info, warn}; - -use fuser::{ - self, KernelConfig, ReplyAttr, ReplyBmap, ReplyCreate, ReplyData, ReplyDirectory, - ReplyDirectoryPlus, ReplyEmpty, ReplyEntry, ReplyIoctl, ReplyLock, ReplyLseek, ReplyOpen, - ReplyStatfs, ReplyWrite, ReplyXattr, Request, TimeOrNow, -}; - -use super::{ - fuse_driver_types::{execute_task, FuseDriver}, - inode_mapping::FileIdResolver, - macros::*, - thread_mode::*, -}; -use crate::{fuse_handler::FuseHandler, types::*}; - -fn get_random_generation() -> u64 { - Instant::now().elapsed().as_nanos() as u64 -} - -impl fuser::Filesystem for FuseDriver -where - TId: FileIdType, - THandler: FuseHandler, -{ - fn init(&mut self, req: &Request, config: &mut KernelConfig) -> Result<(), c_int> { - let req = RequestInfo::from(req); - match self.get_handler().init(&req, config) { - Ok(()) => Ok(()), - Err(e) => { - warn!("[{}] init {:?}", e, req); - Err(e.raw_error()) - } - } - } - - fn destroy(&mut self) { - self.get_handler().destroy(); - } - - fn access(&mut self, req: &Request, ino: u64, mask: i32, reply: ReplyEmpty) { - let req = RequestInfo::from(req); - let handler = self.get_handler(); - let resolver = self.get_resolver(); - execute_task!(self, { - match handler.access( - &req, - resolver.resolve_id(ino), - AccessMask::from_bits_retain(mask), - ) { - Ok(()) => reply.ok(), - Err(e) => { - warn!("access: ino {:x?}, [{}], {:?}", ino, e, req); - reply.error(e.raw_error()) - } - }; - }); - } - - fn bmap(&mut self, req: &Request<'_>, ino: u64, blocksize: u32, idx: u64, reply: ReplyBmap) { - let req = RequestInfo::from(req); - let handler = self.get_handler(); - let resolver = self.get_resolver(); - execute_task!(self, { - match handler.bmap(&req, resolver.resolve_id(ino), blocksize, idx) { - Ok(block) => reply.bmap(block), - Err(e) => { - warn!("bmap: ino {:x?}, [{}], {:?}", ino, e, req); - reply.error(e.raw_error()) - } - }; - }); - } - - fn copy_file_range( - &mut self, - req: &Request, - ino_in: u64, - fh_in: u64, - offset_in: i64, - ino_out: u64, - fh_out: u64, - offset_out: i64, - len: u64, - flags: u32, - reply: ReplyWrite, - ) { - let req = RequestInfo::from(req); - let handler = self.get_handler(); - let resolver = self.get_resolver(); - execute_task!(self, { - match handler.copy_file_range( - &req, - resolver.resolve_id(ino_in), - unsafe { BorrowedFileHandle::from_raw(fh_in) }, - offset_in, - resolver.resolve_id(ino_out), - unsafe { BorrowedFileHandle::from_raw(fh_out) }, - offset_out, - len, - flags, - ) { - Ok(bytes_written) => reply.written(bytes_written), - Err(e) => { - warn!("copy_file_range: ino {:x?}, [{}], {:?}", ino_in, e, req); - reply.error(e.raw_error()) - } - }; - }); - } - - fn create( - &mut self, - req: &Request, - parent: u64, - name: &OsStr, - mode: u32, - umask: u32, - flags: i32, - reply: ReplyCreate, - ) { - let req = RequestInfo::from(req); - let handler = self.get_handler(); - let resolver = self.get_resolver(); - let name = name.to_owned(); - execute_task!(self, { - match handler.create( - &req, - resolver.resolve_id(parent), - &name, - mode, - umask, - OpenFlags::from_bits_retain(flags), - ) { - Ok((file_handle, metadata, response_flags)) => { - let default_ttl = handler.get_default_ttl(); - let (id, file_attr) = TId::extract_metadata(metadata); - let ino = resolver.lookup(parent, &name, id, true); - let (fuse_attr, ttl, generation) = file_attr.to_fuse(ino); - reply.created( - &ttl.unwrap_or(default_ttl), - &fuse_attr, - generation.unwrap_or(get_random_generation()), - file_handle.as_raw(), - response_flags.bits(), - ); - } - Err(e) => { - warn!("create: {:?}, parent_ino: {:x?}, {:?}", parent, e, req); - reply.error(e.raw_error()) - } - }; - }); - } - - fn fallocate( - &mut self, - req: &Request, - ino: u64, - fh: u64, - offset: i64, - length: i64, - mode: i32, - reply: ReplyEmpty, - ) { - let req = RequestInfo::from(req); - let handler = self.get_handler(); - let resolver = self.get_resolver(); - execute_task!(self, { - match handler.fallocate( - &req, - resolver.resolve_id(ino), - unsafe { BorrowedFileHandle::from_raw(fh) }, - offset, - length, - FallocateFlags::from_bits_retain(mode), - ) { - Ok(()) => reply.ok(), - Err(e) => { - warn!("fallocate: ino {:x?}, [{}], {:?}", ino, e, req); - reply.error(e.raw_error()) - } - }; - }); - } - - fn flush(&mut self, req: &Request, ino: u64, fh: u64, lock_owner: u64, reply: ReplyEmpty) { - let req = RequestInfo::from(req); - let handler = self.get_handler(); - let resolver = self.get_resolver(); - execute_task!(self, { - match handler.flush( - &req, - resolver.resolve_id(ino), - unsafe { BorrowedFileHandle::from_raw(fh) }, - lock_owner, - ) { - Ok(()) => reply.ok(), - Err(e) => { - warn!("flush: ino {:x?}, [{}], {:?}", ino, e, req); - reply.error(e.raw_error()) - } - }; - }); - } - - fn forget(&mut self, req: &Request, ino: u64, nlookup: u64) { - let req = RequestInfo::from(req); - let handler = self.get_handler(); - let resolver = self.get_resolver(); - handler.forget(&req, resolver.resolve_id(ino), nlookup); - resolver.forget(ino, nlookup); - } - - fn fsync(&mut self, req: &Request, ino: u64, fh: u64, datasync: bool, reply: ReplyEmpty) { - let req = RequestInfo::from(req); - let handler = self.get_handler(); - let resolver = self.get_resolver(); - execute_task!(self, { - match handler.fsync( - &req, - resolver.resolve_id(ino), - unsafe { BorrowedFileHandle::from_raw(fh) }, - datasync, - ) { - Ok(()) => reply.ok(), - Err(e) => { - warn!("fsync: ino {:x?}, [{}], {:?}", ino, e, req); - reply.error(e.raw_error()) - } - }; - }); - } - - fn fsyncdir(&mut self, req: &Request, ino: u64, fh: u64, datasync: bool, reply: ReplyEmpty) { - let req = RequestInfo::from(req); - let handler = self.get_handler(); - let resolver = self.get_resolver(); - execute_task!(self, { - match handler.fsyncdir( - &req, - resolver.resolve_id(ino), - unsafe { BorrowedFileHandle::from_raw(fh) }, - datasync, - ) { - Ok(()) => reply.ok(), - Err(e) => { - warn!("fsyncdir: ino {:x?}, [{}], {:?}", ino, e, req); - reply.error(e.raw_error()) - } - }; - }); - } - - fn getattr(&mut self, req: &Request, ino: u64, fh: Option, reply: ReplyAttr) { - let req = RequestInfo::from(req); - let handler = self.get_handler(); - let resolver = self.get_resolver(); - execute_task!(self, { - handle_fuse_reply_attr!( - handler, - resolver, - &req, - ino, - reply, - getattr, - ( - &req, - resolver.resolve_id(ino), - fh.map(|fh| unsafe { BorrowedFileHandle::from_raw(fh) }) - ) - ); - }); - } - - fn getlk( - &mut self, - req: &Request<'_>, - ino: u64, - fh: u64, - lock_owner: u64, - start: u64, - end: u64, - typ: i32, - pid: u32, - reply: ReplyLock, - ) { - let req = RequestInfo::from(req); - let handler = self.get_handler(); - let resolver = self.get_resolver(); - execute_task!(self, { - let lock_info = LockInfo { - start, - end, - lock_type: LockType::from_bits_retain(typ), - pid, - }; - match handler.getlk( - &req, - resolver.resolve_id(ino), - unsafe { BorrowedFileHandle::from_raw(fh) }, - lock_owner, - lock_info, - ) { - Ok(lock_info) => reply.locked( - lock_info.start, - lock_info.end, - lock_info.lock_type.bits(), - lock_info.pid, - ), - Err(e) => { - warn!("getlk: ino {:x?}, [{}], {:?}", ino, e, req); - reply.error(e.raw_error()) - } - }; - }); - } - - fn getxattr(&mut self, req: &Request, ino: u64, name: &OsStr, size: u32, reply: ReplyXattr) { - let req = RequestInfo::from(req); - let handler = self.get_handler(); - let resolver = self.get_resolver(); - let name = name.to_owned(); - execute_task!(self, { - match handler.getxattr(&req, resolver.resolve_id(ino), &name, size) { - Ok(xattr_data) => { - if size == 0 { - reply.size(xattr_data.len() as u32); - } else if size >= xattr_data.len() as u32 { - reply.data(&xattr_data); - } else { - reply.error(ErrorKind::ResultTooLarge.into()); - } - } - Err(e) => { - warn!("getxattr: ino {:x?}, [{}], {:?}", ino, e, req); - reply.error(e.raw_error()) - } - }; - }); - } - - fn ioctl( - &mut self, - req: &Request<'_>, - ino: u64, - fh: u64, - flags: u32, - cmd: u32, - in_data: &[u8], - out_size: u32, - reply: ReplyIoctl, - ) { - let req = RequestInfo::from(req); - let handler = self.get_handler(); - let resolver = self.get_resolver(); - let in_data = in_data.to_owned(); - execute_task!(self, { - match handler.ioctl( - &req, - resolver.resolve_id(ino), - unsafe { BorrowedFileHandle::from_raw(fh) }, - IOCtlFlags::from_bits_retain(flags), - cmd, - in_data, - out_size, - ) { - Ok((result, data)) => reply.ioctl(result, &data), - Err(e) => { - warn!("ioctl: ino {:x?}, [{}], {:?}", ino, e, req); - reply.error(e.raw_error()) - } - }; - }); - } - - fn link( - &mut self, - req: &Request, - ino: u64, - newparent: u64, - newname: &OsStr, - reply: ReplyEntry, - ) { - let req = RequestInfo::from(req); - let handler = self.get_handler(); - let resolver = self.get_resolver(); - let newname = newname.to_owned(); - execute_task!(self, { - handle_fuse_reply_entry!( - handler, - resolver, - &req, - newparent, - &newname, - reply, - link, - ( - &req, - resolver.resolve_id(ino), - resolver.resolve_id(newparent), - &newname - ) - ); - }); - } - - fn listxattr(&mut self, req: &Request, ino: u64, size: u32, reply: ReplyXattr) { - let req = RequestInfo::from(req); - let handler = self.get_handler(); - let resolver = self.get_resolver(); - execute_task!(self, { - match handler.listxattr(&req, resolver.resolve_id(ino), size) { - Ok(xattr_data) => { - if size == 0 { - reply.size(xattr_data.len() as u32); - } else if size >= xattr_data.len() as u32 { - reply.data(&xattr_data); - } else { - reply.error(ErrorKind::ResultTooLarge.into()); - } - } - Err(e) => { - warn!("listxattr: ino {:x?}, [{}], {:?}", ino, e, req); - reply.error(e.raw_error()) - } - }; - }); - } - - fn lookup(&mut self, req: &Request, parent: u64, name: &OsStr, reply: ReplyEntry) { - let req = RequestInfo::from(req); - let handler = self.get_handler(); - let resolver = self.get_resolver(); - let name = name.to_owned(); - execute_task!(self, { - handle_fuse_reply_entry!( - handler, - resolver, - &req, - parent, - &name, - reply, - lookup, - (&req, resolver.resolve_id(parent), &name) - ); - }); - } - - fn lseek( - &mut self, - req: &Request, - ino: u64, - fh: u64, - offset: i64, - whence: i32, - reply: ReplyLseek, - ) { - let req = RequestInfo::from(req); - let handler = self.get_handler(); - let resolver = self.get_resolver(); - execute_task!(self, { - match handler.lseek( - &req, - resolver.resolve_id(ino), - unsafe { BorrowedFileHandle::from_raw(fh) }, - seek_from_raw(Some(whence), offset), - ) { - Ok(new_offset) => reply.offset(new_offset), - Err(e) => { - warn!("lseek: ino {:x?}, [{}], {:?}", ino, e, req); - reply.error(e.raw_error()) - } - }; - }); - } - - fn mkdir( - &mut self, - req: &Request, - parent: u64, - name: &OsStr, - mode: u32, - umask: u32, - reply: ReplyEntry, - ) { - let req = RequestInfo::from(req); - let handler = self.get_handler(); - let resolver = self.get_resolver(); - let name = name.to_owned(); - execute_task!(self, { - handle_fuse_reply_entry!( - handler, - resolver, - &req, - parent, - &name, - reply, - mkdir, - (&req, resolver.resolve_id(parent), &name, mode, umask) - ); - }); - } - - fn mknod( - &mut self, - req: &Request, - parent: u64, - name: &OsStr, - mode: u32, - umask: u32, - rdev: u32, - reply: ReplyEntry, - ) { - let req = RequestInfo::from(req); - let handler = self.get_handler(); - let resolver = self.get_resolver(); - let name = name.to_owned(); - execute_task!(self, { - handle_fuse_reply_entry!( - handler, - resolver, - &req, - parent, - &name, - reply, - mknod, - ( - &req, - resolver.resolve_id(parent), - &name, - mode, - umask, - DeviceType::from_rdev(rdev.try_into().unwrap()) - ) - ); - }); - } - - fn open(&mut self, req: &Request, ino: u64, _flags: i32, reply: ReplyOpen) { - let req = RequestInfo::from(req); - let handler = self.get_handler(); - let resolver = self.get_resolver(); - execute_task!(self, { - match handler.open( - &req, - resolver.resolve_id(ino), - OpenFlags::from_bits_retain(_flags), - ) { - Ok((file_handle, response_flags)) => { - reply.opened(file_handle.as_raw(), response_flags.bits()) - } - Err(e) => { - warn!("open: ino {:x?}, [{}], {:?}", ino, e, req); - reply.error(e.raw_error()) - } - }; - }); - } - - fn opendir(&mut self, req: &Request, ino: u64, _flags: i32, reply: ReplyOpen) { - let req = RequestInfo::from(req); - let handler = self.get_handler(); - let resolver = self.get_resolver(); - execute_task!(self, { - match handler.opendir( - &req, - resolver.resolve_id(ino), - OpenFlags::from_bits_retain(_flags), - ) { - Ok((file_handle, response_flags)) => { - reply.opened(file_handle.as_raw(), response_flags.bits()) - } - Err(e) => { - warn!("opendir: ino {:x?}, [{}], {:?}", ino, e, req); - reply.error(e.raw_error()) - } - }; - }); - } - - fn read( - &mut self, - req: &Request, - ino: u64, - fh: u64, - offset: i64, - size: u32, - flags: i32, - lock_owner: Option, - reply: ReplyData, - ) { - let req = RequestInfo::from(req); - let handler = self.get_handler(); - let resolver = self.get_resolver(); - execute_task!(self, { - match handler.read( - &req, - resolver.resolve_id(ino), - unsafe { BorrowedFileHandle::from_raw(fh) }, - seek_from_raw(None, offset), - size, - FUSEOpenFlags::from_bits_retain(flags), - lock_owner, - ) { - Ok(data_reply) => reply.data(&data_reply), - Err(e) => { - warn!("read: ino {:x?}, [{}], {:?}", ino, e, req); - reply.error(e.raw_error()) - } - }; - }); - } - - fn readdir( - &mut self, - req: &Request, - ino: u64, - fh: u64, - offset: i64, - mut reply: ReplyDirectory, - ) { - handle_dir_read!( - self, - req, - ino, - fh, - offset, - reply, - readdir, - get_dirmap_iter, - ReplyDirectory - ); - } - - fn readdirplus( - &mut self, - req: &Request, - ino: u64, - fh: u64, - offset: i64, - mut reply: ReplyDirectoryPlus, - ) { - handle_dir_read!( - self, - req, - ino, - fh, - offset, - reply, - readdirplus, - get_dirmapplus_iter, - ReplyDirectoryPlus - ); - } - - fn readlink(&mut self, req: &Request, ino: u64, reply: ReplyData) { - let req = RequestInfo::from(req); - let handler = self.get_handler(); - let resolver = self.get_resolver(); - execute_task!(self, { - match handler.readlink(&req, resolver.resolve_id(ino)) { - Ok(link) => reply.data(&link), - Err(e) => { - warn!("[{}] readlink, ino: {:x?}, {:?}", ino, e, req); - reply.error(e.raw_error()) - } - }; - }); - } - - fn release( - &mut self, - req: &Request, - ino: u64, - fh: u64, - _flags: i32, - _lock_owner: Option, - _flush: bool, - reply: ReplyEmpty, - ) { - let req = RequestInfo::from(req); - let handler = self.get_handler(); - let resolver = self.get_resolver(); - execute_task!(self, { - match handler.release( - &req, - resolver.resolve_id(ino), - unsafe { OwnedFileHandle::from_raw(fh) }, - OpenFlags::from_bits_retain(_flags), - _lock_owner, - _flush, - ) { - Ok(()) => reply.ok(), - Err(e) => { - warn!("release: ino {:x?}, [{}], {:?}", ino, e, req); - reply.error(e.raw_error()) - } - }; - }); - } - - fn releasedir(&mut self, req: &Request, ino: u64, fh: u64, flags: i32, reply: ReplyEmpty) { - let req = RequestInfo::from(req); - let handler = self.get_handler(); - let resolver = self.get_resolver(); - execute_task!(self, { - match handler.releasedir( - &req, - resolver.resolve_id(ino), - unsafe { OwnedFileHandle::from_raw(fh) }, - OpenFlags::from_bits_retain(flags), - ) { - Ok(()) => reply.ok(), - Err(e) => { - warn!("releasedir: ino {:x?}, [{}], {:?}", ino, e, req); - reply.error(e.raw_error()) - } - }; - }); - } - - fn removexattr(&mut self, req: &Request, ino: u64, name: &OsStr, reply: ReplyEmpty) { - let req = RequestInfo::from(req); - let handler = self.get_handler(); - let resolver = self.get_resolver(); - let name = name.to_owned(); - execute_task!(self, { - match handler.removexattr(&req, resolver.resolve_id(ino), &name) { - Ok(()) => reply.ok(), - Err(e) => { - warn!("removexattr: ino {:x?}, [{}], {:?}", ino, e, req); - reply.error(e.raw_error()) - } - }; - }); - } - - fn rename( - &mut self, - req: &Request, - parent: u64, - name: &OsStr, - newparent: u64, - newname: &OsStr, - flags: u32, - reply: ReplyEmpty, - ) { - let req = RequestInfo::from(req); - let handler = self.get_handler(); - let resolver = self.get_resolver(); - let name = name.to_owned(); - let newname = newname.to_owned(); - execute_task!(self, { - match handler.rename( - &req, - resolver.resolve_id(parent), - &name, - resolver.resolve_id(newparent), - &newname, - RenameFlags::from_bits_retain(flags), - ) { - Ok(()) => { - resolver.rename(parent, &name, newparent, &newname); - reply.ok() - } - Err(e) => { - warn!("[{}] rename: parent_ino: {:x?}, {:?}", parent, e, req); - reply.error(e.raw_error()) - } - } - }); - } - - fn rmdir(&mut self, req: &Request, parent: u64, name: &OsStr, reply: ReplyEmpty) { - let req = RequestInfo::from(req); - let handler = self.get_handler(); - let resolver = self.get_resolver(); - let name = name.to_owned(); - execute_task!(self, { - match handler.rmdir(&req, resolver.resolve_id(parent), &name) { - Ok(()) => reply.ok(), - Err(e) => { - warn!("[{}] rmdir: parent_ino: {:x?}, {:?}", parent, e, req); - reply.error(e.raw_error()) - } - }; - }); - } - - fn setattr( - &mut self, - req: &Request, - ino: u64, - mode: Option, - uid: Option, - gid: Option, - size: Option, - atime: Option, - mtime: Option, - ctime: Option, - fh: Option, - crtime: Option, - chgtime: Option, - bkuptime: Option, - _flags: Option, - reply: ReplyAttr, - ) { - let req = RequestInfo::from(req); - let handler = self.get_handler(); - let resolver = self.get_resolver(); - let attrs = SetAttrRequest { - mode, - uid, - gid, - size, - atime: atime, - mtime: mtime, - ctime: ctime, - crtime: crtime, - chgtime: chgtime, - bkuptime: bkuptime, - flags: None, - file_handle: fh.map(|fh| unsafe { BorrowedFileHandle::from_raw(fh) }), - }; - execute_task!(self, { - handle_fuse_reply_attr!( - handler, - resolver, - &req, - ino, - reply, - setattr, - (&req, resolver.resolve_id(ino), attrs) - ); - }); - } - - fn setlk( - &mut self, - req: &Request<'_>, - ino: u64, - fh: u64, - lock_owner: u64, - start: u64, - end: u64, - typ: i32, - pid: u32, - sleep: bool, - reply: ReplyEmpty, - ) { - let req = RequestInfo::from(req); - let handler = self.get_handler(); - let resolver = self.get_resolver(); - execute_task!(self, { - let lock_info = LockInfo { - start, - end, - lock_type: LockType::from_bits_retain(typ), - pid, - }; - match handler.setlk( - &req, - resolver.resolve_id(ino), - unsafe { BorrowedFileHandle::from_raw(fh) }, - lock_owner, - lock_info, - sleep, - ) { - Ok(()) => reply.ok(), - Err(e) => { - warn!("setlk: ino {:x?}, [{}], {:?}", ino, e, req); - reply.error(e.raw_error()) - } - }; - }); - } - - fn setxattr( - &mut self, - req: &Request, - ino: u64, - name: &OsStr, - value: &[u8], - flags: i32, - position: u32, - reply: ReplyEmpty, - ) { - let req = RequestInfo::from(req); - let handler = self.get_handler(); - let resolver = self.get_resolver(); - let name = name.to_owned(); - let value = value.to_owned(); - execute_task!(self, { - match handler.setxattr( - &req, - resolver.resolve_id(ino), - &name, - value, - FUSESetXAttrFlags::from_bits_retain(flags), - position, - ) { - Ok(()) => reply.ok(), - Err(e) => { - warn!("setxattr: ino {:x?}, [{}], {:?}", ino, e, req); - reply.error(e.raw_error()) - } - }; - }); - } - - fn statfs(&mut self, req: &Request, ino: u64, reply: ReplyStatfs) { - let req = RequestInfo::from(req); - let handler = self.get_handler(); - let resolver = self.get_resolver(); - execute_task!(self, { - match handler.statfs(&req, resolver.resolve_id(ino)) { - Ok(statfs) => reply.statfs( - statfs.total_blocks, - statfs.free_blocks, - statfs.available_blocks, - statfs.total_files, - statfs.free_files, - statfs.block_size, - statfs.max_filename_length, - statfs.fragment_size, - ), - Err(e) => { - warn!("statfs: ino {:x?}, [{}], {:?}", ino, e, req); - reply.error(e.raw_error()) - } - }; - }); - } - - fn symlink( - &mut self, - req: &Request, - parent: u64, - link_name: &OsStr, - target: &Path, - reply: ReplyEntry, - ) { - let req = RequestInfo::from(req); - let handler = self.get_handler(); - let resolver = self.get_resolver(); - let link_name = link_name.to_owned(); - let target = target.to_owned(); - execute_task!(self, { - handle_fuse_reply_entry!( - handler, - resolver, - &req, - parent, - &link_name, - reply, - symlink, - (&req, resolver.resolve_id(parent), &link_name, &target) - ); - }); - } - - fn write( - &mut self, - req: &Request, - ino: u64, - fh: u64, - offset: i64, - data: &[u8], - write_flags: u32, - flags: i32, - lock_owner: Option, - reply: ReplyWrite, - ) { - let req = RequestInfo::from(req); - let handler = self.get_handler(); - let resolver = self.get_resolver(); - let data = data.to_owned(); - execute_task!(self, { - match handler.write( - &req, - resolver.resolve_id(ino), - unsafe { BorrowedFileHandle::from_raw(fh) }, - seek_from_raw(None, offset), - data, - FUSEWriteFlags::from_bits_retain(write_flags), - OpenFlags::from_bits_retain(flags), - lock_owner, - ) { - Ok(bytes_written) => reply.written(bytes_written), - Err(e) => { - warn!("write: ino {:x?}, [{}], {:?}", ino, e, req); - reply.error(e.raw_error()) - } - }; - }); - } - - fn unlink(&mut self, req: &Request, parent: u64, name: &OsStr, reply: ReplyEmpty) { - let req = RequestInfo::from(req); - let handler = self.get_handler(); - let resolver = self.get_resolver(); - let name = name.to_owned(); - execute_task!(self, { - match handler.unlink(&req, resolver.resolve_id(parent), &name) { - Ok(()) => reply.ok(), - Err(e) => { - warn!("[{}] unlink: parent_ino: {:x?}, {:?}", parent, e, req); - reply.error(e.raw_error()) - } - }; - }); - } -} diff --git a/src/core/fuse_driver_types.rs b/src/core/fuse_driver_types.rs deleted file mode 100644 index d550d7e..0000000 --- a/src/core/fuse_driver_types.rs +++ /dev/null @@ -1,236 +0,0 @@ -#![allow(unused_imports)] - -use std::{ - collections::{HashMap, VecDeque}, - ffi::{OsStr, OsString}, -}; - -use super::inode_mapping::FileIdResolver; -use crate::fuse_handler::FuseHandler; -use crate::types::*; - -type DirIter = HashMap<(u64, i64), VecDeque<(OsString, u64, TAttr)>>; - -#[cfg(feature = "serial")] -mod serial { - use super::*; - - use std::cell::RefCell; - - pub(crate) struct FuseDriver - where - TId: FileIdType, - THandler: FuseHandler, - { - handler: THandler, - resolver: TId::Resolver, - dirmap_iter: RefCell>, - dirmapplus_iter: RefCell>, - } - - impl FuseDriver - where - TId: FileIdType, - THandler: FuseHandler, - { - /// num_thread is ignored in serial mode, it is kept for consistency with other modes - pub fn new(handler: THandler, _num_threads: usize) -> FuseDriver { - FuseDriver { - handler, - resolver: TId::Resolver::new(), - dirmap_iter: RefCell::new(HashMap::new()), - dirmapplus_iter: RefCell::new(HashMap::new()), - } - } - - pub fn get_handler(&self) -> &THandler { - &self.handler - } - - pub fn get_resolver(&self) -> &TId::Resolver { - &self.resolver - } - - pub fn get_dirmap_iter(&self) -> &RefCell> { - &self.dirmap_iter - } - - pub fn get_dirmapplus_iter(&self) -> &RefCell> { - &self.dirmapplus_iter - } - } - - macro_rules! execute_task { - ($self:expr, $block:block) => { - $block - }; - } - - pub(crate) use execute_task; -} - -#[cfg(feature = "parallel")] -mod parallel { - use super::*; - - use std::sync::Arc; - - use threadpool::ThreadPool; - - #[cfg(feature = "deadlock_detection")] - use parking_lot::{Mutex, MutexGuard}; - #[cfg(not(feature = "deadlock_detection"))] - use std::sync::{Mutex, MutexGuard}; - - pub(crate) struct FuseDriver - where - TId: FileIdType, - THandler: FuseHandler, - { - handler: Arc, - resolver: Arc, - dirmap_iter: Arc>>, - dirmapplus_iter: Arc>>, - pub threadpool: ThreadPool, - } - - impl FuseDriver - where - TId: FileIdType, - THandler: FuseHandler, - { - pub fn new(handler: THandler, num_threads: usize) -> FuseDriver { - #[cfg(feature = "deadlock_detection")] - spawn_deadlock_checker(); - FuseDriver { - handler: Arc::new(handler), - resolver: Arc::new(TId::create_resolver()), - dirmap_iter: Arc::new(Mutex::new(HashMap::new())), - dirmapplus_iter: Arc::new(Mutex::new(HashMap::new())), - threadpool: ThreadPool::new(num_threads), - } - } - - pub fn get_handler(&self) -> Arc { - self.handler.clone() - } - - pub fn get_resolver(&self) -> Arc { - self.resolver.clone() - } - - pub fn get_dirmap_iter(&self) -> Arc>> { - self.dirmap_iter.clone() - } - - pub fn get_dirmapplus_iter(&self) -> Arc>> { - self.dirmapplus_iter.clone() - } - } - - macro_rules! execute_task { - ($self:expr, $block:block) => { - $self.threadpool.execute(move || $block); - }; - } - - pub(crate) use execute_task; -} - -#[cfg(feature = "async")] -mod async_task { - use super::*; - - use std::sync::Arc; - use tokio::runtime::Runtime; - use tokio::sync::Mutex; - - pub(crate) struct FuseDriver - where - TId: FileIdType, - THandler: FuseHandler, - { - handler: Arc, - resolver: Arc, - dirmap_iter: Arc>>, - dirmapplus_iter: Arc>>, - pub runtime: Runtime, - } - - impl FuseDriver - where - TId: FileIdType, - THandler: FuseHandler, - { - pub fn new(handler: THandler, _num_threads: usize) -> FuseDriver { - #[cfg(feature = "deadlock_detection")] - spawn_deadlock_checker(); - FuseDriver { - handler: Arc::new(handler), - resolver: Arc::new(TId::create_resolver()), - dirmap_iter: Arc::new(Mutex::new(HashMap::new())), - dirmapplus_iter: Arc::new(Mutex::new(HashMap::new())), - runtime: Runtime::new().unwrap(), - } - } - - pub fn get_handler(&self) -> Arc { - self.handler.clone() - } - - pub fn get_resolver(&self) -> Arc { - self.resolver.clone() - } - - pub fn get_dirmap_iter(&self) -> Arc>> { - self.dirmap_iter.clone() - } - - pub fn get_dirmapplus_iter(&self) -> Arc>> { - self.dirmapplus_iter.clone() - } - } - - macro_rules! execute_task { - ($self:expr, $block:block) => { - $self.runtime.spawn(async move { $block }); - }; - } - - pub(crate) use execute_task; -} - -#[cfg(feature = "deadlock_detection")] -fn spawn_deadlock_checker() { - use log::{error, info}; - use parking_lot::deadlock; - use std::thread; - use std::time::Duration; - - // Create a background thread which checks for deadlocks every 10s - thread::spawn(move || loop { - thread::sleep(Duration::from_secs(10)); - let deadlocks = deadlock::check_deadlock(); - if deadlocks.is_empty() { - info!("# No deadlock"); - continue; - } - - eprintln!("# {} deadlocks detected", deadlocks.len()); - for (i, threads) in deadlocks.iter().enumerate() { - error!("Deadlock #{}", i); - for t in threads { - error!("Thread Id {:#?}\n, {:#?}", t.thread_id(), t.backtrace()); - } - } - }); -} - -#[cfg(feature = "serial")] -pub use serial::*; - -#[cfg(feature = "parallel")] -pub use parallel::*; - -#[cfg(feature = "async")] -pub use async_task::*; diff --git a/src/core/macros.rs b/src/core/macros.rs deleted file mode 100644 index a56b993..0000000 --- a/src/core/macros.rs +++ /dev/null @@ -1,207 +0,0 @@ -macro_rules! handle_fuse_reply_entry { - ($handler:expr, $resolver:expr, $req:expr, $parent:expr, $name:expr, $reply:expr, - $function:ident, ($($args:expr),*)) => { - macro_rules! if_lookup { - (lookup, $choice1:tt, $choice2:tt) => { - $choice1 - }; - ($any:tt, $choice1:tt, $choice2:tt) => { - $choice2 - }; - } - - let handler = $handler; - match handler.$function($($args),*) { - Ok(metadata) => { - let default_ttl = handler.get_default_ttl(); - let (id, file_attr) = TId::extract_metadata(metadata); - let ino = $resolver.lookup($parent, $name, id, true); - let (fuse_attr, ttl, generation) = file_attr.to_fuse(ino); - $reply.entry( - &ttl.unwrap_or(default_ttl), - &fuse_attr, - generation.unwrap_or(get_random_generation()), - ); - } - Err(e) => { - if_lookup!($function, { - if e.kind() == ErrorKind::FileNotFound { - // Lookup is preemptivly done in normal situations, we don't need to log an error - // eg: before creating a file - info!("{}: parent_ino {:x?}, [{}], {:?}", stringify!($function), $parent, e, $req); - } else { - warn!("{}: parent_ino {:x?}, [{}], {:?}", stringify!($function), $parent, e, $req); - }; - }, { - warn!("{}: parent_ino {:x?}, [{}], {:?}", stringify!($function), $parent, e, $req); - }); - $reply.error(e.raw_error()) - } - } - }; -} - -macro_rules! handle_fuse_reply_attr { - ($handler:expr, $resolve:expr, $req:expr, $ino:expr, $reply:expr, - $function:ident, ($($args:expr),*)) => { - match $handler.$function($($args),*) { - Ok(file_attr) => { - let default_ttl = $handler.get_default_ttl(); - let (fuse_attr, ttl, _) = file_attr.to_fuse($ino); - $reply.attr(&ttl.unwrap_or(default_ttl), &fuse_attr); - } - Err(e) => { - warn!("{}: ino {:x?}, [{}], {:?}", stringify!($function), $ino, e, $req); - $reply.error(e.raw_error()) - } - } - }; -} - -/// Handles directory read operations for FUSE filesystem.//+ -/////+ -/// This macro implements the logic for reading directory contents, supporting both//+ -/// regular directory reads (`readdir`) and extended directory reads (`readdirplus`).//+ -/////+ -/// # Parameters//+ -/////+ -/// * `$self`: The current filesystem instance.//+ -/// * `$req`: The FUSE request object.//+ -/// * `$ino`: The inode number of the directory being read.//+ -/// * `$fh`: The file handle of the open directory.//+ -/// * `$offset`: The offset from which to start reading directory entries.//+ -/// * `$reply`: The FUSE reply object to send the response.//+ -/// * `$handler_method`: The method to call on the handler to retrieve directory entries.//+ -/// * `$unpack_method`: The method to unpack metadata for each directory entry.//+ -/// * `$get_iter_method`: The method to retrieve the directory iterator.//+ -/// * `$reply_type`: The type of reply (readdir or readdirplus).//+ -/////+ -/// # Returns//+ -/////+ -/// This macro doesn't return a value directly, but it populates the `$reply` object//+ -/// with directory entries or an error code.// -macro_rules! handle_dir_read { - ($self:expr, $req:expr, $ino:expr, $fh:expr, $offset:expr, $reply:expr, - $handler_method:ident, $get_iter_method:ident, $reply_type:ty) => {{ - // Inner macro to handle readdir vs readdirplus differences - macro_rules! if_readdir { - (readdir, $choice1:tt, $choice2:tt) => { - $choice1 - }; - (readdirplus, $choice1:tt, $choice2:tt) => { - $choice2 - }; - } - - let req_info = RequestInfo::from($req); - let handler = $self.get_handler(); - let resolver = $self.get_resolver(); - let dirmap_iter = $self.$get_iter_method(); - - execute_task!($self, { - // Validate offset - if $offset < 0 { - error!("readdir called with a negative offset"); - $reply.error(ErrorKind::InvalidArgument.into()); - return; - } - - // ### Initialize directory iterator - let mut dir_iter = match $offset { - // First read: fetch children from handler - 0 => match handler.$handler_method(&req_info, resolver.resolve_id($ino), unsafe { - BorrowedFileHandle::from_raw($fh) - }) { - Ok(children) => { - // Unpack and process children - let (child_list, attr_list): (Vec<_>, Vec<_>) = children - .into_iter() - .map(|item| { - let (child_id, child_attr) = if_readdir!( - $handler_method, - { TId::extract_minimal_metadata(item.1) }, - { TId::extract_metadata(item.1) } - ); - ((item.0, child_id), child_attr) - }) - .unzip(); - - // Add children to resolver and create iterator - resolver - .add_children( - $ino, - child_list, - if_readdir!($handler_method, false, true), - ) - .into_iter() - .zip(attr_list.into_iter()) - .map(|((file_name, file_ino), file_attr)| { - (file_name, file_ino, file_attr) - }) - .collect() - } - Err(e) => { - warn!("readdir {:?}: {:?}", req_info, e); - $reply.error(e.raw_error()); - return; - } - }, - // Subsequent reads: retrieve saved iterator - _ => match { dirmap_iter.safe_borrow_mut().remove(&($ino, $offset)) } { - Some(dirmap_iter) => dirmap_iter, - None => { - // Case when fuse tries to read again after the final item - $reply.ok(); - return; - } - }, - }; - - let mut new_offset = $offset + 1; - - // ### Process directory entries - if_readdir!( - $handler_method, - { - // readdir: Add entries until buffer is full - while let Some((name, ino, kind)) = dir_iter.pop_front() { - if $reply.add(ino, new_offset, kind, &name) { - dirmap_iter - .safe_borrow_mut() - .insert(($ino, new_offset), dir_iter); - break; - } - new_offset += 1; - } - $reply.ok(); - }, - { - // readdirplus: Add entries with extended attributes - let default_ttl = handler.get_default_ttl(); - while let Some((name, ino, file_attr)) = dir_iter.pop_front() { - let (fuse_attr, ttl, generation) = file_attr.to_fuse(ino); - if $reply.add( - ino, - new_offset, - name, - &ttl.unwrap_or(default_ttl), - &fuse_attr, - generation.unwrap_or(get_random_generation()), - ) { - dirmap_iter - .safe_borrow_mut() - .insert((ino, new_offset), dir_iter); - break; - } - new_offset += 1; - } - $reply.ok(); - } - ); - }); - }}; -} - -pub(super) use handle_dir_read; -pub(super) use handle_fuse_reply_attr; -pub(super) use handle_fuse_reply_entry; diff --git a/src/core/thread_mode.rs b/src/core/thread_mode.rs deleted file mode 100644 index c7ebdc3..0000000 --- a/src/core/thread_mode.rs +++ /dev/null @@ -1,69 +0,0 @@ -use std::ops::{Deref, DerefMut}; - -pub(crate) trait SafeBorrowable { - type Guard<'a>: Deref + DerefMut - where - Self: 'a; - - fn safe_borrow_mut(&self) -> Self::Guard<'_>; -} - -#[cfg(feature = "serial")] -mod safe_borrowable_impl { - use super::*; - - use std::cell::{RefCell, RefMut}; - - impl SafeBorrowable for RefCell { - type Guard<'a> = RefMut<'a, T> where Self: 'a; - - fn safe_borrow_mut(&self) -> Self::Guard<'_> { - self.borrow_mut() - } - } -} - -#[cfg(all(feature = "parallel", not(feature = "deadlock_detection")))] -mod safe_borrowable_impl { - use super::*; - - use std::sync::{Mutex, MutexGuard}; - - impl SafeBorrowable for Mutex { - type Guard<'a> = MutexGuard<'a, T> where Self: 'a; - - fn safe_borrow_mut(&self) -> Self::Guard<'_> { - self.lock().unwrap() - } - } -} - -#[cfg(all(feature = "parallel", feature = "deadlock_detection"))] -mod safe_borrowable_impl { - use super::*; - - use parking_lot::{Mutex, MutexGuard}; - - impl SafeBorrowable for Mutex { - type Guard<'a> = MutexGuard<'a, T> where Self: 'a; - - fn safe_borrow_mut(&self) -> Self::Guard<'_> { - self.lock() - } - } -} - -#[cfg(any(feature = "async"))] -mod safe_borrowable_impl { - use super::*; - - use tokio::sync::{Mutex, MutexGuard}; - - impl SafeBorrowable for Mutex { - type Guard<'a> = MutexGuard<'a, T> where Self: 'a; - - async fn safe_borrow_mut(&self) -> Self::Guard<'_> { - self.lock().await - } - } -} diff --git a/src/fuse_async.rs b/src/fuse_async.rs new file mode 100644 index 0000000..dab7acb --- /dev/null +++ b/src/fuse_async.rs @@ -0,0 +1,10 @@ +#![cfg(feature = "async")] + +mod fuse_driver; +pub(crate) use fuse_driver::FuseDriver; + +mod fuse_handler; +pub use fuse_handler::FuseHandler; + +mod mouting; +pub use mouting::*; diff --git a/src/fuse_async/fuse_driver.rs b/src/fuse_async/fuse_driver.rs new file mode 100644 index 0000000..06f13d6 --- /dev/null +++ b/src/fuse_async/fuse_driver.rs @@ -0,0 +1,8 @@ +use crate::types::*; + +use super::fuse_handler::FuseHandler; +use crate::fuse_common::inode_mapping::*; + +use easy_fuser_macro::implement_fuse_driver; + +implement_fuse_driver!("async"); diff --git a/src/fuse_async/fuse_handler.rs b/src/fuse_async/fuse_handler.rs new file mode 100644 index 0000000..826f99b --- /dev/null +++ b/src/fuse_async/fuse_handler.rs @@ -0,0 +1,5 @@ +use crate::types::*; + +use easy_fuser_macro::implement_fuse_handler; + +implement_fuse_handler!("async"); diff --git a/src/fuse_async/mouting.rs b/src/fuse_async/mouting.rs new file mode 100644 index 0000000..d32efc7 --- /dev/null +++ b/src/fuse_async/mouting.rs @@ -0,0 +1,40 @@ +use std::io; +use std::path::Path; + +use super::{FuseDriver, FuseHandler}; +use crate::types::*; +use fuser::{mount2, spawn_mount2}; +pub use fuser::{BackgroundSession, MountOption, Session, SessionUnmounter}; + +#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/mount.md"))] +pub fn mount( + filesystem: FS, + mountpoint: P, + options: &[MountOption], + num_threads: usize, +) -> io::Result<()> +where + T: FileIdType, + FS: FuseHandler, + P: AsRef, +{ + let driver = FuseDriver::new(filesystem, num_threads); + mount2(driver, mountpoint, options) +} + +#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/spawn_mount.md"))] + +pub fn spawn_mount( + filesystem: FS, + mountpoint: P, + options: &[MountOption], + num_threads: usize, +) -> io::Result +where + T: FileIdType, + FS: FuseHandler + Send, + P: AsRef, +{ + let driver = FuseDriver::new(filesystem, num_threads); + spawn_mount2(driver, mountpoint, options) +} diff --git a/src/fuse_common.rs b/src/fuse_common.rs new file mode 100644 index 0000000..6e40e14 --- /dev/null +++ b/src/fuse_common.rs @@ -0,0 +1,3 @@ +pub(crate) mod inode_mapping; + +pub(crate) use inode_mapping::{InodeResolvable, ROOT_INO}; diff --git a/src/core/inode_mapping.rs b/src/fuse_common/inode_mapping.rs similarity index 100% rename from src/core/inode_mapping.rs rename to src/fuse_common/inode_mapping.rs diff --git a/src/templates.rs b/src/fuse_common/templates.rs similarity index 100% rename from src/templates.rs rename to src/fuse_common/templates.rs diff --git a/src/templates/default_fuse_handler.rs b/src/fuse_common/templates/default_fuse_handler.rs similarity index 100% rename from src/templates/default_fuse_handler.rs rename to src/fuse_common/templates/default_fuse_handler.rs diff --git a/src/templates/fd_handler_helper.rs b/src/fuse_common/templates/fd_handler_helper.rs similarity index 100% rename from src/templates/fd_handler_helper.rs rename to src/fuse_common/templates/fd_handler_helper.rs diff --git a/src/templates/mirror_fs.rs b/src/fuse_common/templates/mirror_fs.rs similarity index 100% rename from src/templates/mirror_fs.rs rename to src/fuse_common/templates/mirror_fs.rs diff --git a/src/fuse_handler.rs b/src/fuse_handler.rs deleted file mode 100644 index 5a76a9a..0000000 --- a/src/fuse_handler.rs +++ /dev/null @@ -1,594 +0,0 @@ -/// The `FuseHandler` trait is the core interface for implementing a userspace filesystem via FUSE (Filesystem in Userspace). -/// -/// This trait defines methods that correspond to various filesystem operations. By implementing this trait, -/// you can create custom filesystem behaviors for your FUSE-based filesystem. -/// -/// # Type Parameter -/// -/// - `T`: A type that implements `FileIdType`. This represents the unique identifier for files and directories in your filesystem. -/// -/// # Usage -/// -/// To create a custom filesystem, implement this trait for your struct. You can choose to implement all methods -/// or rely on default implementations provided by one of the provided templates (like `MirrorFs` or `DefaultFuseHandler`). -/// -/// ## Example: Custom Filesystem with MirrorFs as base -/// -/// ```rust, no_run -/// use easy_fuser::templates::{DefaultFuseHandler, mirror_fs::*}; -/// use easy_fuser::prelude::*; -/// use std::path::{Path, PathBuf}; -/// use std::ffi::OsStr; -/// -/// struct MyCustomFs { -/// inner: Box>, -/// // other fields... -/// } -/// -/// impl MyCustomFs { -/// pub fn new(source_path: PathBuf) -> Self { -/// MyCustomFs { inner: Box::new(MirrorFsReadOnly::new(source_path, DefaultFuseHandler::new())) } -/// -/// } -/// } -/// -/// impl FuseHandler for MyCustomFs { -/// fn get_inner(&self) -> &dyn FuseHandler { -/// // Delegate to MirrorFsReadOnly for standard behavior -/// self.inner.as_ref() -/// } -/// -/// fn lookup(&self, req: &RequestInfo, parent_id: PathBuf, name: &OsStr) -> FuseResult { -/// // Custom logic for lookup operation -/// // ... -/// -/// // Delegate to inner handler for standard behavior -/// self.inner.lookup(req, parent_id, name) -/// } -/// -/// // Implement other FuseHandler methods as needed, delegating to self.inner as appropriate -/// // ... -/// } -/// ``` -/// -/// # Important Methods -/// -/// While all methods in this trait are important for a fully functional filesystem, some key methods include: -/// -/// - `lookup`: Look up a directory entry by name and get its attributes. -/// - `getattr`: Get file attributes. -/// - `read`: Read data from a file. -/// - `write`: Write data to a file. -/// - `readdir`: Read directory contents. -/// - `open`: Open a file. -/// - `create`: Create and open a file. -/// -/// # Default Implementations -/// -/// Many methods in this trait have default implementations that delegate to the inner handler returned by `get_inner()`. -/// This allows for easy extension and customization of existing filesystem implementations by chaining/overriding their behaviors. -/// -/// # Multithreading, Safety, Traits & Lifetime -/// -/// ## Using serial feature -/// -/// This trait is bound by the `'static` lifetime. However, it doesn't need `Send` or `Sync`. -/// -/// **Note:** Using `spawn_mount` for your filesystem will require at least the `Send` trait. -/// -/// ## Using parallel or async feature -/// -/// This trait requires `Send + Sync` and `'static` lifetime. -/// -/// **Important:** FUSE will lock some operations to run at the same time with the same inode, such as `readdir`. -/// This behaviour is not well documented and cannot be guaranteed for now. -/// -//// # Additional Resources: -/// For more detailed information, refer to the fuser project documentation, which serves as the foundation for this crate: https://docs.rs/fuser -/// -/// Documentation is inspired by the original fuser documentation -use std::ffi::{OsStr, OsString}; -use std::path::Path; -use std::time::Duration; - -use crate::types::*; - -mod private { - #[cfg(not(feature = "serial"))] - pub trait OptionalSendSync: Sync + Send {} - #[cfg(not(feature = "serial"))] - impl OptionalSendSync for T {} - #[cfg(feature = "serial")] - pub trait OptionalSendSync {} - #[cfg(feature = "serial")] - impl OptionalSendSync for T {} -} -use private::OptionalSendSync; - -pub trait FuseHandler: OptionalSendSync + 'static { - /// Delegate unprovided methods to another FuseHandler, enabling composition - fn get_inner(&self) -> &dyn FuseHandler; - - /// Provide a default Time-To-Live for file metadata - /// - /// Can be overriden for each FileAttributes returned. - fn get_default_ttl(&self) -> Duration { - Duration::from_secs(1) - } - - /// Initialize the filesystem and configure kernel connection - fn init(&self, req: &RequestInfo, config: &mut KernelConfig) -> FuseResult<()> { - self.get_inner().init(req, config) - } - - /// Perform cleanup operations on filesystem exit - fn destroy(&self) { - self.get_inner().destroy(); - } - - /// Check file access permissions - /// - /// This method is called for the access() system call. If the 'default_permissions' - /// mount option is given, this method is not called. This method is not called - /// under Linux kernel versions 2.4.x - fn access(&self, req: &RequestInfo, file_id: TId, mask: AccessMask) -> FuseResult<()> { - self.get_inner().access(req, file_id, mask) - } - - /// Map block index within file to block index within device - /// - /// Note: This makes sense only for block device backed filesystems mounted - /// with the 'blkdev' option - fn bmap(&self, req: &RequestInfo, file_id: TId, blocksize: u32, idx: u64) -> FuseResult { - self.get_inner().bmap(req, file_id, blocksize, idx) - } - - /// Copy the specified range from the source inode to the destination inode - fn copy_file_range( - &self, - req: &RequestInfo, - file_in: TId, - file_handle_in: BorrowedFileHandle, - offset_in: i64, - file_out: TId, - file_handle_out: BorrowedFileHandle, - offset_out: i64, - len: u64, - flags: u32, // Not implemented yet in standard - ) -> FuseResult { - self.get_inner().copy_file_range( - req, - file_in, - file_handle_in, - offset_in, - file_out, - file_handle_out, - offset_out, - len, - flags, - ) - } - - /// Create and open a file - /// - /// If the file does not exist, first create it with the specified mode, and then - /// open it. Open flags (with the exception of O_NOCTTY) are available in flags. - /// If this method is not implemented or under Linux kernel versions earlier than - /// 2.6.15, the mknod() and open() methods will be called instead. - fn create( - &self, - req: &RequestInfo, - parent_id: TId, - name: &OsStr, - mode: u32, - umask: u32, - flags: OpenFlags, - ) -> FuseResult<(OwnedFileHandle, TId::Metadata, FUSEOpenResponseFlags)> { - self.get_inner() - .create(req, parent_id, name, mode, umask, flags) - } - - /// Preallocate or deallocate space to a file - fn fallocate( - &self, - req: &RequestInfo, - file_id: TId, - file_handle: BorrowedFileHandle, - offset: i64, - length: i64, - mode: FallocateFlags, - ) -> FuseResult<()> { - self.get_inner() - .fallocate(req, file_id, file_handle, offset, length, mode) - } - - /// Flush cached data for an open file - /// - /// Called on each close() of the opened file. Not guaranteed to be called after writes or at all. - /// Used for returning write errors or removing file locks. - fn flush( - &self, - req: &RequestInfo, - file_id: TId, - file_handle: BorrowedFileHandle, - lock_owner: u64, - ) -> FuseResult<()> { - self.get_inner() - .flush(req, file_id, file_handle, lock_owner) - } - - /// Release references to an inode, if the nlookup count reaches zero (to substract from the number of lookups). - fn forget(&self, req: &RequestInfo, file_id: TId, nlookup: u64) { - self.get_inner().forget(req, file_id, nlookup); - } - - /// Synchronize file contents - /// - /// If datasync is true, only flush user data, not metadata. - fn fsync( - &self, - req: &RequestInfo, - file_id: TId, - file_handle: BorrowedFileHandle, - datasync: bool, - ) -> FuseResult<()> { - self.get_inner().fsync(req, file_id, file_handle, datasync) - } - - /// Synchronize directory contents - /// - /// If the datasync parameter is true, then only the directory contents should - /// be flushed, not the metadata. The file_handle will contain the value set - /// by the opendir method, or will be undefined if the opendir method didn't - /// set any value. - fn fsyncdir( - &self, - req: &RequestInfo, - file_id: TId, - file_handle: BorrowedFileHandle, - datasync: bool, - ) -> FuseResult<()> { - self.get_inner() - .fsyncdir(req, file_id, file_handle, datasync) - } - - /// Modify file attributes - fn getattr( - &self, - req: &RequestInfo, - file_id: TId, - file_handle: Option, - ) -> FuseResult { - self.get_inner().getattr(req, file_id, file_handle) - } - - /// Test for a POSIX file lock. - fn getlk( - &self, - req: &RequestInfo, - file_id: TId, - file_handle: BorrowedFileHandle, - lock_owner: u64, - lock_info: LockInfo, - ) -> FuseResult { - self.get_inner() - .getlk(req, file_id, file_handle, lock_owner, lock_info) - } - - /// Get an extended attribute - fn getxattr( - &self, - req: &RequestInfo, - file_id: TId, - name: &OsStr, - size: u32, - ) -> FuseResult> { - self.get_inner().getxattr(req, file_id, name, size) - } - - /// control device - fn ioctl( - &self, - req: &RequestInfo, - file_id: TId, - file_handle: BorrowedFileHandle, - flags: IOCtlFlags, - cmd: u32, - in_data: Vec, - out_size: u32, - ) -> FuseResult<(i32, Vec)> { - self.get_inner() - .ioctl(req, file_id, file_handle, flags, cmd, in_data, out_size) - } - - /// Create a hard link. - fn link( - &self, - req: &RequestInfo, - file_id: TId, - newparent: TId, - newname: &OsStr, - ) -> FuseResult { - self.get_inner().link(req, file_id, newparent, newname) - } - - /// List extended attribute names - fn listxattr(&self, req: &RequestInfo, file_id: TId, size: u32) -> FuseResult> { - self.get_inner().listxattr(req, file_id, size) - } - - /// Retrieve file attributes for a directory entry by name and increment the lookup count associated with the inode. - fn lookup(&self, req: &RequestInfo, parent_id: TId, name: &OsStr) -> FuseResult { - self.get_inner().lookup(req, parent_id, name) - } - - /// Reposition read/write file offset - fn lseek( - &self, - req: &RequestInfo, - file_id: TId, - file_handle: BorrowedFileHandle, - seek: SeekFrom, - ) -> FuseResult { - self.get_inner().lseek(req, file_id, file_handle, seek) - } - - /// Create a new directory - fn mkdir( - &self, - req: &RequestInfo, - parent_id: TId, - name: &OsStr, - mode: u32, - umask: u32, - ) -> FuseResult { - self.get_inner().mkdir(req, parent_id, name, mode, umask) - } - - /// Create a new file node (regular file, device, FIFO, socket, etc) - fn mknod( - &self, - req: &RequestInfo, - parent_id: TId, - name: &OsStr, - mode: u32, - umask: u32, - rdev: DeviceType, - ) -> FuseResult { - self.get_inner() - .mknod(req, parent_id, name, mode, umask, rdev) - } - - /// Open a file and return a file handle. - /// - /// Open flags (with the exception of O_CREAT, O_EXCL, O_NOCTTY and O_TRUNC) are available in flags. You may store an arbitrary file handle (pointer, index, etc) in file_handle response, and use this in other all other file operations (read, write, flush, release, fsync). Filesystem may also implement stateless file I/O and not store anything in fh. There are also some flags (direct_io, keep_cache) which the filesystem may set, to change the way the file is opened. See fuse_file_info structure in for more details. - fn open( - &self, - req: &RequestInfo, - file_id: TId, - flags: OpenFlags, - ) -> FuseResult<(OwnedFileHandle, FUSEOpenResponseFlags)> { - self.get_inner().open(req, file_id, flags) - } - - /// Open a directory - /// - /// Allows storing a file handle for use in subsequent directory operations. - fn opendir( - &self, - req: &RequestInfo, - file_id: TId, - flags: OpenFlags, - ) -> FuseResult<(OwnedFileHandle, FUSEOpenResponseFlags)> { - self.get_inner().opendir(req, file_id, flags) - } - - /// Read data from a file - /// - /// Read should send exactly the number of bytes requested except on EOF or error, otherwise the rest of the data will be substituted with zeroes. An exception to this is when the file has been opened in ‘direct_io’ mode, in which case the return value of the read system call will reflect the return value of this operation. fh will contain the value set by the open method, or will be undefined if the open method didn’t set any value. - /// - /// flags: these are the file flags, such as O_SYNC. Only supported with ABI >= 7.9 lock_owner: only supported with ABI >= 7.9 - fn read( - &self, - req: &RequestInfo, - file_id: TId, - file_handle: BorrowedFileHandle, - seek: SeekFrom, - size: u32, - flags: FUSEOpenFlags, - lock_owner: Option, - ) -> FuseResult> { - self.get_inner() - .read(req, file_id, file_handle, seek, size, flags, lock_owner) - } - - /// Read directory contents - /// - /// Returns a list of directory entries with minimal metadata. - /// - /// Important: The returned file names (OsString) must not contain any slashes ('/'). - /// Including slashes in the file names will result in undefined behavior. - fn readdir( - &self, - req: &RequestInfo, - file_id: TId, - file_handle: BorrowedFileHandle, - ) -> FuseResult> { - self.get_inner().readdir(req, file_id, file_handle) - } - - /// Read directory contents with full file attributes - /// - /// Default implementation combines readdir and lookup operations. - /// - /// Important: The returned file names (OsString) must not contain any slashes ('/'). - /// Including slashes in the file names will result in undefined behavior. - fn readdirplus( - &self, - req: &RequestInfo, - file_id: TId, - file_handle: BorrowedFileHandle, - ) -> FuseResult> { - let readdir_result = self.readdir(req, file_id.clone(), file_handle)?; - let mut result = Vec::with_capacity(readdir_result.len()); - for (name, _) in readdir_result.into_iter() { - let metadata = self.lookup(req, file_id.clone(), &name)?; - result.push((name, metadata)); - } - Ok(result) - } - - /// Read the target of a symbolic link - fn readlink(&self, req: &RequestInfo, file_id: TId) -> FuseResult> { - self.get_inner().readlink(req, file_id) - } - - /// Release an open file - /// - /// Called when all file descriptors are closed and all memory mappings are unmapped. - /// Guaranteed to be called once for every open() call. - fn release( - &self, - req: &RequestInfo, - file_id: TId, - file_handle: OwnedFileHandle, - flags: OpenFlags, - lock_owner: Option, - flush: bool, - ) -> FuseResult<()> { - self.get_inner() - .release(req, file_id, file_handle, flags, lock_owner, flush) - } - - /// Release an open directory - /// - /// This method is called exactly once for every successful opendir operation. - /// The file_handle parameter will contain the value set by the opendir method, - /// or will be undefined if the opendir method didn't set any value. - fn releasedir( - &self, - req: &RequestInfo, - file_id: TId, - file_handle: OwnedFileHandle, - flags: OpenFlags, - ) -> FuseResult<()> { - self.get_inner() - .releasedir(req, file_id, file_handle, flags) - } - - /// Remove an extended attribute. - fn removexattr(&self, req: &RequestInfo, file_id: TId, name: &OsStr) -> FuseResult<()> { - self.get_inner().removexattr(req, file_id, name) - } - - /// Rename a file or directory - fn rename( - &self, - req: &RequestInfo, - parent_id: TId, - name: &OsStr, - newparent: TId, - newname: &OsStr, - flags: RenameFlags, - ) -> FuseResult<()> { - self.get_inner() - .rename(req, parent_id, name, newparent, newname, flags) - } - - /// Remove a directory - fn rmdir(&self, req: &RequestInfo, parent_id: TId, name: &OsStr) -> FuseResult<()> { - self.get_inner().rmdir(req, parent_id, name) - } - - /// Set file attributes. - fn setattr( - &self, - req: &RequestInfo, - file_id: TId, - attrs: SetAttrRequest, - ) -> FuseResult { - self.get_inner().setattr(req, file_id, attrs) - } - - /// Acquire, modify or release a POSIX file lock - /// - /// For POSIX threads (NPTL) there's a 1-1 relation between pid and owner, but - /// otherwise this is not always the case. For checking lock ownership, 'fi->owner' - /// must be used. The l_pid field in 'struct flock' should only be used to fill - /// in this field in getlk(). - fn setlk( - &self, - req: &RequestInfo, - file_id: TId, - file_handle: BorrowedFileHandle, - lock_owner: u64, - lock_info: LockInfo, - sleep: bool, - ) -> FuseResult<()> { - self.get_inner() - .setlk(req, file_id, file_handle, lock_owner, lock_info, sleep) - } - - /// Set an extended attribute - fn setxattr( - &self, - req: &RequestInfo, - file_id: TId, - name: &OsStr, - value: Vec, - flags: FUSESetXAttrFlags, - position: u32, - ) -> FuseResult<()> { - self.get_inner() - .setxattr(req, file_id, name, value, flags, position) - } - - /// Get file system statistics - fn statfs(&self, req: &RequestInfo, file_id: TId) -> FuseResult { - self.get_inner().statfs(req, file_id) - } - - /// Create a symbolic link. - fn symlink( - &self, - req: &RequestInfo, - parent_id: TId, - link_name: &OsStr, - target: &Path, - ) -> FuseResult { - self.get_inner().symlink(req, parent_id, link_name, target) - } - - /// Write data to a file - /// - /// Write should return exactly the number of bytes requested except on error. An exception to this is when the file has been opened in ‘direct_io’ mode, in which case the return value of the write system call will reflect the return value of this operation. fh will contain the value set by the open method, or will be undefined if the open method didn’t set any value. - /// - /// write_flags: will contain FUSE_WRITE_CACHE, if this write is from the page cache. If set, the pid, uid, gid, and fh may not match the value that would have been sent if write cachin is disabled flags: these are the file flags, such as O_SYNC. Only supported with ABI >= 7.9 lock_owner: only supported with ABI >= 7.9 - fn write( - &self, - req: &RequestInfo, - file_id: TId, - file_handle: BorrowedFileHandle, - seek: SeekFrom, - data: Vec, - write_flags: FUSEWriteFlags, - flags: OpenFlags, - lock_owner: Option, - ) -> FuseResult { - self.get_inner().write( - req, - file_id, - file_handle, - seek, - data, - write_flags, - flags, - lock_owner, - ) - } - - /// Remove a file - fn unlink(&self, req: &RequestInfo, parent_id: TId, name: &OsStr) -> FuseResult<()> { - self.get_inner().unlink(req, parent_id, name) - } -} diff --git a/src/fuse_parallel.rs b/src/fuse_parallel.rs new file mode 100644 index 0000000..c85b935 --- /dev/null +++ b/src/fuse_parallel.rs @@ -0,0 +1,10 @@ +#![cfg(feature = "parallel")] + +mod fuse_driver; +pub(crate) use fuse_driver::FuseDriver; + +mod fuse_handler; +pub use fuse_handler::FuseHandler; + +mod mouting; +pub use mouting::*; diff --git a/src/fuse_parallel/fuse_driver.rs b/src/fuse_parallel/fuse_driver.rs new file mode 100644 index 0000000..fa6fa50 --- /dev/null +++ b/src/fuse_parallel/fuse_driver.rs @@ -0,0 +1,8 @@ +use crate::types::*; + +use super::fuse_handler::FuseHandler; +use crate::fuse_common::inode_mapping::*; + +use easy_fuser_macro::implement_fuse_driver; + +implement_fuse_driver!("parallel"); diff --git a/src/fuse_parallel/fuse_handler.rs b/src/fuse_parallel/fuse_handler.rs new file mode 100644 index 0000000..adb85cc --- /dev/null +++ b/src/fuse_parallel/fuse_handler.rs @@ -0,0 +1,5 @@ +use crate::types::*; + +use easy_fuser_macro::implement_fuse_handler; + +implement_fuse_handler!("parallel"); diff --git a/src/fuse_parallel/mouting.rs b/src/fuse_parallel/mouting.rs new file mode 100644 index 0000000..d32efc7 --- /dev/null +++ b/src/fuse_parallel/mouting.rs @@ -0,0 +1,40 @@ +use std::io; +use std::path::Path; + +use super::{FuseDriver, FuseHandler}; +use crate::types::*; +use fuser::{mount2, spawn_mount2}; +pub use fuser::{BackgroundSession, MountOption, Session, SessionUnmounter}; + +#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/mount.md"))] +pub fn mount( + filesystem: FS, + mountpoint: P, + options: &[MountOption], + num_threads: usize, +) -> io::Result<()> +where + T: FileIdType, + FS: FuseHandler, + P: AsRef, +{ + let driver = FuseDriver::new(filesystem, num_threads); + mount2(driver, mountpoint, options) +} + +#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/spawn_mount.md"))] + +pub fn spawn_mount( + filesystem: FS, + mountpoint: P, + options: &[MountOption], + num_threads: usize, +) -> io::Result +where + T: FileIdType, + FS: FuseHandler + Send, + P: AsRef, +{ + let driver = FuseDriver::new(filesystem, num_threads); + spawn_mount2(driver, mountpoint, options) +} diff --git a/src/fuse_serial.rs b/src/fuse_serial.rs new file mode 100644 index 0000000..76b1e98 --- /dev/null +++ b/src/fuse_serial.rs @@ -0,0 +1,10 @@ +#![cfg(feature = "serial")] + +mod fuse_driver; +pub(crate) use fuse_driver::FuseDriver; + +mod fuse_handler; +pub use fuse_handler::FuseHandler; + +mod mouting; +pub use mouting::*; diff --git a/src/fuse_serial/fuse_driver.rs b/src/fuse_serial/fuse_driver.rs new file mode 100644 index 0000000..420bfbb --- /dev/null +++ b/src/fuse_serial/fuse_driver.rs @@ -0,0 +1,8 @@ +use crate::types::*; + +use super::fuse_handler::FuseHandler; +use crate::fuse_common::inode_mapping::*; + +use easy_fuser_macro::implement_fuse_driver; + +implement_fuse_driver!("serial"); diff --git a/src/fuse_serial/fuse_handler.rs b/src/fuse_serial/fuse_handler.rs new file mode 100644 index 0000000..0a8d0b1 --- /dev/null +++ b/src/fuse_serial/fuse_handler.rs @@ -0,0 +1,5 @@ +use crate::types::*; + +use easy_fuser_macro::implement_fuse_handler; + +implement_fuse_handler!("serial"); diff --git a/src/fuse_serial/mouting.rs b/src/fuse_serial/mouting.rs new file mode 100644 index 0000000..373e1d7 --- /dev/null +++ b/src/fuse_serial/mouting.rs @@ -0,0 +1,34 @@ +use std::io; +use std::path::Path; + +use super::{FuseDriver, FuseHandler}; +use crate::types::*; +use fuser::{mount2, spawn_mount2}; +pub use fuser::{BackgroundSession, MountOption, Session, SessionUnmounter}; + +#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/mount.md"))] +pub fn mount(filesystem: FS, mountpoint: P, options: &[MountOption]) -> io::Result<()> +where + T: FileIdType, + FS: FuseHandler, + P: AsRef, +{ + let driver = FuseDriver::new(filesystem, 1); + mount2(driver, mountpoint, options) +} + +#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/spawn_mount.md"))] + +pub fn spawn_mount( + filesystem: FS, + mountpoint: P, + options: &[MountOption], +) -> io::Result +where + T: FileIdType, + FS: FuseHandler + Send, + P: AsRef, +{ + let driver = FuseDriver::new(filesystem, 1); + spawn_mount2(driver, mountpoint, options) +} diff --git a/src/inode_mapper.rs b/src/inode_mapper.rs index ad93d37..5dd246a 100644 --- a/src/inode_mapper.rs +++ b/src/inode_mapper.rs @@ -4,7 +4,7 @@ use std::ffi::{OsStr, OsString}; use std::hash::Hash; use std::sync::Arc; -use super::{Inode, ROOT_INODE}; +use crate::prelude::{Inode, ROOT_INODE}; /// Helper structure for managing inodes and their relationships. /// @@ -538,8 +538,8 @@ mod tests { use std::collections::HashSet; use std::ffi::OsString; + use crate::prelude::ROOT_INODE; use crate::types::Inode; - use crate::ROOT_INODE; #[test] fn test_insert_child_returns_old_inode() { diff --git a/src/lib.rs b/src/lib.rs index c23ea55..c8078ef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,102 +10,18 @@ compile_error!("Feature 'async' is not yet implemented."); ))] compile_error!("At least one of the features 'serial', 'parallel', or 'async' must be enabled"); -#[cfg(all(feature = "serial", any(feature = "parallel", feature = "async")))] -compile_error!("Feature 'serial' cannot be used with feature parallel or async"); - -#[cfg(all(feature = "parallel", any(feature = "serial", feature = "async")))] -compile_error!("Feature 'parallel' cannot be used with feature serial or async"); - -#[cfg(all(feature = "async", any(feature = "serial", feature = "parallel")))] -compile_error!("Feature 'async' cannot be used with feature serial or parallel"); - -mod core; -mod fuse_handler; +pub mod fuse_async; +mod fuse_common; +pub mod fuse_parallel; +pub mod fuse_serial; pub mod inode_mapper; -pub mod templates; pub mod types; pub mod unix_fs; -pub use fuse_handler::FuseHandler; -use fuser::{BackgroundSession, MountOption}; - pub mod prelude { //! Re-exports the necessary types and functions from the `easy_fuser` crate. - pub use super::fuse_handler::FuseHandler; pub use super::types::*; - pub use super::{mount, spawn_mount}; pub use fuser::{BackgroundSession, MountOption, Session, SessionUnmounter}; } - -// Implentation of the high-level functions -use std::io; -use std::path::Path; - -use core::FuseDriver; -use fuser::{mount2, spawn_mount2}; -use prelude::*; - -#[doc = include_str!("../docs/mount.md")] -#[cfg(not(feature = "serial"))] -pub fn mount( - filesystem: FS, - mountpoint: P, - options: &[MountOption], - num_threads: usize, -) -> io::Result<()> -where - T: FileIdType, - FS: FuseHandler, - P: AsRef, -{ - let driver = FuseDriver::new(filesystem, num_threads); - mount2(driver, mountpoint, options) -} -#[doc = include_str!("../docs/mount.md")] -#[cfg(feature = "serial")] -pub fn mount(filesystem: FS, mountpoint: P, options: &[MountOption]) -> io::Result<()> -where - T: FileIdType, - FS: FuseHandler, - P: AsRef, -{ - // num_thread argument will not be taken into account in this function due to feature serial - let driver = FuseDriver::new(filesystem, 1); - mount2(driver, mountpoint, options) -} - -#[doc = include_str!("../docs/spawn_mount.md")] -#[cfg(not(feature = "serial"))] -pub fn spawn_mount( - filesystem: FS, - mountpoint: P, - options: &[MountOption], - num_threads: usize, -) -> io::Result -where - T: FileIdType, - FS: FuseHandler + Send, - P: AsRef, -{ - let driver = FuseDriver::new(filesystem, num_threads); - spawn_mount2(driver, mountpoint, options) -} - -#[doc = include_str!("../docs/spawn_mount.md")] -#[cfg(feature = "serial")] -pub fn spawn_mount( - filesystem: FS, - mountpoint: P, - options: &[MountOption], -) -> io::Result -where - T: FileIdType, - FS: FuseHandler + Send, - P: AsRef, -{ - // num_thread argument will not be taken into account in this function due to feature serial - let driver = FuseDriver::new(filesystem, 1); - spawn_mount2(driver, mountpoint, options) -} diff --git a/src/types/file_id_type.rs b/src/types/file_id_type.rs index 718e50e..4d799f9 100644 --- a/src/types/file_id_type.rs +++ b/src/types/file_id_type.rs @@ -15,7 +15,7 @@ use std::{ use fuser::FileType as FileKind; -use crate::core::InodeResolvable; +use crate::fuse_common::InodeResolvable; use super::arguments::FileAttribute; use super::inode::*; @@ -39,7 +39,7 @@ use super::inode::*; /// - Cons: Path components are stored in reverse order, which may require additional handling. /// - Root: Represented by an empty vector. pub trait FileIdType: - 'static + Debug + Clone + PartialEq + Eq + std::hash::Hash + InodeResolvable + 'static + Debug + Clone + PartialEq + Eq + std::hash::Hash + InodeResolvable + Send { /// Full metadata type for the file system. /// @@ -48,7 +48,7 @@ pub trait FileIdType: /// /// For PathBuf-based: FileAttribute /// - User only needs to provide FileAttribute; Inode is managed internally. - type Metadata; + type Metadata: Send; /// Minimal metadata type for the file system. /// @@ -57,7 +57,7 @@ pub trait FileIdType: /// /// For PathBuf-based: FileKind /// - User only needs to provide FileKind; Inode is managed internally. - type MinimalMetadata; + type MinimalMetadata: Send; #[doc(hidden)] type _Id; diff --git a/src/types/inode.rs b/src/types/inode.rs index b0638a1..1f65a25 100644 --- a/src/types/inode.rs +++ b/src/types/inode.rs @@ -1,6 +1,6 @@ //! Inode number in a FUSE (Filesystem in Userspace) filesystem. -use crate::core::ROOT_INO; +use crate::fuse_common::ROOT_INO; /// Represents the mountpoint folder in a FuseFilesystem /// Its value is 1 and should not be modified. diff --git a/src/unix_fs/linux_fs.rs b/src/unix_fs/linux_fs.rs index 1d2b533..ed6b014 100644 --- a/src/unix_fs/linux_fs.rs +++ b/src/unix_fs/linux_fs.rs @@ -4,7 +4,7 @@ use std::{ path::Path, }; -use crate::PosixError; +use crate::types::PosixError; use libc::{self, c_char, c_int, c_uint, off_t, size_t, ssize_t}; use super::{cstring_from_path, StatFs}; diff --git a/tests/integration_test.rs b/tests/integration_test.rs index edc98d8..e7a015b 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -1,4 +1,4 @@ -use easy_fuser::prelude::*; +use easy_fuser::fuse_parallel::*; use easy_fuser::templates::{mirror_fs::*, DefaultFuseHandler}; use std::fs::{self, File}; @@ -21,9 +21,9 @@ fn test_mirror_fs_operations() { let mntpoint_clone = mntpoint.clone(); let handle = std::thread::spawn(move || { let fs = MirrorFs::new(source_path.clone(), DefaultFuseHandler::new()); - #[cfg(feature = "serial")] - mount(fs, &mntpoint_clone, &[]).unwrap(); - #[cfg(not(feature = "serial"))] + //#[cfg(feature = "serial")] + //mount(fs, &mntpoint_clone, &[]).unwrap(); + //#[cfg(not(feature = "serial"))] mount(fs, &mntpoint_clone, &[], 4).unwrap(); }); std::thread::sleep(Duration::from_millis(50)); // Wait for the mount to finish