|
@@ -1,6 +1,9 @@
|
|
|
use btlib::{
|
|
|
blocktree::{Blocktree, ModeAuthorizer},
|
|
|
- crypto::{tpm::TpmCredStore, CredStore},
|
|
|
+ crypto::{
|
|
|
+ tpm::{TpmCredStore, TpmCreds},
|
|
|
+ CredStore,
|
|
|
+ },
|
|
|
};
|
|
|
use fuse_backend_rs::{
|
|
|
api::server::Server,
|
|
@@ -15,6 +18,7 @@ use std::{
|
|
|
os::{raw::c_int, unix::ffi::OsStrExt},
|
|
|
path::{Path, PathBuf},
|
|
|
str::FromStr,
|
|
|
+ sync::mpsc::Sender,
|
|
|
};
|
|
|
use tss_esapi::{
|
|
|
tcti_ldr::{TabrmdConfig, TctiNameConf},
|
|
@@ -50,8 +54,8 @@ extern "C" {
|
|
|
|
|
|
/// 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<P: AsRef<Path>>(mountpoint: P) -> File {
|
|
|
- let mountpoint = CString::new(mountpoint.as_ref().as_os_str().as_bytes()).unwrap();
|
|
|
+fn mount_at<P: AsRef<Path>>(mnt_point: P) -> File {
|
|
|
+ let mountpoint = CString::new(mnt_point.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) }
|
|
@@ -63,7 +67,8 @@ struct FuseDaemon<'a> {
|
|
|
path: &'a Path,
|
|
|
/// The configuration string to use to connect to a Tabrmd instance.
|
|
|
tabrmd_config: &'a str,
|
|
|
- tpm_state_path: PathBuf,
|
|
|
+ /// An optional [Sender] which is called when this daemon has finished starting up.
|
|
|
+ started_signal: Option<Sender<()>>,
|
|
|
}
|
|
|
|
|
|
impl<'a> FuseDaemon<'a> {
|
|
@@ -71,18 +76,19 @@ impl<'a> FuseDaemon<'a> {
|
|
|
FuseDaemon {
|
|
|
path,
|
|
|
tabrmd_config,
|
|
|
- tpm_state_path: path.join("tpm_state"),
|
|
|
+ started_signal: None,
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- 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");
|
|
|
+ fn tpm_state_path<P: AsRef<Path>>(path: P) -> PathBuf {
|
|
|
+ path.as_ref().join("tpm_state")
|
|
|
+ }
|
|
|
+
|
|
|
+ fn mnt_path<P: AsRef<Path>>(path: P) -> PathBuf {
|
|
|
+ path.as_ref().join("mnt")
|
|
|
+ }
|
|
|
+
|
|
|
+ fn server<P: AsRef<Path>>(&self, mnt_path: P) -> Server<Blocktree<TpmCreds, ModeAuthorizer>> {
|
|
|
let empty = fs::read_dir(&mnt_path)
|
|
|
.expect("failed to read mountdir")
|
|
|
.next()
|
|
@@ -91,7 +97,7 @@ impl<'a> FuseDaemon<'a> {
|
|
|
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.tpm_state_path.to_owned())
|
|
|
+ let cred_store = TpmCredStore::new(context, Self::tpm_state_path(self.path))
|
|
|
.expect("failed to create TpmCredStore");
|
|
|
let node_creds = cred_store
|
|
|
.node_creds()
|
|
@@ -106,13 +112,35 @@ impl<'a> FuseDaemon<'a> {
|
|
|
Blocktree::new_existing(bt_path, node_creds, ModeAuthorizer {})
|
|
|
}
|
|
|
.expect("failed to create blocktree");
|
|
|
- let server = Server::new(fs);
|
|
|
- let mut session = FuseSession::new(&mnt_path, FSNAME, FSTYPE, false)
|
|
|
+
|
|
|
+ Server::new(fs)
|
|
|
+ }
|
|
|
+
|
|
|
+ fn fuse_session<P: AsRef<Path>>(&self, mnt_path: P) -> FuseSession {
|
|
|
+ self.path
|
|
|
+ .try_create_dir()
|
|
|
+ .expect("failed to create main directory");
|
|
|
+ let mut session = FuseSession::new(mnt_path.as_ref(), FSNAME, FSTYPE, false)
|
|
|
.expect("failed to create FUSE session");
|
|
|
- session.set_fuse_file(mount_at(&mnt_path));
|
|
|
+ session.set_fuse_file(mount_at(mnt_path));
|
|
|
+
|
|
|
+ session
|
|
|
+ }
|
|
|
+
|
|
|
+ fn start(&self) {
|
|
|
+ let mnt_path = Self::mnt_path(self.path);
|
|
|
+ mnt_path
|
|
|
+ .try_create_dir()
|
|
|
+ .expect("failed to create mount directory");
|
|
|
+ let server = self.server(&mnt_path);
|
|
|
+ let session = self.fuse_session(&mnt_path);
|
|
|
+ drop(mnt_path);
|
|
|
let mut channel = session
|
|
|
.new_channel()
|
|
|
.expect("failed to create FUSE channel");
|
|
|
+ if let Some(tx) = self.started_signal.as_ref() {
|
|
|
+ tx.send(()).expect("failed to send started signal");
|
|
|
+ }
|
|
|
|
|
|
loop {
|
|
|
match channel.get_request() {
|
|
@@ -149,7 +177,13 @@ fn main() {
|
|
|
|
|
|
#[cfg(test)]
|
|
|
mod test {
|
|
|
- use std::time::Duration;
|
|
|
+ use std::{
|
|
|
+ ffi::{OsStr, OsString},
|
|
|
+ fs::{read, read_dir, remove_file, write, ReadDir},
|
|
|
+ sync::mpsc::{channel, Receiver},
|
|
|
+ thread::JoinHandle,
|
|
|
+ time::Duration,
|
|
|
+ };
|
|
|
|
|
|
use btlib::{crypto::Creds, log::BuilderExt, Epoch, Principaled};
|
|
|
use swtpm_harness::SwtpmHarness;
|
|
@@ -157,32 +191,165 @@ mod test {
|
|
|
|
|
|
use super::*;
|
|
|
|
|
|
- /// Creates a new file system and mounts it at `/tmp/btfuse.<random>/mnt`.
|
|
|
+ /// Unmounts the file system at the given path.
|
|
|
+ fn unmount<P: AsRef<Path>>(mnt_path: P) {
|
|
|
+ const PROG: &str = "fusermount";
|
|
|
+ let mnt_path = mnt_path
|
|
|
+ .as_ref()
|
|
|
+ .as_os_str()
|
|
|
+ .to_str()
|
|
|
+ .expect("failed to convert mnt_path to `str`");
|
|
|
+ let code = std::process::Command::new(PROG)
|
|
|
+ .args(["-u", mnt_path])
|
|
|
+ .status()
|
|
|
+ .expect("waiting for exit status failed")
|
|
|
+ .code()
|
|
|
+ .expect("code returned None");
|
|
|
+ if code != 0 {
|
|
|
+ panic!("{PROG} exited with a non-zero status: {code}");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ fn file_names(read_dir: ReadDir) -> impl Iterator<Item = OsString> {
|
|
|
+ read_dir.map(|entry| entry.unwrap().file_name())
|
|
|
+ }
|
|
|
+
|
|
|
+ struct TestCase {
|
|
|
+ temp_path_rx: Receiver<PathBuf>,
|
|
|
+ mnt_path: Option<PathBuf>,
|
|
|
+ handle: Option<JoinHandle<()>>,
|
|
|
+ }
|
|
|
+
|
|
|
+ impl TestCase {
|
|
|
+ const ROOT_PASSWD: &str = "Gnurlingwheel";
|
|
|
+
|
|
|
+ fn new() -> TestCase {
|
|
|
+ let (tx, rx) = channel();
|
|
|
+ let (started_tx, started_rx) = channel();
|
|
|
+ let handle = std::thread::spawn(move || {
|
|
|
+ let dir = TempDir::new("btfuse").expect("failed to create TempDir");
|
|
|
+ tx.send(dir.path().to_owned()).expect("send failed");
|
|
|
+ let swtpm = SwtpmHarness::new().expect("failed to start swtpm");
|
|
|
+ {
|
|
|
+ let context = swtpm.context().expect("failed to create TPM context");
|
|
|
+ let cred_store =
|
|
|
+ TpmCredStore::new(context, FuseDaemon::tpm_state_path(dir.path()))
|
|
|
+ .expect("failed to create TpmCredStore");
|
|
|
+ let root_creds = cred_store
|
|
|
+ .gen_root_creds(Self::ROOT_PASSWD)
|
|
|
+ .expect("failed to gen root creds");
|
|
|
+ let mut node_creds = cred_store.node_creds().expect("failed to get node creds");
|
|
|
+ let expires = Epoch::now() + Duration::from_secs(3600);
|
|
|
+ let writecap = root_creds
|
|
|
+ .issue_writecap(node_creds.principal(), vec![], expires)
|
|
|
+ .expect("failed to issue writecap to node creds");
|
|
|
+ cred_store
|
|
|
+ .assign_node_writecap(&mut node_creds, writecap)
|
|
|
+ .expect("failed to assign writecap");
|
|
|
+ }
|
|
|
+ let mut daemon = FuseDaemon::new(dir.path(), swtpm.tabrmd_config());
|
|
|
+ daemon.started_signal = Some(started_tx);
|
|
|
+ daemon.start()
|
|
|
+ });
|
|
|
+ started_rx
|
|
|
+ .recv_timeout(Duration::from_secs(1))
|
|
|
+ .expect("failed to received started signal from `FuseDaemon`");
|
|
|
+ println!("sent started signal");
|
|
|
+ TestCase {
|
|
|
+ temp_path_rx: rx,
|
|
|
+ mnt_path: None,
|
|
|
+ handle: Some(handle),
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ fn mnt_path(&mut self) -> &PathBuf {
|
|
|
+ self.mnt_path.get_or_insert_with(|| {
|
|
|
+ let temp_path = self
|
|
|
+ .temp_path_rx
|
|
|
+ .recv_timeout(Duration::from_secs(1))
|
|
|
+ .expect("receive failed");
|
|
|
+ FuseDaemon::mnt_path(temp_path)
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ fn wait(&mut self) {
|
|
|
+ if let Some(handle) = self.handle.take() {
|
|
|
+ handle.join().expect("join failed");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ fn unmount_and_wait(&mut self) {
|
|
|
+ let mnt_path = self.mnt_path();
|
|
|
+ unmount(mnt_path);
|
|
|
+ self.wait();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ impl Drop for TestCase {
|
|
|
+ fn drop(&mut self) {
|
|
|
+ self.unmount_and_wait();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Creates a new file system and mounts it at `/tmp/btfuse.<random>/mnt` so it can be
|
|
|
+ /// tested manually.
|
|
|
//#[test]
|
|
|
#[allow(dead_code)]
|
|
|
- fn server_start() {
|
|
|
- const ROOT_PASSWD: &str = "Gnurlingwheel";
|
|
|
+ fn manual_test() {
|
|
|
std::env::set_var("RUST_LOG", "debug");
|
|
|
env_logger::Builder::from_default_env().btformat().init();
|
|
|
- let temp_dir = TempDir::new("btfuse").expect("failed to create TempDir");
|
|
|
- let swtpm = SwtpmHarness::new().expect("failed to start swtpm");
|
|
|
- let daemon = FuseDaemon::new(temp_dir.path(), swtpm.tabrmd_config());
|
|
|
- {
|
|
|
- let context = swtpm.context().expect("failed to create TPM context");
|
|
|
- let cred_store = TpmCredStore::new(context, daemon.tpm_state_path.to_owned())
|
|
|
- .expect("failed to create TpmCredStore");
|
|
|
- let root_creds = cred_store
|
|
|
- .gen_root_creds(ROOT_PASSWD)
|
|
|
- .expect("failed to gen root creds");
|
|
|
- let mut node_creds = cred_store.node_creds().expect("failed to get node creds");
|
|
|
- let expires = Epoch::now() + Duration::from_secs(3600);
|
|
|
- let writecap = root_creds
|
|
|
- .issue_writecap(node_creds.principal(), vec![], expires)
|
|
|
- .expect("failed to issue writecap to node creds");
|
|
|
- cred_store
|
|
|
- .assign_node_writecap(&mut node_creds, writecap)
|
|
|
- .expect("failed to assign writecap");
|
|
|
- }
|
|
|
- daemon.start();
|
|
|
+ let mut case = TestCase::new();
|
|
|
+ case.wait();
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Tests if the file system can be mount then unmounted successfully.
|
|
|
+ #[test]
|
|
|
+ fn mount_then_unmount() {
|
|
|
+ let _ = TestCase::new();
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn write_read() {
|
|
|
+ const EXPECTED: &[u8] =
|
|
|
+ b"The paths to failure are uncountable, yet to success there is but one.";
|
|
|
+ let mut case = TestCase::new();
|
|
|
+ let file_path = case.mnt_path().join("file");
|
|
|
+
|
|
|
+ write(&file_path, EXPECTED).expect("write failed");
|
|
|
+
|
|
|
+ let actual = read(&file_path).expect("read failed");
|
|
|
+ assert_eq!(EXPECTED, actual);
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn create_file_then_readdir() {
|
|
|
+ const DATA: &[u8] = b"Au revoir Shoshanna!";
|
|
|
+ let file_name = OsStr::new("landa_dialog.txt");
|
|
|
+ let expected = [file_name];
|
|
|
+ let mut case = TestCase::new();
|
|
|
+ let mnt_path = case.mnt_path();
|
|
|
+ let file_path = mnt_path.join(file_name);
|
|
|
+
|
|
|
+ write(&file_path, DATA).expect("write failed");
|
|
|
+
|
|
|
+ let first = file_names(read_dir(&mnt_path).expect("read_dir failed"));
|
|
|
+ assert!(first.eq(expected));
|
|
|
+ let second = file_names(read_dir(&mnt_path).expect("read_dir failed"));
|
|
|
+ assert!(second.eq(expected));
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn create_then_delete_file() {
|
|
|
+ const DATA: &[u8] = b"The universe is hostile, so impersonal. Devour to survive";
|
|
|
+ let file_name = OsStr::new("tool_lyrics.txt");
|
|
|
+ let mut case = TestCase::new();
|
|
|
+ let mnt_path = case.mnt_path();
|
|
|
+ let file_path = mnt_path.join(file_name);
|
|
|
+ write(&file_path, DATA).expect("write failed");
|
|
|
+
|
|
|
+ remove_file(&file_path).expect("remove_file failed");
|
|
|
+
|
|
|
+ let expected: [&OsStr; 0] = [];
|
|
|
+ assert!(file_names(read_dir(&mnt_path).expect("read_dir failed")).eq(expected))
|
|
|
}
|
|
|
}
|