use btlib::{ blocktree::Blocktree, crypto::{tpm::TpmCredStore, CredStore}, }; use fuse_backend_rs::{api::server::Server, transport::{FuseSession, Error}}; use log::error; use std::{ ffi::{c_char, CString}, fs::{self, File}, io, os::fd::FromRawFd, os::{raw::c_int, unix::ffi::OsStrExt}, path::{Path, PathBuf}, str::FromStr, }; use tss_esapi::{ tcti_ldr::{TabrmdConfig, TctiNameConf}, Context, }; const DEFAULT_TABRMD: &str = "bus_type=session"; const MOUNT_OPTIONS: &str = "default_permissions"; const FSNAME: &str = "btfuse"; const FSTYPE: &str = "bt"; trait PathExt { fn try_create_dir(&self) -> io::Result<()>; } impl> PathExt for T { fn try_create_dir(&self) -> io::Result<()> { match fs::create_dir(self) { Ok(_) => Ok(()), Err(err) => match err.kind() { io::ErrorKind::AlreadyExists => Ok(()), _ => Err(err), }, } } } #[link(name = "fuse3")] extern "C" { /// Opens a channel to the kernel. fn fuse_open_channel(mountpoint: *const c_char, options: *const c_char) -> c_int; } /// Calls into libfuse3 to mount this file system at the given path. The file descriptor to use /// to communicate with the kernel is returned. fn mount_at>(mountpoint: P) -> File { let mountpoint = CString::new(mountpoint.as_ref().as_os_str().as_bytes()).unwrap(); let options = CString::new(MOUNT_OPTIONS).unwrap(); let raw_fd = unsafe { fuse_open_channel(mountpoint.as_ptr(), options.as_ptr()) }; unsafe { File::from_raw_fd(raw_fd) } } /// A fuse daemon process. struct FuseDaemon<'a> { /// The path of the directory to store all file system information in. path: &'a Path, /// The configuration string to use to connect to a Tabrmd instance. tabrmd_config: &'a str, } impl<'a> FuseDaemon<'a> { fn new(path: &'a Path, tabrmd_config: &'a str) -> FuseDaemon<'_> { FuseDaemon { path, tabrmd_config, } } fn start(&self) { self.path .try_create_dir() .expect("failed to create main directory"); let mnt_path = self.path.join("mnt"); mnt_path .try_create_dir() .expect("failed to create mount directory"); let empty = fs::read_dir(&mnt_path) .expect("failed to read mountdir") .next() .is_none(); let context = Context::new(TctiNameConf::Tabrmd( TabrmdConfig::from_str(self.tabrmd_config).expect("failed to parse Tabrmd config"), )) .expect("failed to connect to Tabrmd"); let cred_store = TpmCredStore::new(context, self.path.join("tpm_state")) .expect("failed to create TpmCredStore"); let node_creds = cred_store .node_creds() .expect("failed to retrieve node creds"); let bt_path = self.path.join("bt"); bt_path .try_create_dir() .expect("failed to create blocktree directory"); let fs = if empty { Blocktree::new_empty(bt_path, 0, node_creds) } else { Blocktree::new_existing(bt_path, node_creds) } .expect("failed to create blocktree"); let server = Server::new(fs); let mut session = FuseSession::new(&mnt_path, FSNAME, FSTYPE, false) .expect("failed to create FUSE session"); session.set_fuse_file(mount_at(&mnt_path)); let mut channel = session .new_channel() .expect("failed to create FUSE channel"); loop { match channel.get_request() { Ok(Some((reader, writer))) => { if let Err(err) = server.handle_message(reader, writer.into(), None, None) { error!("error while handling FUSE message: {err}"); } } Ok(None) => break, Err(err) => { match err { // Occurs when the file system is unmounted. Error::SessionFailure(_) => break, _ => error!("{err}"), } } } } } } fn main() { env_logger::init(); let main_dir = std::env::args().nth(1).expect("no mount point given"); let main_dir = PathBuf::from_str(main_dir.as_str()).expect("failed to convert mount point to PathBuf"); let tabrmd_string = std::env::var("BT_TABRMD").ok(); let tabrmd_str = tabrmd_string .as_ref() .map_or(DEFAULT_TABRMD, |s| s.as_str()); let daemon = FuseDaemon::new(&main_dir, tabrmd_str); daemon.start(); } #[cfg(test)] mod test { use btlib::test_helpers; use tempdir::TempDir; use super::*; /// Starts the `FuseDaemon` in a new temporary directory prefixed by "btfuse". #[test] fn server_start() { std::env::set_var("RUST_LOG", "info"); env_logger::init(); let temp_dir = TempDir::new("btfuse").expect("failed to create TempDir"); let swtpm = test_helpers::SwtpmHarness::new().expect("failed to start swtpm"); let daemon = FuseDaemon::new(temp_dir.path(), swtpm.tabrmd_config()); daemon.start(); } }