diff --git a/src/pxar/fuse.rs b/src/pxar/fuse.rs index 056d665a..cea4ff23 100644 --- a/src/pxar/fuse.rs +++ b/src/pxar/fuse.rs @@ -4,23 +4,47 @@ use std::ffi::{OsStr, CString}; use std::fs::File; +use std::io::BufReader; use std::os::unix::ffi::OsStrExt; use std::path::Path; +use std::sync::Mutex; use libc; use libc::{c_int, c_void, c_char, size_t}; use failure::*; +use super::decoder::Decoder; + +/// Node ID of the root inode +/// This is the only one whose ID is not equal to the offset in the file. +/// This is ok since offset 1 is part of the entry header and will therefore +/// not occur again, but remapping to the correct offset of 0 is required. +const FUSE_ROOT_ID: u64 = 1; + +fn decoder_callback(path: &Path) -> Result<(), Error> { + println!("{:#?}", path); + Ok(()) +} + +/// FFI types for easier readability +type Request = *mut c_void; +type MutPtr = *mut c_void; +type ConstPtr = *const c_void; +type StrPtr = *const c_char; + #[link(name = "fuse3")] extern "C" { - fn fuse_session_new(args: *const FuseArgs, oprs: *const Operations, size: size_t, op: *const c_void) -> *mut c_void; - fn fuse_set_signal_handlers(session: *const c_void) -> c_int; - fn fuse_remove_signal_handlers(session: *const c_void); + fn fuse_session_new(args: *const FuseArgs, oprs: *const Operations, size: size_t, op: ConstPtr) -> MutPtr; + fn fuse_set_signal_handlers(session: ConstPtr) -> c_int; + fn fuse_remove_signal_handlers(session: ConstPtr); fn fuse_daemonize(foreground: c_int) -> c_int; - fn fuse_session_mount(session: *const c_void, mountpoint: *const c_char) -> c_int; - fn fuse_session_unmount(session: *const c_void); - fn fuse_session_loop(session: *const c_void) -> c_int; - fn fuse_session_destroy(session: *const c_void); + fn fuse_session_mount(session: ConstPtr, mountpoint: StrPtr) -> c_int; + fn fuse_session_unmount(session: ConstPtr); + fn fuse_session_loop(session: ConstPtr) -> c_int; + fn fuse_session_destroy(session: ConstPtr); + fn fuse_reply_attr(req: Request, attr: *const libc::stat, timeout: f64) -> c_int; + fn fuse_reply_err(req: Request, errno: c_int) -> c_int; + fn fuse_req_userdata(req: Request) -> MutPtr; } /// Command line arguments passed to fuse. @@ -28,24 +52,67 @@ extern "C" { #[derive(Debug)] struct FuseArgs { argc: c_int, - argv: *const *const c_char, + argv: *const StrPtr, allocated: c_int, } /// `Session` stores a pointer to the session context and is used to mount the /// archive to the given mountpoint. -#[derive(Debug)] pub struct Session { - ptr: *mut c_void, - archive: File, + ptr: MutPtr, verbose: bool, } /// `Operations` defines the callback function table of supported operations. #[repr(C)] +#[derive(Default)] struct Operations { - init: extern fn(userdata: *mut c_void) -> *mut c_void, - destroy: extern fn(userdata: *mut c_void) -> *mut c_void, + // The order in which the functions are listed matters, as the offset in the + // struct defines what function the fuse driver uses. + // It should therefore not be altered! + init: Option, + destroy: Option, + lookup: Option, + forget: Option, + getattr: Option, + setattr: Option, + readlink: Option, + mknod: Option, + mkdir: Option, + unlink: Option, + rmdir: Option, + symlink: Option, + rename: Option, + link: Option, + open: Option, + read: Option, + write: Option, + flush: Option, + release: Option, + fsync: Option, + opendir: Option, + readdir: Option, + releasedir: Option, + fsyncdir: Option, + statfs: Option, + setxattr: Option, + getxattr: Option, + listxattr: Option, + removexattr: Option, + access: Option, + create: Option, + getlk: Option, + setlk: Option, + bmap: Option, + ioctl: Option, + poll: Option, + write_buf: Option, + retrieve_reply: Option, + forget_multi: Option, + flock: Option, + fallocate: Option, + readdirplus: Option, + copy_file_range: Option, } impl Session { @@ -72,16 +139,30 @@ impl Session { }; // Register the callback funcitons for the session - let oprs = Operations { - init: init, - destroy: destroy, - }; + let mut oprs = Operations::default(); + oprs.init = Some(init); + oprs.destroy = Some(destroy); + oprs.lookup = Some(lookup); + oprs.getattr = Some(getattr); + oprs.open = Some(open); + oprs.read = Some(read); + oprs.opendir = Some(opendir); + oprs.readdir = Some(readdir); + // By storing the decoder as userdata of the session, each request may + // access it. + let reader = BufReader::new(file); + let decoder = Decoder::new(reader, decoder_callback as fn(&Path) -> Result<(), Error>)?; + let session_decoder = Box::new(Mutex::new(decoder)); let session_ptr = unsafe { fuse_session_new( &args as *const FuseArgs, &oprs as *const Operations, std::mem::size_of::(), - std::ptr::null() + // Ownership of session_decoder is passed to the session here. + // It has to be reclaimed before dropping the session to free + // the decoder and close the underlying file. This is done inside + // the destroy callback function. + Box::into_raw(session_decoder) as ConstPtr )}; if session_ptr.is_null() { @@ -94,7 +175,6 @@ impl Session { Ok(Self { ptr: session_ptr, - archive: file, verbose: verbose, }) } @@ -147,13 +227,84 @@ impl Drop for Session { } } -/// Callback functions for fuse kernel driver. -extern "C" fn init(_userdata: *mut c_void) -> *mut c_void { - println!("Init callback"); - return std::ptr::null_mut(); +/// Creates a context providing an exclusive mutable reference to the decoder. +/// +/// Each callback function needing access to the decoder can easily get an +/// exclusive handle by running the code inside this context. +/// Responses with error code can easily be generated by returning with the +/// error code. +fn run_in_context, fn(&Path) -> Result<(), Error>>) -> Result<(), i32>>(req: Request, code: F) { + let ptr = unsafe { fuse_req_userdata(req) as *mut Mutex, fn(&Path) -> Result<(), Error>>> }; + let boxed_decoder = unsafe { Box::from_raw(ptr) }; + let result = boxed_decoder.lock() + .map(|mut decoder| code(&mut decoder)) + .unwrap_or(Err(libc::ENOENT)); + + if let Err(err) = result { + unsafe { let _res = fuse_reply_err(req, err); } + } + + // Release ownership of boxed decoder, do not drop it. + let _ = Box::into_raw(boxed_decoder); } -extern "C" fn destroy(_userdata: *mut c_void) -> *mut c_void { - println!("Destroy callback"); - return std::ptr::null_mut(); + +/// Callback functions for fuse kernel driver. +extern "C" fn init(_decoder: MutPtr) { + // Notting to do here for now } + +/// Cleanup the userdata created while creating the session, which is the decoder +extern "C" fn destroy(decoder: MutPtr) { + // Get ownership of the decoder and drop it when Box goes out of scope. + unsafe { Box::from_raw(decoder); } +} + +extern "C" fn lookup(req: Request, _parent: u64, _name: StrPtr) { + run_in_context(req, |decoder| { + // code goes here + + Err(libc::ENOENT) + }); +} + +extern "C" fn getattr(req: Request, _inode: u64, _fileinfo: MutPtr) { + run_in_context(req, |decoder| { + // code goes here + + Err(libc::ENOENT) + }); +} + +extern "C" fn open(req: Request, _inode: u64, _fileinfo: MutPtr) { + run_in_context(req, |decoder| { + // code goes here + + Err(libc::ENOENT) + }); +} + +extern "C" fn read(req: Request, _inode: u64, _size: size_t, _offset: c_int, _fileinfo: MutPtr) { + run_in_context(req, |decoder| { + // code goes here + + Err(libc::ENOENT) + }); +} + +extern "C" fn opendir(req: Request, _inode: u64, _fileinfo: MutPtr) { + run_in_context(req, |decoder| { + // code goes here + + Err(libc::ENOENT) + }); +} + +extern "C" fn readdir(req: Request, _inode: u64, _size: size_t, _offset: c_int, _fileinfo: MutPtr) { + run_in_context(req, |decoder| { + // code goes here + + Err(libc::ENOENT) + }); +} +