Преглед изворни кода

* Implemented Blocktree::unlink.
* Created a framework for testing the mounted BT in btfuse.

Matthew Carr пре 2 година
родитељ
комит
7dbb358c1a

+ 4 - 4
Cargo.lock

@@ -1159,18 +1159,18 @@ dependencies = [
 
 [[package]]
 name = "thiserror"
-version = "1.0.35"
+version = "1.0.37"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c53f98874615aea268107765aa1ed8f6116782501d18e53d08b471733bea6c85"
+checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.35"
+version = "1.0.37"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8b463991b4eab2d801e724172285ec4195c650e8ec79b149e6c2a8e6dd3f783"
+checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb"
 dependencies = [
  "proc-macro2",
  "quote",

+ 208 - 41
crates/btfuse/src/main.rs

@@ -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))
     }
 }

+ 126 - 27
crates/btlib/src/blocktree.rs

@@ -259,6 +259,7 @@ mod private {
         next_handle: Handle,
         unclaimed_handles: Vec<Handle>,
         lookup_count: u64,
+        delete: bool,
     }
 
     impl InodeTableValue {
@@ -275,6 +276,7 @@ mod private {
                 next_handle: FIRST_HANDLE + 1,
                 lookup_count: 1,
                 unclaimed_handles: vec![FIRST_HANDLE],
+                delete: false,
             }
         }
 
@@ -356,16 +358,16 @@ mod private {
             }
         }
 
+        /// Increments `lookup_count` by 1 and returns its current value.
         fn incr_lookup_count(&mut self) -> u64 {
-            let prev = self.lookup_count;
             self.lookup_count += 1;
-            prev
+            self.lookup_count
         }
 
+        /// Decrements `lookup_count` by `count` and returns its current value.
         fn decr_lookup_count(&mut self, count: u64) -> u64 {
-            let prev = self.lookup_count;
             self.lookup_count -= count;
-            prev
+            self.lookup_count
         }
     }
 
@@ -397,7 +399,7 @@ mod private {
         authorizer: A,
     }
 
-    impl<C: Creds + Clone + 'static, A> Blocktree<C, A> {
+    impl<C: Creds + 'static, A> Blocktree<C, A> {
         /// Creates a new empty blocktree at the given path.
         pub fn new_empty(
             btdir: PathBuf,
@@ -562,7 +564,9 @@ mod private {
 
         /// Returns the [Err] variant containing the [io::Error] corresponding to [libc::ENOSYS].
         fn not_supported<T>() -> io::Result<T> {
-            Err(io::Error::from_raw_os_error(libc::ENOSYS))
+            let err = io::Error::from_raw_os_error(libc::ENOSYS);
+            debug!("{err}");
+            Err(err)
         }
 
         fn access_entry<T, F: FnOnce(InodeTableEntry) -> io::Result<T>>(
@@ -722,7 +726,7 @@ mod private {
 
         fn inode_forget(&self, inode: Inode, count: u64) -> io::Result<()> {
             let mut inodes = self.inodes.write().err_to_string()?;
-            let prev = {
+            let lookup_count = {
                 let inode_lock = match inodes.get_mut(&inode) {
                     Some(inode_lock) => inode_lock,
                     None => {
@@ -733,8 +737,17 @@ mod private {
                 let mut value = inode_lock.write().err_to_string()?;
                 value.decr_lookup_count(count)
             };
-            if 1 == prev {
-                inodes.remove(&inode);
+            if 0 == lookup_count {
+                let delete = inodes
+                    .remove(&inode)
+                    .unwrap()
+                    .into_inner()
+                    .err_to_string()?
+                    .delete;
+                if delete {
+                    let path = Self::block_path(&self.path, inode);
+                    std::fs::remove_file(path)?;
+                }
             }
             Ok(())
         }
@@ -948,7 +961,6 @@ mod private {
 
             let (handle, attr) =
                 self.open_then_take_handle(inode, block_path, |handle, value| {
-                    value.incr_lookup_count();
                     let block = value.block_mut(handle)?;
                     Ok(block.mut_meta_body().access_secrets(|secrets| {
                         secrets.inode = inode;
@@ -977,14 +989,14 @@ mod private {
 
         fn write(
             &self,
-            ctx: &Context,
+            _ctx: &Context,
             inode: Self::Inode,
             handle: Self::Handle,
             r: &mut dyn fuse_backend_rs::api::filesystem::ZeroCopyReader,
             size: u32,
-            offset: u64,
-            lock_owner: Option<u64>,
-            delayed_write: bool,
+            _offset: u64,
+            _lock_owner: Option<u64>,
+            _delayed_write: bool,
             // `flags` and `fuse_flags` are the arguments that were passed to `open` when this
             // handle was returned.
             flags: u32,
@@ -1034,10 +1046,10 @@ mod private {
 
         fn flush(
             &self,
-            ctx: &Context,
+            _ctx: &Context,
             inode: Self::Inode,
             handle: Self::Handle,
-            lock_owner: u64,
+            _lock_owner: u64,
         ) -> io::Result<()> {
             debug!("Blocktree::flush called for inode {inode}");
             self.access_block_mut(inode, handle, |block| block.flush())
@@ -1045,16 +1057,22 @@ mod private {
 
         fn read(
             &self,
-            ctx: &Context,
+            _ctx: &Context,
             inode: Self::Inode,
             handle: Self::Handle,
             w: &mut dyn fuse_backend_rs::api::filesystem::ZeroCopyWriter,
             size: u32,
-            offset: u64,
-            lock_owner: Option<u64>,
+            _offset: u64,
+            _lock_owner: Option<u64>,
             flags: u32,
         ) -> io::Result<usize> {
             debug!("Blocktree::read called on inode {inode}");
+            if (flags as libc::c_int & libc::O_WRONLY) != 0 {
+                return Err(io::Error::new(
+                    io::ErrorKind::PermissionDenied,
+                    "file is write only",
+                ));
+            }
             let mut size: usize = size.try_into().box_err()?;
             self.access_block_mut(inode, handle, |block| {
                 let mut buf = [0u8; crate::SECTOR_SZ_DEFAULT];
@@ -1105,9 +1123,8 @@ mod private {
             let mut size: usize = size.try_into().box_err()?;
             self.access_value(inode, |value| {
                 let dir = value
-                    .handle_values
-                    .get(&handle)
-                    .ok_or(Error::InvalidHandle { handle, inode })?
+                    .value(handle)
+                    .map_err(|_| Error::InvalidHandle { handle, inode })?
                     .directory()?;
                 let mut index: u64 = 0;
                 for (name, entry) in dir.entries() {
@@ -1166,6 +1183,45 @@ mod private {
             self.access_block_mut(inode, handle, |block| block.seek(seek_from))
         }
 
+        fn unlink(&self, ctx: &Context, parent: Self::Inode, name: &CStr) -> io::Result<()> {
+            debug!("Blocktree::unlink called on parent {parent}");
+            let name = name.to_str().box_err()?;
+            let (block_path, inode) = self.borrow_block(parent, |block| {
+                let ctx = AuthzContext::new(ctx, block.meta());
+                self.authorizer.can_write(&ctx)?;
+
+                block.seek(SeekFrom::Start(0))?;
+                let mut dir: Directory = read_from(block)?;
+                let inode = match dir.entries.remove(name) {
+                    None => return Err(io::Error::from_raw_os_error(libc::ENOENT)),
+                    Some(entry) => entry.inode().ok_or_else(|| {
+                        io::Error::new(
+                            io::ErrorKind::InvalidInput,
+                            format!("name {name} does not refer to a file or directory"),
+                        )
+                    })?,
+                };
+                block.seek(SeekFrom::Start(0))?;
+                write_to(&dir, block)?;
+
+                let mut block_path = block.meta_body().path.clone();
+                block_path.push_component(name.to_owned());
+                Ok((block_path, inode))
+            })?;
+            self.open_value(inode, block_path, |value| {
+                // We mark the block for deletion if `nlink` drops to zero.
+                value.delete = value.try_borrow_block(|block| {
+                    let nlink = block.mut_meta_body().access_secrets(|secrets| {
+                        secrets.nlink -= 1;
+                        Ok(secrets.nlink)
+                    })?;
+                    block.flush_meta()?;
+                    Ok(0 == nlink)
+                })?;
+                Ok(())
+            })
+        }
+
         //////////////////////////////////
         // METHODS WHICH ARE NOT SUPPORTED
         //////////////////////////////////
@@ -1437,11 +1493,6 @@ mod private {
             debug!("Blocktree::symlink called");
             Self::not_supported()
         }
-
-        fn unlink(&self, _ctx: &Context, parent: Self::Inode, _name: &CStr) -> io::Result<()> {
-            debug!("Blocktree::unlink called on parent {parent}");
-            Self::not_supported()
-        }
     }
 }
 
@@ -1869,4 +1920,52 @@ mod tests {
         let err = result.err().unwrap();
         assert_eq!(io::ErrorKind::PermissionDenied, err.kind());
     }
+
+    /// Tests that a call to `read` fails when a file is opened write only.
+    #[test]
+    fn open_write_only_read_is_error() {
+        let name = CString::new("books.ods").unwrap();
+        let case = BtTestCase::new_empty();
+        let bt = &case.bt;
+        let ctx = case.context();
+
+        let (entry, handle, ..) = bt
+            .create(
+                &ctx,
+                SpecInodes::RootDir.into(),
+                name.as_c_str(),
+                CreateIn {
+                    mode: libc::S_IFREG | 0o644,
+                    umask: 0,
+                    flags: 0,
+                    fuse_flags: 0,
+                },
+            )
+            .expect("failed to create file");
+        let inode = entry.inode;
+        let handle = handle.unwrap();
+
+        bt.release(&ctx, inode, 0, handle, false, false, None)
+            .expect("release failed");
+
+        let flags = libc::O_WRONLY as u32;
+        let (handle, ..) = bt.open(&ctx, inode, flags, 0).expect("open failed");
+        let handle = handle.unwrap();
+
+        const LEN: usize = 32;
+        let mut reader = BtCursor::new([1u8; LEN]);
+        let result = bt.read(
+            &ctx,
+            inode,
+            handle,
+            &mut reader,
+            LEN.try_into().unwrap(),
+            0,
+            None,
+            flags,
+        );
+
+        let err = result.err().unwrap();
+        assert_eq!(io::ErrorKind::PermissionDenied, err.kind());
+    }
 }

+ 7 - 1
crates/btlib/src/crypto/merkle_stream.rs

@@ -7,7 +7,7 @@ mod private {
     use crate::{
         crypto::{Error, HashKind, Result},
         trailered::Trailered,
-        BlockMeta, BoxInIoErr, Decompose, MetaAccess, Sectored, TryCompose, WriteInteg,
+        Block, BlockMeta, BoxInIoErr, Decompose, MetaAccess, Sectored, TryCompose, WriteInteg,
         SECTOR_SZ_DEFAULT,
     };
     use serde::{Deserialize, Serialize};
@@ -658,6 +658,12 @@ mod private {
     }
 
     impl<T: MetaAccess> MetaAccess for MerkleStream<T> {}
+
+    impl<T: Block + WriteInteg> Block for MerkleStream<T> {
+        fn flush_meta(&mut self) -> crate::Result<()> {
+            self.trailered.flush_meta()
+        }
+    }
 }
 
 #[cfg(test)]

+ 59 - 0
crates/btlib/src/crypto/mod.rs

@@ -1659,6 +1659,12 @@ pub trait Encrypter {
     fn encrypt(&self, slice: &[u8]) -> Result<Vec<u8>>;
 }
 
+impl<T: Encrypter> Encrypter for &T {
+    fn encrypt(&self, slice: &[u8]) -> Result<Vec<u8>> {
+        (*self).encrypt(slice)
+    }
+}
+
 pub trait EncrypterExt: Encrypter {
     /// Serializes the given value into a new vector, then encrypts it and returns the resulting
     /// ciphertext.
@@ -1675,6 +1681,12 @@ pub trait Decrypter {
     fn decrypt(&self, slice: &[u8]) -> Result<Vec<u8>>;
 }
 
+impl<T: Decrypter> Decrypter for &T {
+    fn decrypt(&self, slice: &[u8]) -> Result<Vec<u8>> {
+        (*self).decrypt(slice)
+    }
+}
+
 pub trait DecrypterExt: Decrypter {
     fn ser_decrypt<T: DeserializeOwned>(&self, ct: &Ciphertext<T>) -> Result<T> {
         let pt = self.decrypt(ct.data.as_slice())?;
@@ -1788,6 +1800,18 @@ pub trait Signer {
     }
 }
 
+impl<T: Signer> Signer for &T {
+    type Op<'s> = T::Op<'s> where Self: 's;
+
+    fn init_sign(&self) -> Result<Self::Op<'_>> {
+        (*self).init_sign()
+    }
+
+    fn sign<'a, I: Iterator<Item = &'a [u8]>>(&self, parts: I) -> Result<Signature> {
+        (*self).sign(parts)
+    }
+}
+
 pub trait VerifyOp: Sized {
     type Arg;
 
@@ -1894,12 +1918,30 @@ pub trait Verifier {
     }
 }
 
+impl<T: Verifier> Verifier for &T {
+    type Op<'v> = T::Op<'v> where Self: 'v;
+
+    fn init_verify(&self) -> Result<Self::Op<'_>> {
+        (*self).init_verify()
+    }
+
+    fn verify<'a, I: Iterator<Item = &'a [u8]>>(&self, parts: I, signature: &[u8]) -> Result<()> {
+        (*self).verify(parts, signature)
+    }
+}
+
 /// Trait for types which can be used as public credentials.
 pub trait CredsPub: Verifier + Encrypter + Principaled {
     /// Returns a reference to the public signing key which can be used to verify signatures.
     fn public_sign(&self) -> &AsymKey<Public, Sign>;
 }
 
+impl<T: CredsPub> CredsPub for &T {
+    fn public_sign(&self) -> &AsymKey<Public, Sign> {
+        (*self).public_sign()
+    }
+}
+
 /// Trait for types which contain private credentials.
 pub trait CredsPriv: Decrypter + Signer {
     /// Returns a reference to the writecap associated with these credentials, if one has been
@@ -1907,6 +1949,12 @@ pub trait CredsPriv: Decrypter + Signer {
     fn writecap(&self) -> Option<&Writecap>;
 }
 
+impl<T: CredsPriv> CredsPriv for &T {
+    fn writecap(&self) -> Option<&Writecap> {
+        (*self).writecap()
+    }
+}
+
 /// Trait for types which contain both public and private credentials.
 pub trait Creds: CredsPriv + CredsPub + Clone {
     fn issue_writecap(
@@ -1931,6 +1979,17 @@ pub trait Creds: CredsPriv + CredsPub + Clone {
     }
 }
 
+impl<C: Creds> Creds for &C {
+    fn issue_writecap(
+        &self,
+        issued_to: Principal,
+        path_components: Vec<String>,
+        expires: Epoch,
+    ) -> Result<Writecap> {
+        (*self).issue_writecap(issued_to, path_components, expires)
+    }
+}
+
 /// A trait for types which store credentials.
 pub trait CredStore {
     type CredHandle: Creds;

+ 5 - 1
crates/btlib/src/crypto/secret_stream.rs

@@ -178,7 +178,11 @@ mod private {
 
     impl<T: MetaAccess> MetaAccess for SecretStream<T> {}
 
-    impl<T: Read + Write + Seek + MetaAccess> Block for SecretStream<T> {}
+    impl<T: Block> Block for SecretStream<T> {
+        fn flush_meta(&mut self) -> crate::Result<()> {
+            self.inner.flush_meta()
+        }
+    }
 }
 
 #[cfg(test)]

+ 27 - 12
crates/btlib/src/lib.rs

@@ -1,6 +1,3 @@
-#![allow(incomplete_features)]
-#![feature(trait_upcasting)]
-
 pub use block_path::{BlockPath, BlockPathError};
 pub mod blocktree;
 /// Code which enables cryptographic operations.
@@ -170,7 +167,10 @@ impl<T, E: Display> StrInIoErr<T> for std::result::Result<T, E> {
 const SECTOR_SZ_DEFAULT: usize = 4096;
 
 /// Trait for types which provide read and write access to blocks.
-pub trait Block: Read + Write + Seek + MetaAccess {}
+pub trait Block: Read + Write + Seek + MetaAccess {
+    /// Flushes metadata to persistent storage.
+    fn flush_meta(&mut self) -> Result<()>;
+}
 
 // A trait for streams which only allow reads and writes in fixed sized units called sectors.
 pub trait Sectored {
@@ -285,7 +285,7 @@ trait ReadExt: Read {
 impl<T: Read> ReadExt for T {}
 
 trait HashExt: Hashable {
-    /// Returns the hash produced by the `DefaultHasher`.
+    /// Returns the hash produced by [DefaultHasher].
     fn default_hash(&self) -> u64 {
         let mut hasher = DefaultHasher::new();
         self.hash(&mut hasher);
@@ -583,6 +583,16 @@ impl<T: Read + Seek, C: Creds> BlockStream<T, C> {
     }
 }
 
+impl<T: Write + Seek, C: Signer> BlockStream<T, C> {
+    fn sign_flush_meta(&mut self) -> io::Result<()> {
+        self.meta_body_buf.clear();
+        self.meta.sig = self
+            .creds
+            .ser_sign_into(&self.meta.body, &mut self.meta_body_buf)?;
+        self.trailered.flush(&self.meta)
+    }
+}
+
 impl<T: Write + Seek, C: Signer + Principaled + Decrypter> Write for BlockStream<T, C> {
     fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
         self.trailered.write(buf)
@@ -601,12 +611,7 @@ impl<T: Write + Seek, C: Signer + Principaled + Decrypter> WriteInteg for BlockS
         let meta_body = &mut self.meta.body;
         let integ = meta_body.integrity.get_or_insert_with(VarHash::default);
         integ.as_mut().copy_from_slice(integrity);
-        self.meta_body_buf.clear();
-        self.meta.sig = self
-            .creds
-            .ser_sign_into(&meta_body, &mut self.meta_body_buf)?;
-        self.trailered.flush(&self.meta)?;
-        Ok(())
+        self.sign_flush_meta()
     }
 }
 
@@ -638,7 +643,11 @@ impl<T, C> AsMut<BlockMeta> for BlockStream<T, C> {
 
 impl<T, C> MetaAccess for BlockStream<T, C> {}
 
-impl<T: Read + Write + Seek + MetaAccess, C: Creds> Block for BlockStream<T, C> {}
+impl<T: Read + Write + Seek, C: Creds> Block for BlockStream<T, C> {
+    fn flush_meta(&mut self) -> Result<()> {
+        self.sign_flush_meta().map_err(|err| err.into())
+    }
+}
 
 pub struct BlockOpenOptions<T, C> {
     inner: T,
@@ -1006,6 +1015,12 @@ pub trait Principaled {
     }
 }
 
+impl<T: Principaled> Principaled for &T {
+    fn principal_of_kind(&self, kind: HashKind) -> Principal {
+        (*self).principal_of_kind(kind)
+    }
+}
+
 /// An instant in time represented by the number of seconds since January 1st 1970, 00:00:00 UTC.
 #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
 pub struct Epoch(u64);

+ 5 - 1
crates/btlib/src/sectored_buf.rs

@@ -327,7 +327,11 @@ mod private {
 
     impl<T: MetaAccess> MetaAccess for SectoredBuf<T> {}
 
-    impl<T: Read + Write + Seek + MetaAccess> Block for SectoredBuf<T> {}
+    impl<T: Block> Block for SectoredBuf<T> {
+        fn flush_meta(&mut self) -> Result<()> {
+            self.inner.flush_meta()
+        }
+    }
 }
 
 #[cfg(test)]

+ 7 - 1
crates/btlib/src/trailered.rs

@@ -9,7 +9,7 @@ mod private {
     use btserde::{read_from, write_to};
     use serde::{de::DeserializeOwned, Serialize};
 
-    use crate::{BlockMeta, BoxInIoErr, Decompose, MetaAccess, Result, WriteInteg};
+    use crate::{Block, BlockMeta, BoxInIoErr, Decompose, MetaAccess, Result, WriteInteg};
 
     /// A struct which wraps a stream and which writes a trailing data structure to it when flushed.
     pub struct Trailered<T, D> {
@@ -153,6 +153,12 @@ mod private {
     }
 
     impl<T: MetaAccess, D> MetaAccess for Trailered<T, D> {}
+
+    impl<T: Block, D> Trailered<T, D> {
+        pub fn flush_meta(&mut self) -> Result<()> {
+            self.inner.flush_meta()
+        }
+    }
 }
 
 #[cfg(test)]