diff --git a/src/pxar/fuse.rs b/src/pxar/fuse.rs new file mode 100644 index 00000000..056d665a --- /dev/null +++ b/src/pxar/fuse.rs @@ -0,0 +1,159 @@ +//! Low level FUSE implementation for pxar. +//! +//! Allows to mount the archive as read-only filesystem to inspect its contents. + +use std::ffi::{OsStr, CString}; +use std::fs::File; +use std::os::unix::ffi::OsStrExt; +use std::path::Path; + +use libc; +use libc::{c_int, c_void, c_char, size_t}; +use failure::*; + +#[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_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); +} + +/// Command line arguments passed to fuse. +#[repr(C)] +#[derive(Debug)] +struct FuseArgs { + argc: c_int, + argv: *const *const c_char, + 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, + verbose: bool, +} + +/// `Operations` defines the callback function table of supported operations. +#[repr(C)] +struct Operations { + init: extern fn(userdata: *mut c_void) -> *mut c_void, + destroy: extern fn(userdata: *mut c_void) -> *mut c_void, +} + +impl Session { + /// Create a new low level fuse session. + /// + /// `Session` is created using the provided mount options and sets the + /// default signal handlers. + /// Options have to be provided as comma separated OsStr, e.g. + /// ("ro,default_permissions"). + pub fn new(archive_path: &Path, options: &OsStr, verbose: bool)-> Result { + let file = File::open(archive_path)?; + // First argument should be the executable name + let arguments = vec![ + CString::new("pxar-mount").unwrap(), + CString::new("-o").unwrap(), + CString::new(options.as_bytes())?, + ]; + + let arg_ptrs: Vec<_> = arguments.iter().map(|opt| opt.as_ptr()).collect(); + let args = FuseArgs { + argc: arg_ptrs.len() as i32, + argv: arg_ptrs.as_ptr(), + allocated: 0, + }; + + // Register the callback funcitons for the session + let oprs = Operations { + init: init, + destroy: destroy, + }; + + let session_ptr = unsafe { fuse_session_new( + &args as *const FuseArgs, + &oprs as *const Operations, + std::mem::size_of::(), + std::ptr::null() + )}; + + if session_ptr.is_null() { + bail!("error while creating new fuse session"); + } + + if unsafe { fuse_set_signal_handlers(session_ptr) } != 0 { + bail!("error while setting signal handlers"); + } + + Ok(Self { + ptr: session_ptr, + archive: file, + verbose: verbose, + }) + } + + /// Actually mount the filesystem for this session on the provided mountpoint + /// and daemonize process. + pub fn mount(&mut self, mountpoint: &Path) -> Result<(), Error> { + if self.verbose { + println!("Mounting archive to {:#?}", mountpoint); + } + let mountpoint = mountpoint.canonicalize()?; + let path_cstr = CString::new(mountpoint.as_os_str().as_bytes()) + .map_err(|err| format_err!("invalid mountpoint - {}", err))?; + if unsafe { fuse_session_mount(self.ptr, path_cstr.as_ptr()) } != 0 { + bail!("mounting on {:#?} failed", mountpoint); + } + + // Do not send process to background if verbose flag is set + if !self.verbose && unsafe { fuse_daemonize(0) } != 0 { + bail!("could not send process to background"); + } + + Ok(()) + } + + /// Execute session loop which handles requests from kernel. + pub fn run_loop(&mut self) -> Result<(), Error> { + if self.verbose { + println!("Executing fuse session loop"); + } + let result = unsafe { fuse_session_loop(self.ptr) }; + if result < 0 { + bail!("fuse session loop exited with - {}", result); + } + if result > 0 { + eprintln!("fuse session loop recieved signal - {}", result); + } + + Ok(()) + } +} + +impl Drop for Session { + fn drop(&mut self) { + unsafe { + fuse_session_unmount(self.ptr); + fuse_remove_signal_handlers(self.ptr); + fuse_session_destroy(self.ptr); + } + } +} + +/// 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(); +} + +extern "C" fn destroy(_userdata: *mut c_void) -> *mut c_void { + println!("Destroy callback"); + return std::ptr::null_mut(); +}