Sfoglia il codice sorgente

Got the file system integration tests in btfuse to pass.

Matthew Carr 2 anni fa
parent
commit
a2a899cbd5

+ 2 - 0
Cargo.lock

@@ -234,6 +234,7 @@ version = "0.1.0"
 dependencies = [
  "btfproto",
  "btlib",
+ "btserde",
  "bytes",
  "lazy_static",
  "libc",
@@ -259,6 +260,7 @@ dependencies = [
  "btfproto",
  "btfproto-tests",
  "btlib",
+ "btserde",
  "bytes",
  "ctor",
  "env_logger",

+ 14 - 1
TODO.txt

@@ -99,4 +99,17 @@ Implement `Blocktree::batch_forget`.
 
 - 26, 13, mdcarr941@gmail.com, 44a6ef,
 Implement a timeout mechanism in LocalFs which will purge handles and locks that have not been
-accessed for a configured period of time.
+accessed for a configured period of time.
+
+- 27, 8, mdcarr941@gmail.com, 1c59d92
+SECURITY: Reusing the IV for every sector in a block is a security risk. This is equivalent to using
+ECB mode with a cipher whose block size equals the sector size, meaning that patterns in the cipher
+text will be clearly visible. Design a method to avoid reusing the same IV for every sector.
+(Maybe use the sector index as the IV? That's kind of like CTR mode. Ah, I could hash the IV with
+the sector index, then use that as the IV for the sector.)
+
+- 27, 3, mdcarr941@gmail.com, 1c59d92
+SECURITY: Inode numbers a currently being exposed as the name of the file a block is stored in. This
+should be  avoided by hashing the inodes along with a salt. Because this salt needs to be accessible
+even before we've decrypted any data in the filesystem, we need to use data from the credentials.
+(Perhaps the path in the writecap?)

+ 1 - 0
crates/btfproto-tests/Cargo.toml

@@ -8,6 +8,7 @@ edition = "2021"
 [dependencies]
 btlib = { path = "../btlib" }
 btfproto = { path = "../btfproto" }
+btserde = { path = "../btserde" }
 tempdir = { version = "0.3.7" }
 lazy_static = { version = "1.4.0" }
 bytes = { version = "1.3.0" }

+ 17 - 6
crates/btfproto-tests/src/lib.rs

@@ -8,23 +8,34 @@ use btlib::{
 use core::time::Duration;
 use lazy_static::lazy_static;
 
+fn one_hour_hence() -> Epoch {
+    Epoch::now() + Duration::from_secs(3600)
+}
+
 lazy_static! {
-    static ref ROOT_CREDS: ConcreteCreds = ConcreteCreds::generate().unwrap();
+    static ref ROOT_CREDS: ConcreteCreds = {
+        let mut root_creds = ConcreteCreds::generate().unwrap();
+        let writecap = root_creds
+            .issue_writecap(root_creds.principal(), vec![], one_hour_hence())
+            .unwrap();
+        root_creds.set_writecap(writecap);
+        root_creds
+    };
     static ref NODE_CREDS: ConcreteCreds = {
         let root_creds = &ROOT_CREDS;
         let mut node_creds = ConcreteCreds::generate().unwrap();
         let writecap = root_creds
-            .issue_writecap(
-                node_creds.principal(),
-                vec![],
-                Epoch::now() + Duration::from_secs(3600),
-            )
+            .issue_writecap(node_creds.principal(), vec![], one_hour_hence())
             .unwrap();
         node_creds.set_writecap(writecap);
         node_creds
     };
 }
 
+pub fn root_creds() -> &'static ConcreteCreds {
+    &ROOT_CREDS
+}
+
 pub fn node_creds() -> &'static ConcreteCreds {
     &NODE_CREDS
 }

+ 91 - 34
crates/btfproto-tests/src/local_fs.rs

@@ -1,11 +1,20 @@
+use crate::root_creds;
+
 use super::node_creds;
 
-use btfproto::local_fs::{LocalFs, ModeAuthorizer};
+use btfproto::{
+    local_fs::{LocalFs, ModeAuthorizer},
+    server::FsProvider,
+};
 use btlib::{
-    crypto::{ConcreteCreds, CredsPriv},
-    BlockPath,
+    crypto::{ConcreteCreds, CredsPriv, CredsPub},
+    AuthzAttrs, BlockError, BlockPath, IssuedProcRec,
+};
+use std::{
+    net::{IpAddr, Ipv6Addr, SocketAddr},
+    path::PathBuf,
+    sync::Arc,
 };
-use std::sync::Arc;
 use tempdir::TempDir;
 
 pub fn bind_path<C: CredsPriv>(creds: C) -> BlockPath {
@@ -16,32 +25,80 @@ pub fn bind_path<C: CredsPriv>(creds: C) -> BlockPath {
     writecap.bind_path()
 }
 
-pub type ConcreteFs = LocalFs<ConcreteCreds, ModeAuthorizer>;
+pub type ConcreteFs = LocalFs<&'static ConcreteCreds, ModeAuthorizer>;
 
 pub struct LocalFsTest {
     dir: TempDir,
     fs: ConcreteFs,
-    from: Arc<BlockPath>,
+    node_bind_path: Arc<BlockPath>,
 }
 
 impl LocalFsTest {
-    pub fn new_empty() -> LocalFsTest {
+    pub const NODE_UID: u32 = 1000;
+    pub const NODE_GID: u32 = 1000;
+
+    pub async fn new_empty() -> LocalFsTest {
         let dir = TempDir::new("fuse").expect("failed to create temp dir");
-        let fs = LocalFs::new_empty(dir.path().to_owned(), 0, Self::creds(), ModeAuthorizer {})
+        let node_creds = node_creds();
+        Self::grant_node_access(dir.path().to_owned()).await;
+        let node_writecap = node_creds
+            .writecap()
+            .ok_or(BlockError::MissingWritecap)
+            .unwrap();
+        let node_bind_path = Arc::new(node_writecap.bind_path());
+        let fs = LocalFs::new_existing(dir.path().to_owned(), node_creds, ModeAuthorizer)
             .expect("failed to create empty blocktree");
-        let from = Arc::new(bind_path(node_creds()));
-        LocalFsTest { dir, fs, from }
+        LocalFsTest {
+            dir,
+            fs,
+            node_bind_path,
+        }
     }
 
     pub fn new_existing(dir: TempDir) -> LocalFsTest {
-        let fs = LocalFs::new_existing(dir.path().to_owned(), Self::creds(), ModeAuthorizer {})
+        let fs = LocalFs::new_existing(dir.path().to_owned(), node_creds(), ModeAuthorizer)
             .expect("failed to create blocktree from existing directory");
         let from = Arc::new(bind_path(node_creds()));
-        LocalFsTest { dir, fs, from }
+        LocalFsTest {
+            dir,
+            fs,
+            node_bind_path: from,
+        }
+    }
+
+    async fn grant_node_access(path: PathBuf) {
+        let root_creds = root_creds();
+        let root_writecap = root_creds
+            .writecap()
+            .ok_or(BlockError::MissingWritecap)
+            .unwrap();
+        let root_bind_path = Arc::new(root_writecap.bind_path());
+        let node_creds = node_creds();
+        let node_writecap = node_creds
+            .writecap()
+            .ok_or(BlockError::MissingWritecap)
+            .unwrap();
+        let node_bind_path = node_writecap.bind_path();
+        let fs = LocalFs::new_empty(path, 0, root_creds, ModeAuthorizer).unwrap();
+
+        let proc_rec = IssuedProcRec {
+            addr: SocketAddr::new(
+                IpAddr::V6(Ipv6Addr::LOCALHOST),
+                node_bind_path.port().unwrap(),
+            ),
+            pub_creds: node_creds.concrete_pub(),
+            writecap: node_writecap.to_owned(),
+            authz_attrs: AuthzAttrs {
+                uid: Self::NODE_UID,
+                gid: Self::NODE_GID,
+                supp_gids: Vec::new(),
+            },
+        };
+        fs.grant_access(&root_bind_path, proc_rec).await.unwrap();
     }
 
     pub fn into_parts(self) -> (TempDir, ConcreteFs, Arc<BlockPath>) {
-        (self.dir, self.fs, self.from)
+        (self.dir, self.fs, self.node_bind_path)
     }
 
     pub fn fs(&self) -> &ConcreteFs {
@@ -53,7 +110,7 @@ impl LocalFsTest {
     }
 
     pub fn from(&self) -> &Arc<BlockPath> {
-        &self.from
+        &self.node_bind_path
     }
 }
 
@@ -61,8 +118,8 @@ impl LocalFsTest {
 mod tests {
     use super::*;
 
-    use btfproto::{local_fs::Error, msg::*, server::FsProvider, Inode};
-    use btlib::{Result, SECTOR_SZ_DEFAULT};
+    use btfproto::{msg::*, local_fs::Error};
+    use btlib::{Inode, Result, SECTOR_SZ_DEFAULT};
     use bytes::BytesMut;
     use std::{
         io::{self, Cursor, Write as IoWrite},
@@ -72,7 +129,7 @@ mod tests {
     /// Tests that a new file can be created, written to and the written data can be read from it.
     #[tokio::test]
     async fn create_write_read() {
-        let case = LocalFsTest::new_empty();
+        let case = LocalFsTest::new_empty().await;
         let bt = &case.fs;
         let from = case.from();
         let name = "README.md";
@@ -117,7 +174,7 @@ mod tests {
 
     #[tokio::test]
     async fn lookup() {
-        let case = LocalFsTest::new_empty();
+        let case = LocalFsTest::new_empty().await;
         let bt = &case.fs;
         let from = case.from();
 
@@ -146,7 +203,7 @@ mod tests {
     async fn new_existing() {
         const EXPECTED: &[u8] = b"cool as cucumbers";
         let name = "RESIGNATION.docx";
-        let case = LocalFsTest::new_empty();
+        let case = LocalFsTest::new_empty().await;
         let bt = &case.fs;
         let from = case.from();
         let flags = Flags::new(libc::O_RDWR);
@@ -167,8 +224,8 @@ mod tests {
                 .unwrap();
             assert_eq!(EXPECTED.len() as u64, written);
 
-            let flush_msg = Flush { inode, handle };
-            bt.flush(from, flush_msg).await.unwrap();
+            let close_msg = Close { inode, handle };
+            bt.close(from, close_msg).await.unwrap();
         }
 
         let case = LocalFsTest::new_existing(case.dir);
@@ -205,7 +262,7 @@ mod tests {
     #[tokio::test]
     async fn open_read_only_write_is_error() {
         let name = "books.ods";
-        let case = LocalFsTest::new_empty();
+        let case = LocalFsTest::new_empty().await;
         let bt = &case.fs;
         let from = case.from();
 
@@ -252,7 +309,7 @@ mod tests {
     /// Tests that multiple handles see consistent metadata associated with a block.
     #[tokio::test]
     async fn ensure_metadata_consistency() {
-        let case = LocalFsTest::new_empty();
+        let case = LocalFsTest::new_empty().await;
         let from = case.from();
         let trash = ".Trash";
         let file = "file.txt";
@@ -307,7 +364,7 @@ mod tests {
     #[tokio::test]
     async fn read_with_smaller_size() {
         const DATA: [u8; 8] = [0, 1, 2, 3, 4, 5, 6, 7];
-        let case = LocalFsTest::new_empty();
+        let case = LocalFsTest::new_empty().await;
         let from = case.from();
         let file = "file.txt";
         let bt = &case.fs;
@@ -360,7 +417,7 @@ mod tests {
         const NREADS: usize = 32;
         const DATA_LEN: usize = SIZE * NREADS;
         const DATA: [u8; DATA_LEN] = integer_array::<DATA_LEN>(0);
-        let case = LocalFsTest::new_empty();
+        let case = LocalFsTest::new_empty().await;
         let from = case.from();
         let file = "file.txt";
         let bt = &case.fs;
@@ -410,7 +467,7 @@ mod tests {
 
     #[tokio::test]
     async fn rename_in_same_directory() {
-        let case = LocalFsTest::new_empty();
+        let case = LocalFsTest::new_empty().await;
         let bt = &case.fs;
         let from = case.from();
         let root: Inode = SpecInodes::RootDir.into();
@@ -456,7 +513,7 @@ mod tests {
 
     #[tokio::test]
     async fn rename_to_different_directory() {
-        let case = LocalFsTest::new_empty();
+        let case = LocalFsTest::new_empty().await;
         let bt = &case.fs;
         let from = case.from();
         let root = SpecInodes::RootDir.into();
@@ -510,7 +567,7 @@ mod tests {
 
     #[tokio::test]
     async fn rename_no_replace_same_directory() {
-        let case = LocalFsTest::new_empty();
+        let case = LocalFsTest::new_empty().await;
         let bt = &case.fs;
         let from = case.from();
         let root: Inode = SpecInodes::RootDir.into();
@@ -546,7 +603,7 @@ mod tests {
 
     #[tokio::test]
     async fn rename_no_replace_different_directory() {
-        let case = LocalFsTest::new_empty();
+        let case = LocalFsTest::new_empty().await;
         let bt = &case.fs;
         let from = case.from();
         let root = SpecInodes::RootDir.into();
@@ -590,7 +647,7 @@ mod tests {
 
     #[tokio::test]
     async fn read_from_non_owner_is_err() {
-        let case = LocalFsTest::new_empty();
+        let case = LocalFsTest::new_empty().await;
         let bt = &case.fs;
         let name = "file.txt";
         let owner = case.from();
@@ -621,7 +678,7 @@ mod tests {
 
     #[tokio::test]
     async fn allocate_full_sectors_zero_remain_non_zero() {
-        let case = LocalFsTest::new_empty();
+        let case = LocalFsTest::new_empty().await;
         let bt = &case.fs;
         let name = "file.txt";
         let from = case.from();
@@ -662,7 +719,7 @@ mod tests {
 
     #[tokio::test]
     async fn allocate_full_sectors_non_zero_remain_non_zero() {
-        let case = LocalFsTest::new_empty();
+        let case = LocalFsTest::new_empty().await;
         let bt = &case.fs;
         let name = "file.txt";
         let from = case.from();
@@ -711,7 +768,7 @@ mod tests {
 
     #[tokio::test]
     async fn allocate_full_sectors_non_zero_remain_zero() {
-        let case = LocalFsTest::new_empty();
+        let case = LocalFsTest::new_empty().await;
         let bt = &case.fs;
         let name = "file.txt";
         let from = case.from();
@@ -756,7 +813,7 @@ mod tests {
     /// then no change to the block occurs.
     #[tokio::test]
     async fn allocate_new_size_not_greater_than_curr_size() {
-        let case = LocalFsTest::new_empty();
+        let case = LocalFsTest::new_empty().await;
         let bt = &case.fs;
         let name = "file.txt";
         let from = case.from();

+ 16 - 10
crates/btfproto-tests/src/mode_authorizer.rs

@@ -1,12 +1,12 @@
 use super::node_creds;
 
 use btfproto::local_fs::AuthzContext;
-use btlib::BlockMeta;
+use btlib::{crypto::CredsPriv, AuthzAttrs, BlockError, BlockMeta, BlockPath};
 
 pub struct TestCase {
-    ctx_uid: u32,
-    ctx_gid: u32,
+    attrs: AuthzAttrs,
     meta: BlockMeta,
+    from: BlockPath,
 }
 
 impl TestCase {
@@ -23,17 +23,23 @@ impl TestCase {
                 Ok(())
             })
             .expect("failed to update secrets");
-        TestCase {
-            ctx_uid,
-            ctx_gid,
-            meta,
-        }
+        let attrs = AuthzAttrs {
+            uid: ctx_uid,
+            gid: ctx_gid,
+            supp_gids: Vec::new(),
+        };
+        let from = node_creds()
+            .writecap()
+            .ok_or(BlockError::MissingWritecap)
+            .unwrap()
+            .bind_path();
+        TestCase { attrs, meta, from }
     }
 
     pub fn context(&self) -> AuthzContext<'_> {
         AuthzContext {
-            uid: self.ctx_uid,
-            gid: self.ctx_gid,
+            from: &self.from,
+            attrs: &self.attrs,
             meta: &self.meta,
         }
     }

+ 1 - 1
crates/btfproto/src/lib.rs

@@ -1,6 +1,6 @@
 #![feature(type_alias_impl_trait)]
 
-pub type Inode = u64;
+pub type Inode = btlib::Inode;
 pub type Handle = u64;
 
 pub mod msg;

+ 269 - 88
crates/btfproto/src/local_fs.rs

@@ -6,9 +6,9 @@ use btlib::{
     crypto::{Creds, Decrypter, Signer},
     error::{BtErr, DisplayErr},
     BlockAccessor, BlockError, BlockMeta, BlockMetaSecrets, BlockOpenOptions, BlockPath,
-    BlockReader, BlockRecord, DirEntry, DirEntryKind, Directory, Epoch, FileBlock, FlushMeta,
-    MetaAccess, MetaReader, Positioned, Principaled, Result, Split, TrySeek, WriteDual,
-    ZeroExtendable,
+    BlockReader, DirEntry, DirEntryKind, Directory, Epoch, FileBlock, FlushMeta, MetaAccess,
+    MetaReader, Positioned, Principaled, Result, Split, TrySeek, WriteDual, ZeroExtendable,
+    AuthzAttrs, IssuedProcRec, Principal, ProcRec,
 };
 use btserde::{read_from, write_to};
 use core::future::{ready, Ready};
@@ -26,6 +26,7 @@ use std::{
         Arc, Mutex, RwLock, RwLockWriteGuard,
     },
     time::Duration,
+    net::{SocketAddr, IpAddr, Ipv6Addr},
 };
 
 pub use private::{Authorizer, AuthzContext, Error, LocalFs, ModeAuthorizer};
@@ -138,14 +139,20 @@ mod private {
     /// This type provides context for an authorization decision as to whether a given process will
     /// be allowed to access a block.
     pub struct AuthzContext<'a> {
-        /// The user ID of the process being authorized.
-        pub uid: u32,
-        /// The group ID of the process being authorized.
-        pub gid: u32,
+        /// The path from which this request was received.
+        pub from: &'a BlockPath,
+        /// The attributes of the principal whose access is being authorized.
+        pub attrs: &'a AuthzAttrs,
         /// A reference to the metadata of a block, the access to which is being authorized.
         pub meta: &'a BlockMeta,
     }
 
+    impl<'a> AuthzContext<'a> {
+        fn new(from: &'a BlockPath, attrs: &'a AuthzAttrs, meta: &'a BlockMeta) -> Self {
+            Self { from, attrs, meta }
+        }
+    }
+
     /// A trait for types which can render authorization decisions.
     pub trait Authorizer {
         /// Returns [Ok] if read authorization is granted, and [Err] otherwise.
@@ -158,7 +165,7 @@ mod private {
 
     /// A particularly simple authorizer that just looks at the mode bits in the block metadata
     /// to make authorization decisions.
-    pub struct ModeAuthorizer {}
+    pub struct ModeAuthorizer;
 
     impl ModeAuthorizer {
         fn authorize(mode: u32, mask: u32, denied_msg: &str) -> io::Result<()> {
@@ -170,7 +177,7 @@ mod private {
         }
 
         fn user_is_root(ctx: &AuthzContext<'_>) -> bool {
-            ctx.uid == 0
+            ctx.attrs.uid == 0
         }
     }
 
@@ -180,8 +187,8 @@ mod private {
                 return Ok(());
             }
             let secrets = ctx.meta.body().secrets()?;
-            let mask = (libc::S_IRUSR * (secrets.uid == ctx.uid) as u32)
-                | (libc::S_IRGRP * (secrets.gid == ctx.gid) as u32)
+            let mask = (libc::S_IRUSR * (secrets.uid == ctx.attrs.uid) as u32)
+                | (libc::S_IRGRP * (secrets.gid == ctx.attrs.gid) as u32)
                 | libc::S_IROTH;
             Self::authorize(secrets.mode, mask, "read access denied")
         }
@@ -191,8 +198,8 @@ mod private {
                 return Ok(());
             }
             let secrets = ctx.meta.body().secrets()?;
-            let mask = (libc::S_IWUSR * (secrets.uid == ctx.uid) as u32)
-                | (libc::S_IWGRP * (secrets.gid == ctx.gid) as u32)
+            let mask = (libc::S_IWUSR * (secrets.uid == ctx.attrs.uid) as u32)
+                | (libc::S_IWGRP * (secrets.gid == ctx.attrs.gid) as u32)
                 | libc::S_IWOTH;
             Self::authorize(secrets.mode, mask, "write access denied")
         }
@@ -202,8 +209,8 @@ mod private {
                 return Ok(());
             }
             let secrets = ctx.meta.body().secrets()?;
-            let mask = (libc::S_IXUSR * (secrets.uid == ctx.uid) as u32)
-                | (libc::S_IXGRP * (secrets.gid == ctx.gid) as u32)
+            let mask = (libc::S_IXUSR * (secrets.uid == ctx.attrs.uid) as u32)
+                | (libc::S_IXGRP * (secrets.gid == ctx.attrs.gid) as u32)
                 | libc::S_IXOTH;
             Self::authorize(secrets.mode, mask, "exec access denied")
         }
@@ -541,7 +548,7 @@ mod private {
         const DIR_ENTRY_LIMIT: usize = 1024;
     }
 
-    impl<C: Creds + 'static, A> LocalFs<C, A> {
+    impl<C: Creds + 'static, A: Authorizer> LocalFs<C, A> {
         /// Creates a new empty blocktree at the given path.
         pub fn new_empty(
             btdir: PathBuf,
@@ -549,7 +556,7 @@ mod private {
             creds: C,
             authorizer: A,
         ) -> Result<LocalFs<C, A>> {
-            let root_block_path = creds
+            let mut root_block_path = creds
                 .writecap()
                 .ok_or(BlockError::MissingWritecap)?
                 .root_block_path();
@@ -559,7 +566,7 @@ mod private {
                 &btdir,
                 SpecInodes::Sb.into(),
                 creds.clone(),
-                root_block_path.to_owned(),
+                root_block_path.clone(),
             )?;
             let sb = Superblock {
                 generation,
@@ -582,7 +589,7 @@ mod private {
                 &btdir,
                 SpecInodes::RootDir.into(),
                 creds.clone(),
-                root_block_path,
+                root_block_path.clone(),
             )?;
             write_to(&Directory::new(), &mut root_block)?;
             root_block.mut_meta_body().access_secrets(|secrets| {
@@ -596,7 +603,27 @@ mod private {
             })?;
             root_block.flush()?;
 
-            Self::new(btdir, sb, sb_block, root_block, creds, authorizer)
+            let fs = Self::new(btdir, sb, sb_block, root_block, creds, authorizer)?;
+            let writecap = fs.creds.writecap().ok_or(BlockError::MissingWritecap)?;
+            let root_principal = writecap.root_principal();
+            if fs.creds.principal() != root_principal {
+                let proc_rec = IssuedProcRec {
+                    addr: SocketAddr::new(
+                        IpAddr::V6(Ipv6Addr::LOCALHOST),
+                        writecap.bind_path().port()?,
+                    ),
+                    pub_creds: fs.creds.concrete_pub(),
+                    writecap: writecap.to_owned(),
+                    authz_attrs: AuthzAttrs {
+                        uid: 0,
+                        gid: 0,
+                        supp_gids: Vec::new(),
+                    },
+                };
+                root_block_path.push_component(root_principal.to_string());
+                fs.grant_access_to(&Arc::new(root_block_path), proc_rec)?;
+            }
+            Ok(fs)
         }
 
         /// Opens an existing blocktree stored at the given path.
@@ -872,6 +899,7 @@ mod private {
                 };
                 block.rewind()?;
                 write_to(&sb, &mut block)?;
+                block.flush()?;
                 Ok(inode)
             })
         }
@@ -896,13 +924,111 @@ mod private {
             }
         }
 
-        fn authz_context<'a>(&'a self, from: &BlockPath, meta: &'a BlockMeta) -> AuthzContext {
-            // TODO: Use from to lookup the uid and gid in the blocktree.
-            AuthzContext {
-                uid: 0,
-                gid: 0,
-                meta,
+        /// Grants the given credentials access to the directory this instance is responsible for.
+        ///
+        /// # Warning
+        /// This method calls `self.authz_attrs`, so the same consideration for avoiding deadlock
+        /// apply to this method as well. See the documentation of `self.authz_attrs` for details.
+        fn grant_access_to(&self, from: &Arc<BlockPath>, proc_rec: IssuedProcRec) -> Result<()> {
+            let authz_attrs = self.authz_attrs(from)?;
+            let principal = proc_rec.pub_creds.principal();
+            let bind_path = proc_rec.writecap.bind_path();
+            let proc_rec_name = bind_path
+                .components()
+                .last()
+                .ok_or_else(|| bterr!("invalid bind path"))?
+                .to_owned();
+            let inode = self.next_inode()?;
+            self.borrow_block_mut(SpecInodes::RootDir.into(), |block| {
+                self.authorizer
+                    .can_write(&AuthzContext::new(from, &authz_attrs, block.meta()))?;
+                block
+                    .mut_meta_body()
+                    .add_readcap_for(principal.clone(), &proc_rec.pub_creds.enc)?;
+
+                let mut dir = block.read_dir()?;
+                dir.add_file(proc_rec_name, inode)?;
+                // Note that write_dir calls flush, which also ensures metadata is written to disk.
+                block.write_dir(&dir)?;
+
+                Ok(())
+            })?;
+            self.borrow_block_mut(SpecInodes::Sb.into(), |block| {
+                block
+                    .mut_meta_body()
+                    .add_readcap_for(principal.clone(), &proc_rec.pub_creds.enc)?;
+                block.flush_meta()
+            })?;
+            let self_writecap = self.creds.writecap().ok_or(BlockError::MissingWritecap)?;
+            let self_bind_path = Arc::new(self_writecap.bind_path());
+            self.open_value(self_bind_path.clone(), inode, bind_path, |value| {
+                value.borrow_block_mut(|block| {
+                    // TODO: This should not be needed because the principal has been given access
+                    // to the root directory. Once proper inherited readcaps are implemented we will
+                    // no longer need to add a readcap to this block.
+                    block
+                        .mut_meta_body()
+                        .add_readcap_for(principal, &proc_rec.pub_creds.enc)?;
+                    block.write_proc_rec(&ProcRec::Valid(proc_rec))
+                })
+            })?;
+            // We must ensure the reference count for the inode is decremented, otherwise the table
+            // entry will never be freed.
+            let mut inodes = self.inodes.write().display_err()?;
+            self.inode_forget(&mut inodes, &self_bind_path, inode, 1)?;
+            Ok(())
+        }
+
+        fn lookup_inode_in(&self, parent: Inode, name: &str) -> Result<Inode> {
+            let dir = self.borrow_block_mut(parent, |block| block.read_dir())?;
+            dir.entry(name)
+                .ok_or_else(|| io::Error::from_raw_os_error(libc::ENOENT).into())
+                .map(|e| e.inode())
+        }
+
+        fn lookup_inode<'a, I: Iterator<Item = &'a str>>(&self, components: I) -> Result<Inode> {
+            const ROOT: Inode = SpecInodes::RootDir as Inode;
+            let mut inode = ROOT;
+            for component in components {
+                inode = self.lookup_inode_in(inode, component)?;
+            }
+            if inode == ROOT {
+                Err(io::Error::from_raw_os_error(libc::ENOENT).into())
+            } else {
+                Ok(inode)
+            }
+        }
+
+        /// Retrieves the authorization attributes for the principal identified by the given path.
+        /// If the principal is not associated with a valid process record, then an [Err] is
+        /// returned.
+        /// # Warning
+        /// If this method is called while a lock for any component on the given path is held, then
+        /// a deadlock may occur. It's safest to call this method when _no_ locks are held.
+        fn authz_attrs(&self, from: &Arc<BlockPath>) -> Result<AuthzAttrs> {
+            let writecap = self.creds.writecap().ok_or(BlockError::MissingWritecap)?;
+            let root_principal = writecap.root_principal();
+            let from_principal = from.components().last().map_or_else(
+                || Err(bterr!("path from which message was received was empty")),
+                Principal::try_from,
+            )?;
+            if root_principal == from_principal {
+                // Now I am become root, the destroyer of files.
+                return Ok(AuthzAttrs {
+                    uid: 0,
+                    gid: 0,
+                    supp_gids: Vec::new(),
+                });
             }
+            let local_root = writecap.path();
+            let relative = from.relative_to(local_root)?;
+            let inode = self.lookup_inode(relative.components())?;
+            let proc_rec =
+                self.open_value(from.clone(), inode, from.as_ref().to_owned(), |value| {
+                    value.borrow_block_mut(|block| block.read_proc())
+                })?;
+            let proc_rec = proc_rec.validate()?;
+            Ok(proc_rec.authz_attrs)
         }
     }
 
@@ -916,9 +1042,14 @@ mod private {
             let result = (move || {
                 let Lookup { parent, name, .. } = msg;
                 debug!("lookup: parent {parent}, {:?}", name);
+                let authz_attrs = self.authz_attrs(from)?;
+
                 let (dir, block_path) = match self.borrow_block_mut(parent, |parent_block| {
-                    self.authorizer
-                        .can_exec(&self.authz_context(from, parent_block.meta()))?;
+                    self.authorizer.can_exec(&AuthzContext::new(
+                        from,
+                        &authz_attrs,
+                        parent_block.meta(),
+                    ))?;
                     let dir = parent_block.read_dir()?;
                     let path = parent_block.meta_body().path().to_owned();
                     Ok((dir, path))
@@ -932,9 +1063,7 @@ mod private {
                 let entry = dir
                     .entry(name)
                     .ok_or_else(|| io::Error::from_raw_os_error(libc::ENOENT))?;
-                let inode = entry.inode().ok_or_else(|| {
-                    io::Error::new(io::ErrorKind::Unsupported, "can't lookup server entry")
-                })?;
+                let inode = entry.inode();
                 let stat = match self.open_value(from.to_owned(), inode, block_path, |value| {
                     let stat = value.block.meta_body().secrets()?.to_owned();
                     value.incr_lookup_count(from);
@@ -968,15 +1097,17 @@ mod private {
                     umask,
                 } = msg;
                 debug!("create: parent {parent}, name {:?}", name);
+                let authz_attrs = self.authz_attrs(from)?;
 
                 let name = msg.name.to_owned();
 
                 // Add a directory entry to the parent for the new inode.
-                let (inode, mut block_path, uid, gid) = self.borrow_block_mut(parent, |block| {
-                    let ctx = self.authz_context(from, block.meta());
-                    let uid = ctx.uid;
-                    let gid = ctx.gid;
-                    self.authorizer.can_write(&ctx)?;
+                let (inode, mut block_path) = self.borrow_block_mut(parent, |block| {
+                    self.authorizer.can_write(&AuthzContext::new(
+                        from,
+                        &authz_attrs,
+                        block.meta(),
+                    ))?;
 
                     let mut dir = block.read_dir()?;
                     if dir.contains_entry(&name) {
@@ -988,7 +1119,7 @@ mod private {
                     dir.add_file(name.clone(), inode)?;
                     block.write_dir(&dir)?;
 
-                    Ok((inode, block.meta_body().path().clone(), uid, gid))
+                    Ok((inode, block.meta_body().path().clone()))
                 })?;
                 block_path.push_component(name);
 
@@ -1003,8 +1134,11 @@ mod private {
                                 secrets.block_id.generation = self.generation;
                                 secrets.block_id.inode = inode;
                                 secrets.mode = mode & !umask;
-                                secrets.uid = uid;
-                                secrets.gid = gid;
+                                if flags.directory() {
+                                    secrets.mode |= FileType::Dir.value();
+                                }
+                                secrets.uid = authz_attrs.uid;
+                                secrets.gid = authz_attrs.gid;
                                 let now = Epoch::now();
                                 secrets.atime = now;
                                 secrets.ctime = now;
@@ -1049,10 +1183,11 @@ mod private {
                 if flags.value() & libc::O_CLOEXEC != 0 {
                     return Err(Self::unsupported_flag_err("O_CLOEXEC"));
                 }
+                let authz_attrs = self.authz_attrs(from)?;
                 let handle =
                     self.take_handle_if_ok(from.clone(), inode, flags, |handle, value| {
                         value.access_block(from, handle, |block, _| {
-                            let ctx = self.authz_context(from, block.meta());
+                            let ctx = AuthzContext::new(from, &authz_attrs, block.meta());
                             if flags.readable() {
                                 self.authorizer.can_read(&ctx)?;
                             }
@@ -1156,10 +1291,17 @@ mod private {
             let result = (|| {
                 let Flush { inode, handle } = msg;
                 debug!("flush: inode {inode}, handle {handle}");
-                self.access_block_mut(from, inode, handle, |block, flags| {
+                match self.access_block_mut(from, inode, handle, |block, flags| {
                     flags.assert_writeable()?;
                     block.flush().bterr()
-                })
+                }) {
+                    Ok(value) => Ok(value),
+                    Err(err) => match err.downcast_ref::<Error>() {
+                        // If the handle is read-only we just return Ok.
+                        Some(Error::ReadOnlyHandle(..)) => Ok(()),
+                        _ => Err(err),
+                    },
+                }
             })();
             ready(result)
         }
@@ -1208,42 +1350,48 @@ mod private {
 
         type LinkFut<'c> = Ready<Result<LinkReply>>;
         fn link<'c>(&'c self, from: &'c Arc<BlockPath>, msg: Link) -> Self::LinkFut<'c> {
-            let Link {
-                inode,
-                new_parent,
-                name,
-            } = msg;
-            debug!("link: inode {inode}, new_parent {new_parent}, name {name}");
-            let result = self.borrow_block_mut(new_parent, |parent_block| {
-                self.authorizer
-                    .can_write(&self.authz_context(from, parent_block.meta()))?;
+            let result = (move || {
+                let Link {
+                    inode,
+                    new_parent,
+                    name,
+                } = msg;
+                debug!("link: inode {inode}, new_parent {new_parent}, name {name}");
+                let authz_attrs = self.authz_attrs(from)?;
+                self.borrow_block_mut(new_parent, |parent_block| {
+                    self.authorizer.can_write(&AuthzContext::new(
+                        from,
+                        &authz_attrs,
+                        parent_block.meta(),
+                    ))?;
 
-                let mut dir = parent_block.read_dir()?;
-                if dir.contains_entry(name) {
-                    return Err(io::Error::from_raw_os_error(libc::EEXIST).into());
-                }
+                    let mut dir = parent_block.read_dir()?;
+                    if dir.contains_entry(name) {
+                        return Err(io::Error::from_raw_os_error(libc::EEXIST).into());
+                    }
 
-                let attr = self.access_value_mut(inode, |value| {
-                    let block = value.block_mut();
-                    let meta = block.mut_meta_body();
-                    let attr = meta.access_secrets(|secrets| {
-                        secrets.nlink += 1;
-                        Ok(secrets.to_owned())
+                    let attr = self.access_value_mut(inode, |value| {
+                        let block = value.block_mut();
+                        let meta = block.mut_meta_body();
+                        let attr = meta.access_secrets(|secrets| {
+                            secrets.nlink += 1;
+                            Ok(secrets.to_owned())
+                        })?;
+                        block.flush_meta()?;
+                        value.incr_lookup_count(from);
+                        Ok(attr)
                     })?;
-                    block.flush_meta()?;
-                    value.incr_lookup_count(from);
-                    Ok(attr)
-                })?;
-                let file_type = FileType::from_value(attr.mode)?;
-                let entry = match file_type {
-                    FileType::Reg => DirEntry::File(BlockRecord::new(inode)),
-                    FileType::Dir => DirEntry::Directory(BlockRecord::new(inode)),
-                };
-                dir.insert_entry(name.to_owned(), entry);
-                parent_block.write_dir(&dir)?;
-                let entry = self.bt_entry(attr);
-                Ok(LinkReply { entry })
-            });
+                    let file_type = FileType::from_value(attr.mode)?;
+                    let entry = match file_type {
+                        FileType::Reg => DirEntry::File(inode),
+                        FileType::Dir => DirEntry::Directory(inode),
+                    };
+                    dir.insert_entry(name.to_owned(), entry);
+                    parent_block.write_dir(&dir)?;
+                    let entry = self.bt_entry(attr);
+                    Ok(LinkReply { entry })
+                })
+            })();
             ready(result)
         }
 
@@ -1252,21 +1400,20 @@ mod private {
             let result = (move || {
                 let Unlink { parent, name } = msg;
                 debug!("unlink: parent {parent}, name {name}");
+                let authz_attrs = self.authz_attrs(from)?;
                 let (block_path, inode) = self.borrow_block_mut(parent, |parent_block| {
-                    self.authorizer
-                        .can_write(&self.authz_context(from, parent_block.meta()))?;
+                    self.authorizer.can_write(&AuthzContext::new(
+                        from,
+                        &authz_attrs,
+                        parent_block.meta(),
+                    ))?;
 
                     let mut dir = parent_block.read_dir()?;
                     let entry = match dir.remove_entry(name) {
                         None => return Err(io::Error::from_raw_os_error(libc::ENOENT).into()),
                         Some(entry) => entry,
                     };
-                    let inode = entry.inode().ok_or_else(|| {
-                        io::Error::new(
-                            io::ErrorKind::Other,
-                            "no inode associated with the given name",
-                        )
-                    })?;
+                    let inode = entry.inode();
                     parent_block.write_dir(&dir)?;
 
                     let mut block_path = parent_block.meta_body().path().clone();
@@ -1326,9 +1473,13 @@ mod private {
                     attrs_set,
                 } = msg;
                 debug!("write_meta: inode {inode}, handle {:?}", handle);
+                let authz_attrs = self.authz_attrs(from)?;
                 let cb = |block: &mut FileBlock<C>| {
-                    self.authorizer
-                        .can_write(&self.authz_context(from, block.meta()))?;
+                    self.authorizer.can_write(&AuthzContext::new(
+                        from,
+                        &authz_attrs,
+                        block.meta(),
+                    ))?;
                     let attrs = block.mut_meta_body().access_secrets(|secrets| {
                         if attrs_set.mode() {
                             secrets.mode = attrs.mode;
@@ -1401,9 +1552,11 @@ mod private {
             let Close { inode, handle } = msg;
             debug!("close: inode {inode}, handle {handle}");
             let result = self.access_value_mut(inode, |value| {
-                if let Err(err) =
-                    value.access_block_mut(from, handle, |block, _| block.flush().bterr())
-                {
+                if let Err(err) = value.access_block_mut(from, handle, |block, _| {
+                    block.flush()?;
+                    block.flush_meta()?;
+                    Ok(())
+                }) {
                     if let Some(Error::ReadOnlyHandle(_)) = err.downcast_ref::<Error>() {
                     } else {
                         return Err(err);
@@ -1435,5 +1588,33 @@ mod private {
         fn unlock<'c>(&'c self, _from: &'c Arc<BlockPath>, _msg: Unlock) -> Self::UnlockFut<'c> {
             todo!();
         }
+
+        type AddReacapFut<'c> = Ready<Result<()>>;
+        fn add_readcap<'c>(
+            &'c self,
+            from: &'c Arc<BlockPath>,
+            msg: AddReadcap,
+        ) -> Self::AddReacapFut<'c> {
+            let AddReadcap {
+                inode,
+                handle,
+                principal,
+                enc_key,
+            } = msg;
+            debug!("add_readcap: inode {inode}, handle {handle}, principal {principal}");
+            let result = self.access_block_mut(from, inode, handle, |block, _| {
+                block.mut_meta_body().add_readcap_for(principal, &enc_key)
+            });
+            ready(result)
+        }
+
+        type GrantAccessFut<'c> = Ready<Result<()>>;
+        fn grant_access<'c>(
+            &'c self,
+            from: &'c Arc<BlockPath>,
+            msg: IssuedProcRec,
+        ) -> Self::GrantAccessFut<'c> {
+            ready(self.grant_access_to(from, msg))
+        }
     }
 }

+ 102 - 15
crates/btfproto/src/msg.rs

@@ -1,6 +1,9 @@
 use super::{Handle, Inode};
 
-use btlib::{BlockMeta, BlockMetaSecrets, DirEntry, Epoch};
+use btlib::{
+    crypto::{AsymKeyPub, Encrypt},
+    BlockMeta, BlockMetaSecrets, DirEntry, Epoch, IssuedProcRec, Principal,
+};
 use btmsg::CallMsg;
 use core::time::Duration;
 use paste::paste;
@@ -34,6 +37,8 @@ pub enum FsMsg<'a> {
     Forget(Forget),
     Lock(Lock),
     Unlock(Unlock),
+    AddReadcap(AddReadcap),
+    GrantAccess(IssuedProcRec),
 }
 
 #[derive(Serialize, Deserialize)]
@@ -84,6 +89,58 @@ impl From<SpecInodes> for Inode {
     }
 }
 
+#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
+#[repr(i32)]
+/// The generators for the group of [Flag]s. These are mostly copied from [libc], save for
+/// several custom values.
+pub enum FlagValue {
+    // Standard flags.
+    ReadOnly = libc::O_RDONLY,
+    WriteOnly = libc::O_WRONLY,
+    ReadWrite = libc::O_RDWR,
+    AccMode = libc::O_ACCMODE,
+    Create = libc::O_CREAT,
+    Exclusive = libc::O_EXCL,
+    NoCtty = libc::O_NOCTTY,
+    Truncate = libc::O_TRUNC,
+    Append = libc::O_APPEND,
+    NonBlock = libc::O_NONBLOCK,
+    Dsync = libc::O_DSYNC,
+    Async = libc::O_ASYNC,
+    Direct = libc::O_DIRECT,
+    Directory = libc::O_DIRECTORY,
+    NoFollow = libc::O_NOFOLLOW,
+    NoAtime = libc::O_NOATIME,
+    CloseExec = libc::O_CLOEXEC,
+    Rsync = libc::O_RSYNC,
+    // Custom flags.
+    /// Indicates that a process block should be created.
+    Process = 0x01000000,
+    /// Indicates that a server block be created.
+    Server = 0x02000000,
+}
+
+impl FlagValue {
+    pub const fn value(self) -> i32 {
+        self as i32
+    }
+}
+
+impl Copy for FlagValue {}
+
+impl From<FlagValue> for i32 {
+    fn from(flag_value: FlagValue) -> Self {
+        flag_value.value()
+    }
+}
+
+impl BitOr for FlagValue {
+    type Output = Flags;
+    fn bitor(self, rhs: Self) -> Self::Output {
+        Flags::new(self.value() | rhs.value())
+    }
+}
+
 #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
 /// A wrapper type around [i32] with convenience methods for checking if `libc::O_*`
 /// flags have been set.
@@ -92,34 +149,42 @@ pub struct Flags(i32);
 impl Copy for Flags {}
 
 impl Flags {
-    pub fn new(value: i32) -> Self {
+    pub const fn new(value: i32) -> Self {
         Self(value)
     }
 
-    pub fn value(self) -> i32 {
+    pub const fn value(self) -> i32 {
         self.0
     }
 
     /// Returns true if and only if the given open flags allow the file to be written to.
-    pub fn writeable(self) -> bool {
-        const MASK: i32 = libc::O_RDWR | libc::O_WRONLY;
+    pub const fn writeable(self) -> bool {
+        const MASK: i32 = FlagValue::ReadWrite.value() | FlagValue::WriteOnly.value();
         self.0 & MASK != 0
     }
 
-    pub fn readable(self) -> bool {
+    pub const fn readable(self) -> bool {
         !self.write_only()
     }
 
-    pub fn read_only(self) -> bool {
-        self.0 == libc::O_RDONLY
+    pub const fn read_only(self) -> bool {
+        self.0 == FlagValue::ReadOnly.value()
+    }
+
+    pub const fn write_only(self) -> bool {
+        self.0 & FlagValue::WriteOnly.value() != 0
+    }
+
+    pub const fn directory(self) -> bool {
+        self.0 & FlagValue::Directory.value() != 0
     }
 
-    pub fn write_only(self) -> bool {
-        self.0 & libc::O_WRONLY != 0
+    pub const fn process(self) -> bool {
+        self.0 & FlagValue::Process.value() != 0
     }
 
-    pub fn directory(self) -> bool {
-        self.0 & libc::O_DIRECTORY != 0
+    pub const fn server(self) -> bool {
+        self.0 & FlagValue::Server.value() != 0
     }
 
     pub fn assert_readable(self) -> Result<(), io::Error> {
@@ -151,9 +216,23 @@ impl From<Flags> for i32 {
     }
 }
 
-impl AsRef<i32> for Flags {
-    fn as_ref(&self) -> &i32 {
-        &self.0
+impl From<FlagValue> for Flags {
+    fn from(flag_value: FlagValue) -> Self {
+        Self::new(flag_value.value())
+    }
+}
+
+impl BitOr<Flags> for Flags {
+    type Output = Flags;
+    fn bitor(self, rhs: Flags) -> Self::Output {
+        Self::new(self.value() | rhs.value())
+    }
+}
+
+impl BitOr<FlagValue> for Flags {
+    type Output = Flags;
+    fn bitor(self, rhs: FlagValue) -> Self::Output {
+        Self::new(self.value() | rhs.value())
     }
 }
 
@@ -429,3 +508,11 @@ pub struct Unlock {
     pub inode: Inode,
     pub handle: Handle,
 }
+
+#[derive(Serialize, Deserialize)]
+pub struct AddReadcap {
+    pub inode: Inode,
+    pub handle: Handle,
+    pub principal: Principal,
+    pub enc_key: AsymKeyPub<Encrypt>,
+}

+ 43 - 1
crates/btfproto/src/server.rs

@@ -3,7 +3,7 @@ use crate::{
     Handle, Inode,
 };
 
-use btlib::{crypto::Creds, BlockPath, Result};
+use btlib::{crypto::Creds, BlockPath, IssuedProcRec, Result};
 use btmsg::{receiver, MsgCallback, MsgReceived, Receiver};
 use core::future::Future;
 use std::{io::Read, net::IpAddr, sync::Arc};
@@ -99,6 +99,24 @@ pub trait FsProvider: Send + Sync {
     where
         Self: 'c;
     fn unlock<'c>(&'c self, from: &'c Arc<BlockPath>, msg: Unlock) -> Self::UnlockFut<'c>;
+
+    type AddReacapFut<'c>: Send + Future<Output = Result<()>>
+    where
+        Self: 'c;
+    fn add_readcap<'c>(
+        &'c self,
+        from: &'c Arc<BlockPath>,
+        msg: AddReadcap,
+    ) -> Self::AddReacapFut<'c>;
+
+    type GrantAccessFut<'c>: Send + Future<Output = Result<()>>
+    where
+        Self: 'c;
+    fn grant_access<'c>(
+        &'c self,
+        from: &'c Arc<BlockPath>,
+        msg: IssuedProcRec,
+    ) -> Self::GrantAccessFut<'c>;
 }
 
 impl<P: FsProvider> FsProvider for &P {
@@ -198,6 +216,24 @@ impl<P: FsProvider> FsProvider for &P {
     fn unlock<'c>(&'c self, from: &'c Arc<BlockPath>, msg: Unlock) -> Self::UnlockFut<'c> {
         (*self).unlock(from, msg)
     }
+
+    type AddReacapFut<'c> = P::AddReacapFut<'c> where Self: 'c;
+    fn add_readcap<'c>(
+        &'c self,
+        from: &'c Arc<BlockPath>,
+        msg: AddReadcap,
+    ) -> Self::AddReacapFut<'c> {
+        (*self).add_readcap(from, msg)
+    }
+
+    type GrantAccessFut<'c> = P::GrantAccessFut<'c> where Self: 'c;
+    fn grant_access<'c>(
+        &'c self,
+        from: &'c Arc<BlockPath>,
+        msg: IssuedProcRec,
+    ) -> Self::GrantAccessFut<'c> {
+        (*self).grant_access(from, msg)
+    }
 }
 
 struct ServerCallback<P> {
@@ -265,6 +301,12 @@ impl<P: 'static + Send + Sync + FsProvider> MsgCallback for ServerCallback<P> {
                 FsMsg::Forget(forget) => FsReply::Ack(provider.forget(&from, forget).await?),
                 FsMsg::Lock(lock) => FsReply::Ack(provider.lock(&from, lock).await?),
                 FsMsg::Unlock(unlock) => FsReply::Ack(provider.unlock(&from, unlock).await?),
+                FsMsg::AddReadcap(add_readcap) => {
+                    FsReply::Ack(provider.add_readcap(&from, add_readcap).await?)
+                }
+                FsMsg::GrantAccess(proc_rec) => {
+                    FsReply::Ack(provider.grant_access(&from, proc_rec).await?)
+                }
             };
             replier.unwrap().reply(reply).await
         }

+ 3 - 31
crates/btfs/src/main.rs

@@ -1,26 +1,18 @@
 use log::info;
 use std::{
     env::{self, VarError},
-    fs, io,
-    path::Path,
+    fs,
     path::PathBuf,
     str::FromStr,
 };
 use tss_esapi::{tcti_ldr::TabrmdConfig, Context};
 
-use btlib::{
-    blocktree::{Blocktree, ModeAuthorizer},
-    crypto::{
-        tpm::{TpmCredStore, TpmCreds},
-        CredStore,
-    },
-};
+use btlib::crypto::{tpm::TpmCredStore, CredStore};
 
 const PATH_ENV: &str = "BT_DIR";
 const TABRMD_ENV: &str = "BT_TABRMD";
 const TABRMD_CONFIG: &str = "bus_type=session,bus_name=com.intel.tss2.Tabrmd";
 const STATE_FILE: &str = "tpm_state";
-const BLOCK_DIR: &str = "blocks";
 
 fn btdir() -> PathBuf {
     env::var(PATH_ENV).map(PathBuf::from).unwrap_or_else(|err| {
@@ -45,25 +37,6 @@ fn tabrmd_config() -> String {
     })
 }
 
-fn make_blocktree(btdir: &Path, creds: TpmCreds) -> Blocktree<TpmCreds, ModeAuthorizer> {
-    let block_dir = btdir.join(BLOCK_DIR);
-    let exists = match fs::read_dir(&block_dir) {
-        Ok(_) => true,
-        Err(err) => {
-            if let io::ErrorKind::NotFound = err.kind() {
-                false
-            } else {
-                panic!("unexpected error occurred when checking if block dir exists: {err}");
-            }
-        }
-    };
-    if exists {
-        Blocktree::new_existing(block_dir, creds, ModeAuthorizer {}).unwrap()
-    } else {
-        Blocktree::new_empty(block_dir, 0, creds, ModeAuthorizer {}).unwrap()
-    }
-}
-
 fn main() {
     let btdir = btdir();
     info!("using btdir= '{}'", btdir.display());
@@ -73,6 +46,5 @@ fn main() {
     let context =
         Context::new_with_tabrmd(TabrmdConfig::from_str(&tabrmd_config).unwrap()).unwrap();
     let cred_store = TpmCredStore::new(context, btdir.join(STATE_FILE)).unwrap();
-    let node_creds = cred_store.node_creds().unwrap();
-    let _bt = make_blocktree(&btdir, node_creds);
+    let _node_creds = cred_store.node_creds().unwrap();
 }

+ 1 - 0
crates/btfuse/Cargo.toml

@@ -7,6 +7,7 @@ edition = "2021"
 
 [dependencies]
 btlib = { path = "../btlib" }
+btserde = { path = "../btserde" }
 swtpm-harness = { path = "../swtpm-harness" }
 tss-esapi = { version = "7.1.0", features = ["generate-bindings"] }
 btfproto = { path = "../btfproto" }

+ 104 - 0
crates/btfuse/src/config.rs

@@ -0,0 +1,104 @@
+use super::DEFAULT_CONFIG;
+
+use std::path::{Path, PathBuf};
+
+#[derive(PartialEq, Eq, Clone)]
+pub struct Config {
+    pub block_dir: PathBuf,
+    pub mnt_dir: PathBuf,
+    pub tpm_state_file: PathBuf,
+    pub tabrmd: String,
+    pub mnt_options: String,
+    pub threads: usize,
+}
+
+impl Config {
+    pub fn builder() -> ConfigBuilder {
+        ConfigBuilder::new()
+    }
+}
+
+pub struct ConfigRef<'a> {
+    pub block_dir: &'a str,
+    pub mnt_dir: &'a str,
+    pub tpm_state_file: &'a str,
+    pub tabrmd: &'a str,
+    pub mnt_options: &'a str,
+    pub threads: usize,
+}
+
+#[derive(Default, PartialEq, Eq, Clone)]
+pub struct ConfigBuilder {
+    pub block_dir: Option<PathBuf>,
+    pub mnt_dir: Option<PathBuf>,
+    pub tpm_state_file: Option<PathBuf>,
+    pub tabrmd: Option<String>,
+    pub mnt_options: Option<String>,
+    pub threads: Option<usize>,
+}
+
+impl ConfigBuilder {
+    pub fn new() -> Self {
+        Self::default()
+    }
+
+    pub fn with_block_dir(mut self, block_dir: Option<PathBuf>) -> Self {
+        self.block_dir = block_dir;
+        self
+    }
+
+    pub fn with_mnt_dir(mut self, mnt_dir: Option<PathBuf>) -> Self {
+        self.mnt_dir = mnt_dir;
+        self
+    }
+
+    #[allow(dead_code)]
+    pub fn with_tpm_state_file(mut self, mnt_dir: Option<PathBuf>) -> Self {
+        self.tpm_state_file = mnt_dir;
+        self
+    }
+
+    pub fn with_tabrmd(mut self, tabrmd: Option<String>) -> Self {
+        self.tabrmd = tabrmd;
+        self
+    }
+
+    pub fn with_mnt_options(mut self, mnt_options: Option<String>) -> Self {
+        self.mnt_options = mnt_options;
+        self
+    }
+
+    #[allow(dead_code)]
+    pub fn with_threads(mut self, threads: Option<usize>) -> Self {
+        self.threads = threads;
+        self
+    }
+
+    pub fn build(self) -> Config {
+        Config {
+            block_dir: self
+                .block_dir
+                .unwrap_or_else(|| Path::new(DEFAULT_CONFIG.block_dir).to_owned()),
+            mnt_dir: self
+                .mnt_dir
+                .unwrap_or_else(|| Path::new(DEFAULT_CONFIG.mnt_dir).to_owned()),
+            tpm_state_file: self
+                .tpm_state_file
+                .unwrap_or_else(|| Path::new(DEFAULT_CONFIG.tpm_state_file).to_owned()),
+            tabrmd: self
+                .tabrmd
+                .unwrap_or_else(|| DEFAULT_CONFIG.tabrmd.to_owned()),
+            mnt_options: self
+                .mnt_options
+                .unwrap_or_else(|| DEFAULT_CONFIG.mnt_options.to_owned()),
+            threads: self
+                .threads
+                .unwrap_or_else(|| DEFAULT_CONFIG.threads.to_owned()),
+        }
+    }
+}
+
+pub struct EnvVars {
+    pub tabrmd: &'static str,
+    pub mnt_options: &'static str,
+}

+ 14 - 25
crates/btfuse/src/fuse_daemon.rs

@@ -1,27 +1,22 @@
-use crate::{fuse_fs::FuseFs, PathExt, MOUNT_OPTIONS};
+use crate::{fuse_fs::FuseFs, PathExt, DEFAULT_CONFIG};
 
 use btfproto::server::FsProvider;
-use btlib::{
-    BlockPath, Result,
-};
+use btlib::{BlockPath, Result};
 use fuse_backend_rs::{
     api::server::Server,
-    transport::{self, FuseSession, FuseChannel},
+    transport::{self, FuseChannel, FuseSession},
 };
+use futures::future::FutureExt;
 use log::error;
 use std::path::PathBuf;
 use std::{
-    collections::HashMap,
     ffi::{c_char, c_int, CString},
     fs::File,
     os::{fd::FromRawFd, unix::ffi::OsStrExt},
     path::Path,
     sync::Arc,
 };
-use tokio::{
-    task::JoinSet,
-};
-use futures::future::FutureExt;
+use tokio::task::JoinSet;
 
 pub use private::FuseDaemon;
 
@@ -38,7 +33,7 @@ mod private {
     /// to communicate with the kernel is returned.
     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 options = CString::new(DEFAULT_CONFIG.mnt_options).unwrap();
         let raw_fd = unsafe { fuse_open_channel(mountpoint.as_ptr(), options.as_ptr()) };
         unsafe { File::from_raw_fd(raw_fd) }
     }
@@ -50,22 +45,21 @@ mod private {
     impl FuseDaemon {
         const FSNAME: &str = "btfuse";
         const FSTYPE: &str = "bt";
-        const NUM_THREADS: usize = 8;
+        const NUM_THREADS: usize = 1;
 
-        pub fn new<T: AsRef<Path>, P: 'static + FsProvider>(
-            path: T,
-            uid_map: HashMap<u32, Arc<BlockPath>>,
+        pub fn new<P: 'static + FsProvider>(
+            mnt_path: PathBuf,
             fallback_path: Arc<BlockPath>,
             provider: P,
         ) -> Result<Self> {
-            path.try_create_dir()?;
-            let server = Arc::new(Server::new(FuseFs::new(provider, uid_map, fallback_path)));
-            let session = Self::session(path)?;
+            let server = Arc::new(Server::new(FuseFs::new(provider, fallback_path)));
+            let session = Self::session(mnt_path)?;
             let mut set = JoinSet::new();
             for _ in 0..Self::NUM_THREADS {
                 let server = server.clone();
                 let channel = session.new_channel()?;
-                let future = tokio::task::spawn_blocking(move || Self::server_loop(server, channel));
+                let future =
+                    tokio::task::spawn_blocking(move || Self::server_loop(server, channel));
                 let future = future.map(|result| {
                     if let Err(err) = result {
                         error!("server_loop produced an error: {err}");
@@ -76,12 +70,7 @@ mod private {
             Ok(Self { set })
         }
 
-        pub fn mnt_path<T: AsRef<Path>>(path: T) -> PathBuf {
-            path.as_ref().join("mnt")
-        }
-
-        fn session<T: AsRef<Path>>(path: T) -> Result<FuseSession> {
-            let mnt_path = Self::mnt_path(path);
+        fn session<T: AsRef<Path>>(mnt_path: T) -> Result<FuseSession> {
             mnt_path.try_create_dir()?;
             let mut session =
                 FuseSession::new(mnt_path.as_ref(), Self::FSNAME, Self::FSTYPE, false)?;

+ 179 - 98
crates/btfuse/src/fuse_fs.rs

@@ -1,30 +1,28 @@
 use btfproto::{msg::*, server::FsProvider};
 use btlib::{
-    error::DisplayErr,
-    bterr, BlockId, Result, BlockMetaSecrets, BlockPath, Epoch,
+    bterr, collections::Bijection, error::DisplayErr, AuthzAttrs, BlockId, BlockMetaSecrets,
+    BlockPath, Epoch, Result,
 };
-use core::{ffi::CStr, time::Duration};
+use btserde::read_from;
+use bytes::{Buf, BytesMut};
+use core::{ffi::CStr, future::Future, time::Duration};
 use fuse_backend_rs::{
     abi::fuse_abi::{stat64, Attr, CreateIn},
     api::filesystem::{
-        Context, Entry, FileSystem,
-        OpenOptions, SetattrValid, FsOptions,
+        Context, DirEntry, Entry, FileSystem, FsOptions, OpenOptions, SetattrValid, ZeroCopyReader,
+        ZeroCopyWriter,
     },
 };
 use log::{debug, error};
-use core::future::Future;
 use std::{
     io::{self, Result as IoResult},
-    collections::HashMap,
-    sync::Arc,
+    sync::{Arc, RwLock},
 };
+use tokio::runtime::Handle;
 
 pub use private::FuseFs;
 
 mod private {
-    use fuse_backend_rs::api::filesystem::{ZeroCopyWriter, ZeroCopyReader, DirEntry};
-    use tokio::runtime::Handle;
-
     use super::*;
 
     trait BlockMetaSecretsExt {
@@ -59,17 +57,50 @@ mod private {
             })
         }
     }
-    trait EntryExt {
-        fn fuse_entry(&self) -> Result<Entry>;
+
+    fn block_on<F: Future>(future: F) -> F::Output {
+        Handle::current().block_on(future)
     }
 
-    impl EntryExt for btfproto::msg::Entry {
-        fn fuse_entry(&self) -> Result<Entry> {
+    pub struct FuseFs<P> {
+        provider: P,
+        path_by_luid: Bijection<u32, Arc<BlockPath>>,
+        ruid_by_path: RwLock<Bijection<Arc<BlockPath>, u32>>,
+    }
+
+    impl<P> FuseFs<P> {
+        pub fn new(provider: P, fallback_path: Arc<BlockPath>) -> Self {
+            let proc_uid = unsafe { libc::geteuid() };
+            Self {
+                provider,
+                /// luid: Local UID
+                path_by_luid: Bijection::new(proc_uid, fallback_path.clone()),
+                // ruid: Remote UID
+                ruid_by_path: RwLock::new(Bijection::new(fallback_path, 0)),
+            }
+        }
+        fn path_from_luid(&self, luid: u32) -> &Arc<BlockPath> {
+            self.path_by_luid.value(&luid)
+        }
+
+        fn luid_from_ruid(&self, ruid: u32) -> Result<u32> {
+            let guard = self.ruid_by_path.read().display_err()?;
+            let path = guard.key(&ruid);
+            Ok(*self.path_by_luid.key(path))
+        }
+
+        fn convert_ruid(&self, mut attrs: BlockMetaSecrets) -> Result<BlockMetaSecrets> {
+            attrs.uid = self.luid_from_ruid(attrs.uid)?;
+            Ok(attrs)
+        }
+
+        fn fuse_entry(&self, attrs: btfproto::msg::Entry) -> Result<Entry> {
             let btfproto::msg::Entry {
                 attr,
                 attr_timeout,
                 entry_timeout,
-            } = self;
+            } = attrs;
+            let attr = self.convert_ruid(attr)?;
             let BlockId { inode, generation } = attr.block_id;
             let attr = attr.stat()?;
             Ok(Entry {
@@ -77,40 +108,42 @@ mod private {
                 generation,
                 attr,
                 attr_flags: 0,
-                attr_timeout: *attr_timeout,
-                entry_timeout: *entry_timeout,
+                attr_timeout,
+                entry_timeout,
             })
         }
     }
 
-    fn block_on<F: Future>(future: F) -> F::Output
-    {
-        Handle::current().block_on(future)
-    }
-
-    pub struct FuseFs<P> {
-        provider: P,
-        uid_map: HashMap<u32, Arc<BlockPath>>,
-        fallback_path: Arc<BlockPath>,
-    }
-
-    impl<P> FuseFs<P> {
-        fn path(&self, uid: u32) -> &Arc<BlockPath> {
-            self.uid_map.get(&uid).unwrap_or(&self.fallback_path)
-        }
-    }
-
     impl<P: 'static + FsProvider> FuseFs<P> {
-        pub fn new(
-            provider: P,
-            uid_map: HashMap<u32, Arc<BlockPath>>,
-            fallback_path: Arc<BlockPath>,
-        ) -> Self {
-            Self {
-                provider,
-                uid_map,
-                fallback_path,
+        async fn load_authz_attrs(
+            provider: &P,
+            from: &Arc<BlockPath>,
+            path: &BlockPath,
+        ) -> Result<AuthzAttrs> {
+            let mut parent = SpecInodes::RootDir.into();
+            for component in path.components() {
+                let msg = Lookup {
+                    parent,
+                    name: component,
+                };
+                let LookupReply { inode, .. } = provider.lookup(from, msg).await?;
+                parent = inode;
             }
+            let msg = Open {
+                inode: parent,
+                flags: FlagValue::ReadOnly.into(),
+            };
+            let OpenReply { handle, .. } = provider.open(from, msg).await?;
+            let mut buf = BytesMut::new();
+            let msg = Read {
+                inode: parent,
+                handle,
+                offset: 0,
+                size: u64::MAX,
+            };
+            provider.read(from, msg, |data| buf.extend_from_slice(data))?;
+            let mut reader = buf.reader();
+            read_from(&mut reader).map_err(|err| err.into())
         }
     }
 
@@ -121,30 +154,40 @@ mod private {
         type Handle = btfproto::Handle;
 
         fn init(&self, _capable: FsOptions) -> io::Result<FsOptions> {
+            let provider_ref = &self.provider;
+            let default_path = self.path_by_luid.k2v().default();
+            let default_path_clone = default_path.clone();
+            let attrs = block_on(async move {
+                Self::load_authz_attrs(
+                    provider_ref,
+                    &default_path_clone,
+                    default_path_clone.as_ref(),
+                )
+                .await
+            })?;
+            let mut guard = self.ruid_by_path.write().display_err()?;
+            guard.insert(default_path.clone(), attrs.uid);
             Ok(FsOptions::empty())
         }
 
-        fn lookup(
-            &self,
-            ctx: &Context,
-            parent: Self::Inode,
-            name: &CStr,
-        ) -> IoResult<Entry> {
+        fn lookup(&self, ctx: &Context, parent: Self::Inode, name: &CStr) -> IoResult<Entry> {
             block_on(async move {
-                let path = self.path(ctx.uid);
+                let path = self.path_from_luid(ctx.uid);
                 let name = name.to_str().display_err()?;
                 let msg = Lookup { parent, name };
                 let entry = match self.provider.lookup(path, msg).await {
                     Ok(LookupReply { entry, .. }) => entry,
-                    Err(err) => return match err.downcast::<io::Error>() {
-                        Ok(err) => {
-                            debug!("lookup returned io::Error: {err}");
-                            Err(err)
-                        },
-                        Err(err) => Err(io::Error::new(io::ErrorKind::Other, err.to_string())),
+                    Err(err) => {
+                        return match err.downcast::<io::Error>() {
+                            Ok(err) => {
+                                debug!("lookup returned io::Error: {err}");
+                                Err(err)
+                            }
+                            Err(err) => Err(io::Error::new(io::ErrorKind::Other, err.to_string())),
+                        }
                     }
                 };
-                let entry = entry.fuse_entry()?;
+                let entry = self.fuse_entry(entry)?;
                 Ok(entry)
             })
         }
@@ -157,7 +200,7 @@ mod private {
             args: CreateIn,
         ) -> IoResult<(Entry, Option<Self::Handle>, OpenOptions)> {
             block_on(async move {
-                let path = self.path(ctx.uid);
+                let path = self.path_from_luid(ctx.uid);
                 let name = name.to_str().display_err()?;
                 let msg = Create {
                     parent,
@@ -167,11 +210,29 @@ mod private {
                     umask: args.umask,
                 };
                 let CreateReply { entry, handle, .. } = self.provider.create(path, msg).await?;
-                let entry = entry.fuse_entry()?;
+                let entry = self.fuse_entry(entry)?;
                 Ok((entry, Some(handle), OpenOptions::empty()))
             })
         }
 
+        fn mkdir(
+            &self,
+            ctx: &Context,
+            parent: Self::Inode,
+            name: &CStr,
+            mode: u32,
+            umask: u32,
+        ) -> io::Result<Entry> {
+            let args = CreateIn {
+                flags: FlagValue::Directory.value() as u32,
+                mode,
+                umask,
+                fuse_flags: 0,
+            };
+            let (entry, ..) = self.create(ctx, parent, name, args)?;
+            Ok(entry)
+        }
+
         fn open(
             &self,
             ctx: &Context,
@@ -180,7 +241,7 @@ mod private {
             _fuse_flags: u32,
         ) -> IoResult<(Option<Self::Handle>, OpenOptions)> {
             block_on(async move {
-                let path = self.path(ctx.uid);
+                let path = self.path_from_luid(ctx.uid);
                 let msg = Open {
                     inode,
                     flags: Flags::new(flags as i32),
@@ -217,7 +278,7 @@ mod private {
             _lock_owner: Option<u64>,
             _flags: u32,
         ) -> IoResult<usize> {
-            let path = self.path(ctx.uid);
+            let path = self.path_from_luid(ctx.uid);
             let msg = Read {
                 inode,
                 handle,
@@ -241,7 +302,7 @@ mod private {
             _fuse_flags: u32,
         ) -> IoResult<usize> {
             block_on(async move {
-                let path = self.path(ctx.uid);
+                let path = self.path_from_luid(ctx.uid);
                 let size: usize = size.try_into().display_err()?;
                 let WriteReply { written, .. } = self
                     .provider
@@ -259,7 +320,7 @@ mod private {
             _lock_owner: u64,
         ) -> io::Result<()> {
             block_on(async move {
-                let path = self.path(ctx.uid);
+                let path = self.path_from_luid(ctx.uid);
                 let msg = Flush { inode, handle };
                 self.provider.flush(path, msg).await?;
                 Ok(())
@@ -276,7 +337,7 @@ mod private {
             add_entry: &mut dyn FnMut(DirEntry) -> io::Result<usize>,
         ) -> io::Result<()> {
             block_on(async move {
-                let path = self.path(ctx.uid);
+                let path = self.path_from_luid(ctx.uid);
                 let msg = ReadDir {
                     inode,
                     handle,
@@ -286,16 +347,17 @@ mod private {
                 let ReadDirReply { entries, .. } = self.provider.read_dir(path, msg).await?;
                 let mut size: usize = size.try_into().display_err()?;
                 for (index, (name, entry)) in entries.into_iter().enumerate() {
-                    let inode = match entry.inode() {
-                        Some(inode) => inode,
-                        None => continue,
-                    };
+                    // We do not expose non-standard directory entries via FUSE.
+                    if entry.kind() == libc::DT_UNKNOWN {
+                        continue;
+                    }
+                    let inode = entry.inode();
                     let offset = (index as u64) + 1;
                     let dir_entry = DirEntry {
                         ino: inode,
                         offset,
                         type_: entry.kind() as u32,
-                        name: name.as_bytes()
+                        name: name.as_bytes(),
                     };
                     size = size.saturating_sub(add_entry(dir_entry)?);
                     if size == 0 {
@@ -315,7 +377,7 @@ mod private {
         ) -> io::Result<Entry> {
             debug!("link called");
             block_on(async move {
-                let path = self.path(ctx.uid);
+                let path = self.path_from_luid(ctx.uid);
                 let name = new_name.to_str().display_err()?;
                 let msg = Link {
                     inode,
@@ -323,24 +385,44 @@ mod private {
                     name,
                 };
                 let LinkReply { entry, .. } = self.provider.link(path, msg).await?;
-                let entry = entry.fuse_entry()?;
+                let entry = self.fuse_entry(entry)?;
                 Ok(entry)
             })
         }
 
         fn unlink(&self, ctx: &Context, parent: Self::Inode, name: &CStr) -> io::Result<()> {
             block_on(async move {
-                let path = self.path(ctx.uid);
+                let path = self.path_from_luid(ctx.uid);
                 let name = name.to_str().display_err()?;
-                let msg = Unlink {
-                    parent,
-                    name,
-                };
+                let msg = Unlink { parent, name };
                 self.provider.unlink(path, msg).await?;
                 Ok(())
             })
         }
 
+        fn rmdir(&self, ctx: &Context, parent: Self::Inode, name: &CStr) -> io::Result<()> {
+            self.unlink(ctx, parent, name)
+        }
+
+        fn rename(
+            &self,
+            ctx: &Context,
+            olddir: Self::Inode,
+            oldname: &CStr,
+            newdir: Self::Inode,
+            newname: &CStr,
+            _flags: u32,
+        ) -> io::Result<()> {
+            let Entry { inode, .. } = self.lookup(ctx, olddir, oldname)?;
+            let result = (move || {
+                self.link(ctx, inode, newdir, newname)?;
+                self.unlink(ctx, olddir, oldname)?;
+                Ok(())
+            })();
+            self.forget(ctx, inode, 1);
+            result
+        }
+
         fn getattr(
             &self,
             ctx: &Context,
@@ -348,12 +430,13 @@ mod private {
             handle: Option<Self::Handle>,
         ) -> IoResult<(stat64, Duration)> {
             block_on(async move {
-                let path = self.path(ctx.uid);
+                let path = self.path_from_luid(ctx.uid);
                 let msg = ReadMeta { inode, handle };
                 let ReadMetaReply {
                     meta, valid_for, ..
                 } = self.provider.read_meta(path, msg).await?;
-                let stat = meta.body().secrets()?.stat()?;
+                let attrs = self.convert_ruid(meta.body().secrets()?.clone())?;
+                let stat = attrs.stat()?;
                 Ok((stat, valid_for))
             })
         }
@@ -367,7 +450,7 @@ mod private {
             valid: SetattrValid,
         ) -> IoResult<(stat64, Duration)> {
             block_on(async move {
-                let path = self.path(ctx.uid);
+                let path = self.path_from_luid(ctx.uid);
                 let mut msg_attrs = Attrs::default();
                 let mut attrs_set = AttrsSet::none();
                 if valid.intersects(SetattrValid::MODE) {
@@ -418,7 +501,7 @@ mod private {
             handle: Self::Handle,
         ) -> IoResult<()> {
             block_on(async move {
-                let path = self.path(ctx.uid);
+                let path = self.path_from_luid(ctx.uid);
                 let msg = Flush { inode, handle };
                 self.provider.flush(path, msg).await?;
                 Ok(())
@@ -449,7 +532,7 @@ mod private {
                     error!("a non-zero mode argument was given to async_fallocate: {mode}");
                     return Err(io::Error::from_raw_os_error(libc::ENOTSUP));
                 }
-                let path = self.path(ctx.uid);
+                let path = self.path_from_luid(ctx.uid);
                 let msg = Allocate {
                     inode,
                     handle,
@@ -470,7 +553,7 @@ mod tests {
     use btfproto::Inode;
     use btfproto_tests::local_fs::{ConcreteFs, LocalFsTest};
     use fuse_backend_rs::api::filesystem::Context;
-    use std::{collections::HashMap, ffi::CString};
+    use std::ffi::CString;
     use tempdir::TempDir;
 
     struct FuseFsTest {
@@ -479,11 +562,11 @@ mod tests {
     }
 
     impl FuseFsTest {
-        fn new_empty() -> Self {
-            let case = LocalFsTest::new_empty();
+        async fn new_empty() -> Self {
+            let case = LocalFsTest::new_empty().await;
             let from = case.from().to_owned();
             let (dir, fs, ..) = case.into_parts();
-            let fs = FuseFs::new(fs, HashMap::new(), from);
+            let fs = FuseFs::new(fs, from);
             Self { _dir: dir, fs }
         }
 
@@ -502,8 +585,8 @@ mod tests {
 
     #[tokio::test]
     async fn lookup_file_exists() {
-        tokio::task::spawn_blocking(|| {
-            let case = FuseFsTest::new_empty();
+        let case = FuseFsTest::new_empty().await;
+        tokio::task::spawn_blocking(move || {
             let fuse_fs = case.fs();
             let root: Inode = SpecInodes::RootDir.into();
             let ctx = case.ctx();
@@ -524,7 +607,9 @@ mod tests {
             assert_eq!(expected.attr_flags, actual.attr_flags);
             assert_eq!(expected.attr_timeout, actual.attr_timeout);
             assert_eq!(expected.entry_timeout, actual.entry_timeout);
-        }).await.unwrap();
+        })
+        .await
+        .unwrap();
     }
 
     #[tokio::test]
@@ -543,8 +628,8 @@ mod tests {
             };
         }
 
+        let case = FuseFsTest::new_empty().await;
         tokio::task::spawn_blocking(move || {
-            let case = FuseFsTest::new_empty();
             let fuse_fs = case.fs();
             let root: Inode = SpecInodes::RootDir.into();
             let ctx = case.ctx();
@@ -561,8 +646,6 @@ mod tests {
             let actual = {
                 let mut actual = stat64::from(Attr::default());
                 actual.st_mode = 0o777;
-                actual.st_uid = 2372;
-                actual.st_gid = 2312;
                 actual.st_atime = 21323;
                 actual.st_mtime = 21290;
                 actual.st_ctime = 119200;
@@ -574,17 +657,15 @@ mod tests {
                 | SetattrValid::ATIME
                 | SetattrValid::MTIME
                 | SetattrValid::CTIME;
-            let (setattr_return, ..) = fuse_fs
-                .setattr(&ctx, inode, actual, handle, valid)
-                .unwrap();
+            let (setattr_return, ..) = fuse_fs.setattr(&ctx, inode, actual, handle, valid).unwrap();
             let (getattr_return, ..) = fuse_fs.getattr(&ctx, inode, handle).unwrap();
 
             check!(actual, entry, getattr_return, setattr_return, st_mode);
-            check!(actual, entry, getattr_return, setattr_return, st_uid);
-            check!(actual, entry, getattr_return, setattr_return, st_gid);
             check!(actual, entry, getattr_return, setattr_return, st_atime);
             check!(actual, entry, getattr_return, setattr_return, st_mtime);
             check!(actual, entry, getattr_return, setattr_return, st_ctime);
-        }).await.unwrap();
+        })
+        .await
+        .unwrap();
     }
 }

+ 221 - 226
crates/btfuse/src/main.rs

@@ -1,22 +1,21 @@
 mod fuse_daemon;
 use fuse_daemon::FuseDaemon;
+mod config;
 mod fuse_fs;
 
+use config::{Config, ConfigRef, EnvVars};
+
 use btfproto::{local_fs::LocalFs, server::FsProvider};
 use btlib::{
     crypto::{
         tpm::{TpmCredStore, TpmCreds},
         CredStore, Creds, CredsPriv,
     },
-    BlockPath, Result,
+    Result,
 };
 use core::future::Future;
-use log::{error};
-use serde_json::from_str;
 use std::{
-    collections::HashMap,
     env::VarError,
-    ffi::OsString,
     fs::{self},
     io,
     path::{Path, PathBuf},
@@ -28,10 +27,19 @@ use tss_esapi::{
     Context,
 };
 
-const DEFAULT_TABRMD: &str = "bus_type=session";
-const MOUNT_OPTIONS: &str = "default_permissions";
-const TABRMD_ENVVAR: &str = "BT_TABRMD";
-const UIDMAP_ENVVAR: &str = "BT_UIDMAP";
+const ENVVARS: EnvVars = EnvVars {
+    tabrmd: "BT_TABRMD",
+    mnt_options: "BT_MNTOPTS",
+};
+
+const DEFAULT_CONFIG: ConfigRef<'static> = ConfigRef {
+    block_dir: "./bt",
+    mnt_dir: "./mnt",
+    tpm_state_file: "./tpm_state",
+    tabrmd: "bus_type=session",
+    mnt_options: "default_permissions",
+    threads: 8,
+};
 
 trait PathExt {
     fn try_create_dir(&self) -> io::Result<()>;
@@ -49,23 +57,6 @@ impl<T: AsRef<Path>> PathExt for T {
     }
 }
 
-fn tpm_state_path<T: AsRef<Path>>(main_dir: T) -> PathBuf {
-    main_dir.as_ref().join("tpm_state")
-}
-
-fn block_dir<T: AsRef<Path>>(main_dir: T) -> PathBuf {
-    main_dir.as_ref().join("bt")
-}
-
-fn parse_uid_map(uid_map: &str) -> Result<HashMap<u32, Arc<BlockPath>>> {
-    let map: HashMap<u32, BlockPath> = from_str(uid_map)?;
-    let mut output = HashMap::with_capacity(map.len());
-    for (key, value) in map.into_iter() {
-        output.insert(key, Arc::new(value));
-    }
-    Ok(output)
-}
-
 fn node_creds(state_file: PathBuf, tabrmd_cfg: &str) -> Result<TpmCreds> {
     let context = Context::new(TctiNameConf::Tabrmd(TabrmdConfig::from_str(tabrmd_cfg)?))?;
     let cred_store = TpmCredStore::new(context, state_file)?;
@@ -85,22 +76,21 @@ fn provider<C: 'static + Creds + Send + Sync>(
     }
 }
 
-fn run_daemon(
-    main_dir: OsString,
-    tabrmd_string: Option<String>,
-    uid_map: Option<String>,
-    tpm_state_file: Option<PathBuf>,
-) -> impl Send + Sync + Future<Output = ()> {
-    let main_dir = PathBuf::from(main_dir);
-    let tabrmd_cfg = tabrmd_string
-        .as_ref()
-        .map_or(DEFAULT_TABRMD, |s| s.as_str());
-    let uid_map = {
-        let uid_map = uid_map.unwrap_or_else(|| "{}".to_owned());
-        parse_uid_map(&uid_map).unwrap()
-    };
-    let tpm_state_file = tpm_state_file.unwrap_or_else(|| tpm_state_path(&main_dir));
-    let node_creds = node_creds(tpm_state_file, tabrmd_cfg).expect("failed to get node creds");
+fn from_envvar(envvar_name: &str) -> Option<String> {
+    match std::env::var(envvar_name) {
+        Ok(uid_map) => Some(uid_map),
+        Err(err) => match err {
+            VarError::NotPresent => None,
+            VarError::NotUnicode(_) => {
+                panic!("environment variable {envvar_name} contained non-unicode characters");
+            }
+        },
+    }
+}
+
+fn run_daemon(config: Config) -> impl Send + Sync + Future<Output = ()> {
+    let node_creds =
+        node_creds(config.tpm_state_file, &config.tabrmd).expect("failed to get node creds");
     let fallback_path = {
         let writecap = node_creds
             .writecap()
@@ -108,12 +98,9 @@ fn run_daemon(
             .unwrap();
         Arc::new(writecap.bind_path())
     };
-    let provider = {
-        let btdir = block_dir(&main_dir);
-        provider(btdir, node_creds).expect("failed to create FS provider")
-    };
+    let provider = provider(config.block_dir, node_creds).expect("failed to create FS provider");
 
-    let mut daemon = FuseDaemon::new(main_dir, uid_map, fallback_path, provider)
+    let mut daemon = FuseDaemon::new(config.mnt_dir, fallback_path, provider)
         .expect("failed to create FUSE daemon");
     async move { daemon.finished().await }
 }
@@ -121,19 +108,14 @@ fn run_daemon(
 #[tokio::main]
 async fn main() {
     env_logger::init();
-    let main_dir = std::env::args_os().nth(1).expect("no mount point given");
-    let tabrmd_string = std::env::var(TABRMD_ENVVAR).ok();
-    let uid_map = match std::env::var(UIDMAP_ENVVAR) {
-        Ok(uid_map) => Some(uid_map),
-        Err(err) => match err {
-            VarError::NotPresent => None,
-            VarError::NotUnicode(_) => {
-                error!("environment variable {UIDMAP_ENVVAR} contained non-unicode characters");
-                return;
-            }
-        },
-    };
-    run_daemon(main_dir, tabrmd_string, uid_map, None).await;
+    let mut args = std::env::args_os().skip(1).map(PathBuf::from);
+    let builder = Config::builder()
+        .with_block_dir(args.next())
+        .with_mnt_dir(args.next())
+        .with_tabrmd(from_envvar(ENVVARS.tabrmd))
+        .with_mnt_options(from_envvar(ENVVARS.mnt_options));
+    let config = builder.build();
+    run_daemon(config).await;
 }
 
 #[cfg(test)]
@@ -149,9 +131,9 @@ mod test {
             set_permissions, write, Permissions, ReadDir,
         },
         os::unix::fs::PermissionsExt,
+        sync::mpsc,
         thread::JoinHandle,
         time::Duration,
-        sync::mpsc,
     };
     use swtpm_harness::SwtpmHarness;
     use tempdir::TempDir;
@@ -195,45 +177,51 @@ mod test {
     const ROOT_PASSWD: &str = "password";
 
     struct TestCase {
-        _temp_dir: TempDir,
-        mnt_path: PathBuf,
+        config: Config,
         handle: Option<JoinHandle<()>>,
+        node_principal: OsString,
+        // Note that the drop order of these fields is significant.
+        _cred_store: TpmCredStore,
+        _swtpm: SwtpmHarness,
+        _temp_dir: TempDir,
     }
 
     impl TestCase {
         fn new() -> TestCase {
             let tmp = TempDir::new("btfuse").unwrap();
-            let mnt_path = FuseDaemon::mnt_path(tmp.path());
-            let main_dir = OsString::from(tmp.path().to_owned());
             let (mounted_tx, mounted_rx) = mpsc::channel();
-            let handle = std::thread::spawn(move || Self::run(main_dir, mounted_tx));
+            let (swtpm, cred_store) = Self::swtpm();
+            let config = Config::builder()
+                .with_block_dir(Some(tmp.path().join("bt")))
+                .with_mnt_dir(Some(tmp.path().join("mnt")))
+                .with_tpm_state_file(Some(swtpm.state_path().to_owned().into()))
+                .with_tabrmd(Some(swtpm.tabrmd_config().to_owned()))
+                .build();
+            let config_clone = config.clone();
+            let handle = std::thread::spawn(move || Self::run(mounted_tx, config_clone));
             match TIMEOUT {
                 Some(duration) => mounted_rx.recv_timeout(duration).unwrap(),
                 None => mounted_rx.recv().unwrap(),
             };
+            let node_principal =
+                OsString::from(cred_store.node_creds().unwrap().principal().to_string());
             Self {
-                _temp_dir: tmp,
-                mnt_path,
+                config,
                 handle: Some(handle),
+                node_principal,
+                _temp_dir: tmp,
+                _swtpm: swtpm,
+                _cred_store: cred_store,
             }
         }
 
-        fn run(main_dir: OsString, mounted_tx: mpsc::Sender<()>) {
-            let swtpm = Self::swtpm();
-            let state_path = swtpm.state_path().to_owned();
-            let tabrmd_string = Some(swtpm.tabrmd_config().to_owned());
-            let uid_map = None;
+        fn run(mounted_tx: mpsc::Sender<()>, config: Config) {
             let runtime = tokio::runtime::Builder::new_current_thread()
                 .build()
                 .unwrap();
             // run_daemon can only be called in the context of a runtime, hence the need to call
             // spawn_blocking.
-            let started = runtime.spawn_blocking(|| run_daemon(
-                main_dir,
-                tabrmd_string,
-                uid_map,
-                Some(state_path),
-            ));
+            let started = runtime.spawn_blocking(|| run_daemon(config));
             let future = runtime.block_on(started).unwrap();
 
             // The file system is mounted before run_daemon returns.
@@ -241,7 +229,7 @@ mod test {
             runtime.block_on(future);
         }
 
-        fn swtpm() -> SwtpmHarness {
+        fn swtpm() -> (SwtpmHarness, TpmCredStore) {
             let swtpm = SwtpmHarness::new().unwrap();
             let state_path: PathBuf = swtpm.state_path().to_owned();
             let cred_store = {
@@ -257,11 +245,11 @@ mod test {
             cred_store
                 .assign_node_writecap(&mut node_creds, writecap)
                 .unwrap();
-            swtpm
+            (swtpm, cred_store)
         }
 
-        fn mnt_path(&self) -> &PathBuf {
-            &self.mnt_path
+        fn mnt_dir(&self) -> &PathBuf {
+            &self.config.mnt_dir
         }
 
         fn wait(&mut self) {
@@ -278,10 +266,14 @@ mod test {
             if self.handle.is_none() {
                 return;
             }
-            let mnt_path = self.mnt_path();
+            let mnt_path = self.mnt_dir();
             unmount(mnt_path);
             self.wait();
         }
+
+        fn initial_contents(&self) -> Vec<&OsStr> {
+            vec![&self.node_principal]
+        }
     }
 
     impl Drop for TestCase {
@@ -310,7 +302,7 @@ mod test {
         const EXPECTED: &[u8] =
             b"The paths to failure are uncountable, yet to success there is but one.";
         let case = TestCase::new();
-        let file_path = case.mnt_path().join("file");
+        let file_path = case.mnt_dir().join("file");
 
         write(&file_path, EXPECTED)?;
 
@@ -323,15 +315,16 @@ mod 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 case = TestCase::new();
-        let mnt_path = case.mnt_path();
+        let mut expected = case.initial_contents();
+        expected.push(file_name);
+        let mnt_path = case.mnt_dir();
         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));
+        assert!(first.eq(expected.iter().map(|e| *e)));
         let second = file_names(read_dir(&mnt_path).expect("read_dir failed"));
         assert!(second.eq(expected));
     }
@@ -341,165 +334,167 @@ mod test {
         const DATA: &[u8] = b"The universe is hostile, so impersonal. Devour to survive";
         let file_name = OsStr::new("tool_lyrics.txt");
         let case = TestCase::new();
-        let mnt_path = case.mnt_path();
+        let mnt_path = case.mnt_dir();
         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))
+        let expected = case.initial_contents();
+        let actual = file_names(read_dir(&mnt_path).expect("read_dir failed"));
+        assert!(actual.eq(expected));
     }
 
-    //#[test]
-    //fn hard_link_then_remove() {
-    //    const EXPECTED: &[u8] = b"And the lives we've reclaimed";
-    //    let name1 = OsStr::new("refugee_lyrics.txt");
-    //    let name2 = OsStr::new("rise_against_lyrics.txt");
-    //    let case = TestCase::new();
-    //    let mnt_path = case.mnt_path();
-    //    let path1 = mnt_path.join(name1);
-    //    let path2 = mnt_path.join(name2);
-    //    write(&path1, EXPECTED).expect("write failed");
-
-    //    hard_link(&path1, &path2).expect("hard_link failed");
-    //    remove_file(&path1).expect("remove_file failed");
-
-    //    let actual = read(&path2).expect("read failed");
-    //    assert_eq!(EXPECTED, actual);
-    //}
+    #[test]
+    fn hard_link_then_remove() {
+        const EXPECTED: &[u8] = b"And the lives we've reclaimed";
+        let name1 = OsStr::new("refugee_lyrics.txt");
+        let name2 = OsStr::new("rise_against_lyrics.txt");
+        let case = TestCase::new();
+        let mnt_path = case.mnt_dir();
+        let path1 = mnt_path.join(name1);
+        let path2 = mnt_path.join(name2);
+        write(&path1, EXPECTED).expect("write failed");
 
-    //#[test]
-    //fn hard_link_then_remove_both() {
-    //    const EXPECTED: &[u8] = b"And the lives we've reclaimed";
-    //    let name1 = OsStr::new("refugee_lyrics.txt");
-    //    let name2 = OsStr::new("rise_against_lyrics.txt");
-    //    let case = TestCase::new();
-    //    let mnt_path = case.mnt_path();
-    //    let path1 = mnt_path.join(name1);
-    //    let path2 = mnt_path.join(name2);
-    //    write(&path1, EXPECTED).expect("write failed");
-
-    //    hard_link(&path1, &path2).expect("hard_link failed");
-    //    remove_file(&path1).expect("remove_file on path1 failed");
-    //    remove_file(&path2).expect("remove_file on path2 failed");
-
-    //    let expected: [&OsStr; 0] = [];
-    //    assert!(file_names(read_dir(&mnt_path).expect("read_dir failed")).eq(expected));
-    //}
+        hard_link(&path1, &path2).expect("hard_link failed");
+        remove_file(&path1).expect("remove_file failed");
 
-    //#[test]
-    //fn set_mode_bits() {
-    //    const EXPECTED: u32 = libc::S_IFREG | 0o777;
-    //    let case = TestCase::new();
-    //    let file_path = case.mnt_path().join("bagobits");
-    //    write(&file_path, []).expect("write failed");
-    //    let original = metadata(&file_path)
-    //        .expect("metadata failed")
-    //        .permissions()
-    //        .mode();
-    //    assert_ne!(EXPECTED, original);
-
-    //    set_permissions(&file_path, Permissions::from_mode(EXPECTED))
-    //        .expect("set_permissions failed");
-
-    //    let actual = metadata(&file_path)
-    //        .expect("metadata failed")
-    //        .permissions()
-    //        .mode();
-    //    assert_eq!(EXPECTED, actual);
-    //}
+        let actual = read(&path2).expect("read failed");
+        assert_eq!(EXPECTED, actual);
+    }
 
-    //#[test]
-    //fn create_directory() {
-    //    const EXPECTED: &str = "etc";
-    //    let case = TestCase::new();
-    //    let mnt_path = case.mnt_path();
-    //    let dir_path = mnt_path.join(EXPECTED);
+    #[test]
+    fn hard_link_then_remove_both() {
+        const EXPECTED: &[u8] = b"And the lives we've reclaimed";
+        let name1 = OsStr::new("refugee_lyrics.txt");
+        let name2 = OsStr::new("rise_against_lyrics.txt");
+        let case = TestCase::new();
+        let mnt_path = case.mnt_dir();
+        let path1 = mnt_path.join(name1);
+        let path2 = mnt_path.join(name2);
+        write(&path1, EXPECTED).expect("write failed");
 
-    //    create_dir(&dir_path).expect("create_dir failed");
+        hard_link(&path1, &path2).expect("hard_link failed");
+        remove_file(&path1).expect("remove_file on path1 failed");
+        remove_file(&path2).expect("remove_file on path2 failed");
 
-    //    let actual = file_names(read_dir(mnt_path).expect("read_dir failed"));
-    //    assert!(actual.eq([EXPECTED]));
-    //}
+        let expected = case.initial_contents();
+        assert!(file_names(read_dir(&mnt_path).expect("read_dir failed")).eq(expected));
+    }
 
-    //#[test]
-    //fn create_file_under_new_directory() {
-    //    const DIR_NAME: &str = "etc";
-    //    const FILE_NAME: &str = "file";
-    //    let case = TestCase::new();
-    //    let mnt_path = case.mnt_path();
-    //    let dir_path = mnt_path.join(DIR_NAME);
-    //    let file_path = dir_path.join(FILE_NAME);
+    #[test]
+    fn set_mode_bits() {
+        const EXPECTED: u32 = libc::S_IFREG | 0o777;
+        let case = TestCase::new();
+        let file_path = case.mnt_dir().join("bagobits");
+        write(&file_path, []).expect("write failed");
+        let original = metadata(&file_path)
+            .expect("metadata failed")
+            .permissions()
+            .mode();
+        assert_ne!(EXPECTED, original);
+
+        set_permissions(&file_path, Permissions::from_mode(EXPECTED))
+            .expect("set_permissions failed");
+
+        let actual = metadata(&file_path)
+            .expect("metadata failed")
+            .permissions()
+            .mode();
+        assert_eq!(EXPECTED, actual);
+    }
 
-    //    create_dir(&dir_path).expect("create_dir failed");
-    //    write(&file_path, []).expect("write failed");
+    #[test]
+    fn create_directory() {
+        const EXPECTED: &str = "etc";
+        let case = TestCase::new();
+        let mnt_path = case.mnt_dir();
+        let dir_path = mnt_path.join(EXPECTED);
+        let mut expected = case.initial_contents();
+        expected.push(OsStr::new(EXPECTED));
 
-    //    let actual = file_names(read_dir(dir_path).expect("read_dir failed"));
-    //    assert!(actual.eq([FILE_NAME]));
-    //}
+        create_dir(&dir_path).expect("create_dir failed");
 
-    //#[test]
-    //fn create_then_remove_directory() {
-    //    const DIR_NAME: &str = "etc";
-    //    let case = TestCase::new();
-    //    let mnt_path = case.mnt_path();
-    //    let dir_path = mnt_path.join(DIR_NAME);
+        let actual = file_names(read_dir(mnt_path).expect("read_dir failed"));
+        assert!(actual.eq(expected));
+    }
 
-    //    create_dir(&dir_path).expect("create_dir failed");
-    //    remove_dir(&dir_path).expect("remove_dir failed");
+    #[test]
+    fn create_file_under_new_directory() {
+        const DIR_NAME: &str = "etc";
+        const FILE_NAME: &str = "file";
+        let case = TestCase::new();
+        let mnt_path = case.mnt_dir();
+        let dir_path = mnt_path.join(DIR_NAME);
+        let file_path = dir_path.join(FILE_NAME);
 
-    //    let actual = file_names(read_dir(&mnt_path).expect("read_dir failed"));
-    //    const EMPTY: [&str; 0] = [""; 0];
-    //    assert!(actual.eq(EMPTY));
-    //}
+        create_dir(&dir_path).expect("create_dir failed");
+        write(&file_path, []).expect("write failed");
 
-    //#[test]
-    //fn read_only_dir_cant_create_subdir() {
-    //    const DIR_NAME: &str = "etc";
-    //    let case = TestCase::new();
-    //    let dir_path = case.mnt_path().join(DIR_NAME);
-    //    create_dir(&dir_path).expect("create_dir failed");
-    //    set_permissions(&dir_path, Permissions::from_mode(libc::S_IFDIR | 0o444))
-    //        .expect("set_permissions failed");
+        let actual = file_names(read_dir(dir_path).expect("read_dir failed"));
+        assert!(actual.eq([FILE_NAME]));
+    }
 
-    //    let result = create_dir(dir_path.join("sub"));
+    #[test]
+    fn create_then_remove_directory() {
+        const DIR_NAME: &str = "etc";
+        let case = TestCase::new();
+        let mnt_path = case.mnt_dir();
+        let dir_path = mnt_path.join(DIR_NAME);
 
-    //    let err = result.err().expect("create_dir returned `Ok`");
-    //    let os_err = err.raw_os_error().expect("raw_os_error was empty");
-    //    assert_eq!(os_err, libc::EACCES);
-    //}
+        create_dir(&dir_path).expect("create_dir failed");
+        remove_dir(&dir_path).expect("remove_dir failed");
 
-    //#[test]
-    //fn read_only_dir_cant_remove_subdir() {
-    //    const DIR_NAME: &str = "etc";
-    //    let case = TestCase::new();
-    //    let dir_path = case.mnt_path().join(DIR_NAME);
-    //    let sub_path = dir_path.join("sub");
-    //    create_dir(&dir_path).expect("create_dir failed");
-    //    create_dir(&sub_path).expect("create_dir failed");
-    //    set_permissions(&dir_path, Permissions::from_mode(libc::S_IFDIR | 0o444))
-    //        .expect("set_permissions failed");
-
-    //    let result = remove_dir(&sub_path);
-
-    //    let err = result.err().expect("remove_dir returned `Ok`");
-    //    let os_err = err.raw_os_error().expect("raw_os_error was empty");
-    //    assert_eq!(os_err, libc::EACCES);
-    //}
+        let actual = file_names(read_dir(&mnt_path).expect("read_dir failed"));
+        assert!(actual.eq(case.initial_contents()));
+    }
 
-    //#[test]
-    //fn rename_file() {
-    //    const FILE_NAME: &str = "parabola.txt";
-    //    const EXPECTED: &[u8] = b"We are eternal all this pain is an illusion";
-    //    let case = TestCase::new();
-    //    let src_path = case.mnt_path().join(FILE_NAME);
-    //    let dst_path = case.mnt_path().join("parabola_lyrics.txt");
-
-    //    write(&src_path, EXPECTED).unwrap();
-    //    rename(&src_path, &dst_path).unwrap();
-
-    //    let actual = read(&dst_path).unwrap();
-    //    assert_eq!(EXPECTED, actual)
-    //}
+    #[test]
+    fn read_only_dir_cant_create_subdir() {
+        const DIR_NAME: &str = "etc";
+        let case = TestCase::new();
+        let dir_path = case.mnt_dir().join(DIR_NAME);
+        create_dir(&dir_path).expect("create_dir failed");
+        set_permissions(&dir_path, Permissions::from_mode(libc::S_IFDIR | 0o444))
+            .expect("set_permissions failed");
+
+        let result = create_dir(dir_path.join("sub"));
+
+        let err = result.err().expect("create_dir returned `Ok`");
+        let os_err = err.raw_os_error().expect("raw_os_error was empty");
+        assert_eq!(os_err, libc::EACCES);
+    }
+
+    #[test]
+    fn read_only_dir_cant_remove_subdir() {
+        const DIR_NAME: &str = "etc";
+        let case = TestCase::new();
+        let dir_path = case.mnt_dir().join(DIR_NAME);
+        let sub_path = dir_path.join("sub");
+        create_dir(&dir_path).expect("create_dir failed");
+        create_dir(&sub_path).expect("create_dir failed");
+        set_permissions(&dir_path, Permissions::from_mode(libc::S_IFDIR | 0o444))
+            .expect("set_permissions failed");
+
+        let result = remove_dir(&sub_path);
+
+        let err = result.err().expect("remove_dir returned `Ok`");
+        let os_err = err.raw_os_error().expect("raw_os_error was empty");
+        assert_eq!(os_err, libc::EACCES);
+    }
+
+    #[test]
+    fn rename_file() {
+        const FILE_NAME: &str = "parabola.txt";
+        const EXPECTED: &[u8] = b"We are eternal all this pain is an illusion";
+        let case = TestCase::new();
+        let src_path = case.mnt_dir().join(FILE_NAME);
+        let dst_path = case.mnt_dir().join("parabola_lyrics.txt");
+
+        write(&src_path, EXPECTED).unwrap();
+        rename(&src_path, &dst_path).unwrap();
+
+        let actual = read(&dst_path).unwrap();
+        assert_eq!(EXPECTED, actual)
+    }
 }

+ 126 - 2
crates/btlib/src/block_path.rs

@@ -1,12 +1,27 @@
-use crate::Principal;
+use crate::{
+    crypto::{BtHasher, HashKind},
+    Principal, Result,
+};
+use core::hash::Hash;
 use serde::{Deserialize, Serialize};
-use std::fmt::Display;
+use std::{fmt::Display, hash::Hasher};
 
 pub use private::{BlockPath, BlockPathError};
 
 mod private {
     use super::*;
 
+    #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Default, Hash)]
+    pub struct RelBlockPath {
+        components: Vec<String>,
+    }
+
+    impl RelBlockPath {
+        pub fn components(&self) -> impl Iterator<Item = &str> {
+            self.components.iter().map(|e| e.as_str())
+        }
+    }
+
     /// An identifier for a block in a tree.
     #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Default, Hash)]
     pub struct BlockPath {
@@ -107,6 +122,35 @@ mod private {
         pub fn pop_component(&mut self) -> Option<String> {
             self.components.pop()
         }
+
+        pub fn relative_to(&self, rel_to: &BlockPath) -> Result<RelBlockPath> {
+            if self.root != rel_to.root || self.components.len() < rel_to.components.len() {
+                return Err(BlockPathError::NotContained.into());
+            }
+            let option = self
+                .components()
+                .zip(rel_to.components())
+                .find(|(left, right)| left != right);
+            if option.is_some() {
+                return Err(BlockPathError::NotContained.into());
+            }
+            let start = rel_to.components.len();
+            let mut components = Vec::with_capacity(self.components.len() - start + 1);
+            components.extend_from_slice(&self.components[start..]);
+            Ok(RelBlockPath { components })
+        }
+
+        /// Returns the port to use if this path is used as the bind path of a process.
+        pub fn port(&self) -> Result<u16> {
+            let mut hasher = BtHasher::new(HashKind::Sha2_256)?;
+            self.hash(&mut hasher);
+            let hash = hasher.finish();
+            // We compute a port in the dynamic range [49152, 65535] as defined by RFC 6335.
+            const NUM_RES_PORTS: u16 = 49153;
+            const PORTS_AVAIL: u64 = (u16::MAX - NUM_RES_PORTS) as u64;
+            let port = NUM_RES_PORTS + (hash % PORTS_AVAIL) as u16;
+            Ok(port)
+        }
     }
 
     impl<'s> TryFrom<&'s str> for BlockPath {
@@ -161,6 +205,8 @@ mod private {
         EmptyComponent,
         /// Occurs when the leading component of a path is not in the correct format.
         InvalidLeadingComponent,
+        /// Occurs when one path was expected to be contained in another, but that was not the case.
+        NotContained,
     }
 
     impl Display for BlockPathError {
@@ -178,9 +224,14 @@ mod private {
                 BlockPathError::InvalidLeadingComponent => {
                     formatter.write_str("invalid leading path component")
                 }
+                BlockPathError::NotContained => {
+                    formatter.write_str("one path was not contained in another")
+                }
             }
         }
     }
+
+    impl std::error::Error for BlockPathError {}
 }
 
 #[cfg(test)]
@@ -295,4 +346,77 @@ mod tests {
         *second.mut_root() = Principal(VarHash::Sha2_256(PRINCIPAL2.into()));
         assert!(!first.contains(&second));
     }
+
+    #[test]
+    fn relative_to_self_contained_in_rel_to() {
+        let sich = make_path(vec!["sub", "alpha", "sub", "beta"]);
+        let rel_to = make_path(vec!["sub", "alpha"]);
+
+        let relative = sich.relative_to(&rel_to).unwrap();
+
+        let relative: Vec<_> = relative.components().collect();
+        assert_eq!(&["sub", "beta"], relative.as_slice());
+    }
+
+    #[test]
+    fn relative_to_no_difference_is_ok() {
+        let sich = make_path(vec!["sub", "alpha"]);
+        let rel_to = make_path(vec!["sub", "alpha"]);
+
+        let relative = sich.relative_to(&rel_to).unwrap();
+
+        let relative: Vec<_> = relative.components().collect();
+        assert_eq!(&[""; 0], relative.as_slice());
+    }
+
+    #[test]
+    fn relative_to_roots_differ_is_err() {
+        let sich = make_path(vec!["etc"]);
+        let mut rel_to = make_path(vec!["etc"]);
+        let default = Principal::default();
+        assert_ne!(sich.root(), &default);
+        *(rel_to.mut_root()) = default;
+
+        let result = sich.relative_to(&rel_to);
+
+        let err = result.err().unwrap().downcast::<BlockPathError>().unwrap();
+        let matched = if let BlockPathError::NotContained = err {
+            true
+        } else {
+            false
+        };
+        assert!(matched)
+    }
+
+    #[test]
+    fn relative_to_self_shorter_is_err() {
+        let sich = make_path(vec!["etc"]);
+        let rel_to = make_path(vec!["etc", "fstab"]);
+
+        let result = sich.relative_to(&rel_to);
+
+        let err = result.err().unwrap().downcast::<BlockPathError>().unwrap();
+        let matched = if let BlockPathError::NotContained = err {
+            true
+        } else {
+            false
+        };
+        assert!(matched)
+    }
+
+    #[test]
+    fn relative_to_not_contained_is_err() {
+        let sich = make_path(vec!["etc"]);
+        let rel_to = make_path(vec!["etsy"]);
+
+        let result = sich.relative_to(&rel_to);
+
+        let err = result.err().unwrap().downcast::<BlockPathError>().unwrap();
+        let matched = if let BlockPathError::NotContained = err {
+            true
+        } else {
+            false
+        };
+        assert!(matched)
+    }
 }

+ 0 - 2590
crates/btlib/src/blocktree.rs

@@ -1,2590 +0,0 @@
-use btserde::{read_from, write_to};
-use fuse_backend_rs::{
-    abi::fuse_abi::{stat64, statvfs64, CreateIn},
-    api::filesystem::{
-        Context, DirEntry as FuseDirEntry, Entry, FileSystem, FsOptions, OpenOptions, SetattrValid,
-    },
-};
-use log::{debug, error, warn};
-use positioned_io::Size;
-use serde::{Deserialize, Serialize};
-use std::{
-    collections::{
-        hash_map::{self, HashMap},
-        BTreeMap,
-    },
-    ffi::CStr,
-    fmt::{Display, Formatter},
-    fs::File,
-    io::{self, Seek, SeekFrom, Write},
-    path::{Path, PathBuf},
-    sync::{
-        atomic::{AtomicU64, Ordering},
-        Mutex, RwLock, RwLockWriteGuard,
-    },
-    time::Duration,
-};
-
-use crate::{
-    accessor::Accessor,
-    bterr,
-    crypto::{Creds, Decrypter, Signer},
-    error::{BtErr, DisplayErr, IoErr},
-    BlockAccessor, BlockError, BlockMeta, BlockOpenOptions, BlockPath, BlockReader, BlockRecord,
-    BoxInIoErr, DirEntry, DirEntryKind, Directory, Epoch, FileBlock, FlushMeta, MetaAccess,
-    MetaReader, Positioned, Principaled, ReadDual, Result, SeekFromExt, Split, TrySeek, WriteDual,
-};
-
-pub use private::{Blocktree, ModeAuthorizer, SpecInodes};
-
-mod private {
-    use super::*;
-
-    type Inode = u64;
-    type Handle = u64;
-
-    #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
-    pub enum Error {
-        NotOpen(Inode),
-        InvalidHandle { handle: u64, inode: u64 },
-        NoHandlesAvailable(Inode),
-        InodeNotFound(Inode),
-    }
-
-    impl Display for Error {
-        fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
-            match self {
-                Error::NotOpen(inode) => write!(f, "inode {inode} is not open"),
-                Error::InvalidHandle { handle, inode } => {
-                    write!(f, "invalid handle {handle} for inode {inode}")
-                }
-                Error::NoHandlesAvailable(inode) => {
-                    write!(f, "no handles are available for inode {inode}")
-                }
-                Error::InodeNotFound(inode) => write!(f, "inode {inode} could not be found"),
-            }
-        }
-    }
-
-    impl std::error::Error for Error {}
-
-    #[repr(u64)]
-    pub enum SpecInodes {
-        RootDir = 1,
-        Sb = 2,
-        FirstFree = 11,
-    }
-
-    impl From<SpecInodes> for Inode {
-        fn from(special: SpecInodes) -> Self {
-            special as Inode
-        }
-    }
-
-    #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
-    #[repr(u32)] // This type needs to match `libc::mode_t`.
-    /// The type of a file (regular, directory, etc).
-    enum FileType {
-        /// Directory.
-        Dir = libc::S_IFDIR,
-        /// Regular file.
-        Reg = libc::S_IFREG,
-    }
-
-    impl FileType {
-        /// Returns the underlying mode bits for this file type.
-        fn value(self) -> libc::mode_t {
-            self as libc::mode_t
-        }
-
-        /// Attempts to convert the given mode bits into a `FileType` enum value.
-        fn from_value(value: libc::mode_t) -> Result<Self> {
-            if (value & libc::S_IFDIR) != 0 {
-                return Ok(FileType::Dir);
-            }
-            if (value & libc::S_IFREG) != 0 {
-                return Ok(FileType::Reg);
-            }
-            Err(bterr!("unknown file type: 0o{value:0o}"))
-        }
-
-        fn dir_entry_kind(self) -> DirEntryKind {
-            match self {
-                Self::Dir => DirEntryKind::Directory,
-                Self::Reg => DirEntryKind::File,
-            }
-        }
-    }
-
-    impl From<FileType> for libc::mode_t {
-        fn from(file_type: FileType) -> Self {
-            file_type.value()
-        }
-    }
-
-    impl TryFrom<libc::mode_t> for FileType {
-        type Error = crate::Error;
-        fn try_from(value: libc::mode_t) -> Result<Self> {
-            Self::from_value(value)
-        }
-    }
-
-    impl From<FileType> for DirEntryKind {
-        fn from(value: FileType) -> Self {
-            value.dir_entry_kind()
-        }
-    }
-
-    /// This type provides context for an authorization decision as to whether a given process will
-    /// be allowed to access a block.
-    pub struct AuthzContext<'a> {
-        /// The user ID of the process being authorized.
-        pub uid: u32,
-        /// The group ID of the process being authorized.
-        pub gid: u32,
-        /// The process ID of the process being authorized.
-        pub pid: libc::pid_t,
-        /// A reference to the metadata of a block, the access to which is being authorized.
-        pub meta: &'a BlockMeta,
-    }
-
-    impl<'a> AuthzContext<'a> {
-        pub fn new(ctx: &Context, meta: &'a BlockMeta) -> AuthzContext<'a> {
-            AuthzContext {
-                uid: ctx.uid,
-                gid: ctx.gid,
-                pid: ctx.pid,
-                meta,
-            }
-        }
-    }
-
-    /// A trait for types which can render authorization decisions.
-    pub trait Authorizer {
-        /// Returns [Ok] if read authorization is granted, and [Err] otherwise.
-        fn can_read(&self, ctx: &AuthzContext<'_>) -> io::Result<()>;
-        /// Returns [Ok] if write authorization is granted, and [Err] otherwise.
-        fn can_write(&self, ctx: &AuthzContext<'_>) -> io::Result<()>;
-        /// Returns [Ok] if execute authorization is granted, and [Err] otherwise.
-        fn can_exec(&self, ctx: &AuthzContext<'_>) -> io::Result<()>;
-    }
-
-    trait AuthorizerExt: Authorizer {
-        fn read_allowed(&self, ctx: &Context, meta: &BlockMeta) -> io::Result<()> {
-            Authorizer::can_read(self, &AuthzContext::new(ctx, meta))
-        }
-
-        fn write_allowed(&self, ctx: &Context, meta: &BlockMeta) -> io::Result<()> {
-            Authorizer::can_write(self, &AuthzContext::new(ctx, meta))
-        }
-
-        fn exec_allowed(&self, ctx: &Context, meta: &BlockMeta) -> io::Result<()> {
-            Authorizer::can_exec(self, &AuthzContext::new(ctx, meta))
-        }
-    }
-
-    impl<T: Authorizer> AuthorizerExt for T {}
-
-    /// A particularly simple authorizer that just looks at the mode bits in the block metadata
-    /// to make authorization decisions.
-    pub struct ModeAuthorizer {}
-
-    impl ModeAuthorizer {
-        fn authorize(mode: u32, mask: u32, denied_msg: &str) -> io::Result<()> {
-            if (mode & mask) != 0 {
-                Ok(())
-            } else {
-                Err(io::Error::new(io::ErrorKind::PermissionDenied, denied_msg))
-            }
-        }
-
-        fn user_is_root(ctx: &AuthzContext<'_>) -> bool {
-            ctx.uid == 0
-        }
-    }
-
-    impl Authorizer for ModeAuthorizer {
-        fn can_read(&self, ctx: &AuthzContext<'_>) -> io::Result<()> {
-            if Self::user_is_root(ctx) {
-                return Ok(());
-            }
-            let secrets = ctx.meta.body.secrets()?;
-            let mask = (libc::S_IRUSR * (secrets.uid == ctx.uid) as u32)
-                | (libc::S_IRGRP * (secrets.gid == ctx.gid) as u32)
-                | libc::S_IROTH;
-            Self::authorize(secrets.mode, mask, "read access denied")
-        }
-
-        fn can_write(&self, ctx: &AuthzContext<'_>) -> io::Result<()> {
-            if Self::user_is_root(ctx) {
-                return Ok(());
-            }
-            let secrets = ctx.meta.body.secrets()?;
-            let mask = (libc::S_IWUSR * (secrets.uid == ctx.uid) as u32)
-                | (libc::S_IWGRP * (secrets.gid == ctx.gid) as u32)
-                | libc::S_IWOTH;
-            Self::authorize(secrets.mode, mask, "write access denied")
-        }
-
-        fn can_exec(&self, ctx: &AuthzContext<'_>) -> io::Result<()> {
-            if Self::user_is_root(ctx) {
-                return Ok(());
-            }
-            let secrets = ctx.meta.body.secrets()?;
-            let mask = (libc::S_IXUSR * (secrets.uid == ctx.uid) as u32)
-                | (libc::S_IXGRP * (secrets.gid == ctx.gid) as u32)
-                | libc::S_IXOTH;
-            Self::authorize(secrets.mode, mask, "exec access denied")
-        }
-    }
-
-    enum HandleValue {
-        File {
-            accessor: Mutex<Option<Accessor<&'static [u8]>>>,
-        },
-        Directory {
-            accessor: Mutex<Option<Accessor<&'static [u8]>>>,
-            dir: Directory,
-        },
-    }
-
-    impl HandleValue {
-        fn new<T: Size>(accessor: Accessor<T>) -> HandleValue {
-            let (accessor, ..) = accessor.split();
-            HandleValue::File {
-                accessor: Mutex::new(Some(accessor)),
-            }
-        }
-
-        fn get_mutex(&self) -> &Mutex<Option<Accessor<&'static [u8]>>> {
-            match self {
-                Self::File { accessor, .. } => accessor,
-                Self::Directory { accessor, .. } => accessor,
-            }
-        }
-
-        fn take_accessor(&self) -> Result<Accessor<&'static [u8]>> {
-            let mut guard = self.get_mutex().lock().display_err()?;
-            guard
-                .take()
-                .ok_or_else(|| bterr!("reader has already been taken"))
-        }
-
-        fn use_accessor<T, F: FnOnce(Accessor<&'static [u8]>) -> (Accessor<&'static [u8]>, T)>(
-            &self,
-            cb: F,
-        ) -> Result<T> {
-            let mut guard = self.get_mutex().lock().display_err()?;
-            let accessor = guard
-                .take()
-                .ok_or_else(|| bterr!("accessor has already been taken"))?;
-            let (accessor, output) = cb(accessor);
-            *guard = Some(accessor);
-            Ok(output)
-        }
-
-        fn convert_to_dir<C: Signer + Principaled + Decrypter>(
-            self,
-            block: &mut FileBlock<C>,
-        ) -> io::Result<HandleValue> {
-            let accessor = self.take_accessor()?;
-            let mut accessor = Accessor::combine(accessor, block);
-            let dir = accessor.read_dir()?;
-            let (accessor, ..) = accessor.split();
-            Ok(HandleValue::Directory {
-                dir,
-                accessor: Mutex::new(Some(accessor)),
-            })
-        }
-
-        fn directory(&self) -> io::Result<&Directory> {
-            match self {
-                Self::Directory { dir, .. } => Ok(dir),
-                _ => Err(io::Error::new(
-                    io::ErrorKind::Other,
-                    "handle is not for a directory",
-                )),
-            }
-        }
-
-        fn access_block<B: Size, T, F: FnOnce(&mut Accessor<B>) -> Result<T>>(
-            &self,
-            block: B,
-            cb: F,
-        ) -> Result<T> {
-            self.use_accessor(|accessor| {
-                let mut accessor = Accessor::combine(accessor, block);
-                let result = cb(&mut accessor);
-                let (accessor, ..) = accessor.split();
-                (accessor, result)
-            })?
-        }
-    }
-
-    struct InodeTableValue<C> {
-        block: Accessor<FileBlock<C>>,
-        handle_values: HashMap<Handle, HandleValue>,
-        next_handle: Handle,
-        lookup_count: u64,
-        delete: bool,
-    }
-
-    impl<C: Signer + Principaled + Decrypter> InodeTableValue<C> {
-        fn new(block: Accessor<FileBlock<C>>) -> InodeTableValue<C> {
-            Self {
-                block,
-                handle_values: HashMap::new(),
-                next_handle: 1,
-                lookup_count: 1,
-                delete: false,
-            }
-        }
-
-        fn invalid_handle_err(handle: Handle) -> io::Error {
-            io::Error::new(io::ErrorKind::Other, format!("invalid handle {handle}"))
-        }
-
-        fn value(&self, handle: Handle) -> io::Result<&HandleValue> {
-            self.handle_values
-                .get(&handle)
-                .ok_or_else(|| Self::invalid_handle_err(handle))
-        }
-
-        fn block(&self) -> &FileBlock<C> {
-            self.block.get_ref()
-        }
-
-        fn block_mut(&mut self) -> &mut FileBlock<C> {
-            self.block.get_mut()
-        }
-
-        fn convert_to_dir(&mut self, handle: Handle) -> io::Result<()> {
-            let value = self
-                .handle_values
-                .remove(&handle)
-                .ok_or_else(|| Self::invalid_handle_err(handle))?;
-            let block = self.block_mut();
-            let value = value.convert_to_dir(block)?;
-            self.handle_values.insert(handle, value);
-            Ok(())
-        }
-
-        fn access_block<T, F: FnOnce(&mut Accessor<&FileBlock<C>>) -> Result<T>>(
-            &self,
-            handle: Handle,
-            cb: F,
-        ) -> Result<T> {
-            let value = self.value(handle)?;
-            let block = self.block();
-            value.access_block(block, cb)
-        }
-
-        fn access_block_mut<T, F: FnOnce(&mut Accessor<&mut FileBlock<C>>) -> Result<T>>(
-            &mut self,
-            handle: Handle,
-            cb: F,
-        ) -> Result<T> {
-            let value = self
-                .handle_values
-                .get(&handle)
-                .ok_or_else(|| Self::invalid_handle_err(handle))?;
-            let inner = self.block.get_mut();
-            value.access_block(inner, cb)
-        }
-
-        fn borrow_block<T, F: FnOnce(&Accessor<FileBlock<C>>) -> Result<T>>(
-            &self,
-            cb: F,
-        ) -> Result<T> {
-            cb(&self.block)
-        }
-
-        fn borrow_block_mut<T, F: FnOnce(&mut Accessor<FileBlock<C>>) -> Result<T>>(
-            &mut self,
-            cb: F,
-        ) -> Result<T> {
-            cb(&mut self.block)
-        }
-
-        fn new_handle(&mut self) -> Result<Handle> {
-            if self.handle_values.len() as u64 >= u64::MAX {
-                return Err(bterr!("no handles are available"));
-            }
-            let mut handle_value = HandleValue::new(Accessor::new(self.block())?);
-            loop {
-                let handle = self.next_handle;
-                self.next_handle = self.next_handle.wrapping_add(1);
-                match self.handle_values.insert(handle, handle_value) {
-                    Some(prev) => {
-                        // We've wrapped around and this handle is already taken. Put the previous
-                        // value back and try again.
-                        handle_value = self.handle_values.insert(handle, prev).unwrap();
-                    }
-                    // We generated an unused handle. Return it.
-                    None => return Ok(handle),
-                }
-            }
-        }
-
-        fn forget_handle(&mut self, handle: Handle) {
-            self.handle_values.remove(&handle);
-        }
-
-        /// Increments `lookup_count` by 1 and returns its current value.
-        fn incr_lookup_count(&mut self) -> u64 {
-            self.lookup_count += 1;
-            self.lookup_count
-        }
-
-        /// Decrements `lookup_count` by `count` and returns its current value.
-        fn decr_lookup_count(&mut self, count: u64) -> u64 {
-            self.lookup_count -= count;
-            self.lookup_count
-        }
-    }
-
-    type InodeTable<C> = HashMap<Inode, RwLock<InodeTableValue<C>>>;
-    type InodeTableEntry<'a, C> = hash_map::Entry<'a, Inode, RwLock<InodeTableValue<C>>>;
-
-    /// Structure for metadata about a blocktree.
-    #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
-    struct Superblock {
-        /// The generation number of the cluster this part of the blocktree is stored on.
-        generation: u64,
-        /// The next free inode available to the cluster.
-        next_inode: u64,
-    }
-
-    /// Structure for managing the part of a blocktree which is stored in the local filesystem.
-    pub struct Blocktree<C, A> {
-        /// The path to the directory in the local filesystem where this blocktree is located.
-        path: PathBuf,
-        /// A map from inode numbers to their reference counts.
-        inodes: RwLock<InodeTable<C>>,
-        /// The next inode that will be assigned to a new block.
-        next_inode: AtomicU64,
-        /// The generation number of this filesystem. This is the same for every other server in
-        /// the same cluster.
-        generation: u64,
-        /// The credentials this blocktree instance will use for all cryptographic operations.
-        creds: C,
-        authorizer: A,
-    }
-
-    impl<C: Creds + 'static, A> Blocktree<C, A> {
-        /// Creates a new empty blocktree at the given path.
-        pub fn new_empty(
-            btdir: PathBuf,
-            generation: u64,
-            creds: C,
-            authorizer: A,
-        ) -> Result<Blocktree<C, A>> {
-            let root_block_path = creds
-                .writecap()
-                .ok_or(BlockError::MissingWritecap)?
-                .root_block_path();
-
-            // Initialize the superblock.
-            let mut sb_block = Self::open_block(
-                &btdir,
-                SpecInodes::Sb.into(),
-                creds.clone(),
-                root_block_path.to_owned(),
-            )?;
-            let sb = Superblock {
-                generation,
-                next_inode: SpecInodes::FirstFree.into(),
-            };
-            write_to(&sb, &mut sb_block)?;
-            sb_block.mut_meta_body().access_secrets(|secrets| {
-                secrets.block_id.generation = generation;
-                secrets.block_id.inode = SpecInodes::Sb.into();
-                secrets.mode = FileType::Reg.value() | 0o666;
-                secrets.uid = 0;
-                secrets.gid = 0;
-                secrets.nlink = 1;
-                Ok(())
-            })?;
-            sb_block.flush()?;
-
-            // Initialize the root directory.
-            let mut root_block = Self::open_block(
-                &btdir,
-                SpecInodes::RootDir.into(),
-                creds.clone(),
-                root_block_path,
-            )?;
-            write_to(&Directory::new(), &mut root_block)?;
-            root_block.mut_meta_body().access_secrets(|secrets| {
-                secrets.block_id.generation = generation;
-                secrets.block_id.inode = SpecInodes::RootDir.into();
-                secrets.mode = FileType::Dir.value() | 0o777;
-                secrets.uid = 0;
-                secrets.gid = 0;
-                secrets.nlink = 1;
-                Ok(())
-            })?;
-            root_block.flush()?;
-
-            Self::new(btdir, sb, sb_block, root_block, creds, authorizer)
-        }
-
-        /// Opens an existing blocktree stored at the given path.
-        pub fn new_existing(btdir: PathBuf, creds: C, authorizer: A) -> Result<Blocktree<C, A>> {
-            let root_block_path = creds
-                .writecap()
-                .ok_or(BlockError::MissingWritecap)?
-                .root_block_path();
-            let mut sb_block = Self::open_block(
-                &btdir,
-                SpecInodes::Sb.into(),
-                creds.clone(),
-                root_block_path.to_owned(),
-            )?;
-            let sb = read_from(&mut sb_block)?;
-            let root_block = Self::open_block(
-                &btdir,
-                SpecInodes::RootDir.into(),
-                creds.clone(),
-                root_block_path,
-            )?;
-            Self::new(btdir, sb, sb_block, root_block, creds, authorizer)
-        }
-
-        fn new(
-            btdir: PathBuf,
-            sb: Superblock,
-            sb_block: Accessor<FileBlock<C>>,
-            root_block: Accessor<FileBlock<C>>,
-            creds: C,
-            authorizer: A,
-        ) -> Result<Blocktree<C, A>> {
-            let mut inodes = HashMap::with_capacity(1);
-            inodes.insert(
-                SpecInodes::Sb.into(),
-                RwLock::new(InodeTableValue::new(sb_block)),
-            );
-            inodes.insert(
-                SpecInodes::RootDir.into(),
-                RwLock::new(InodeTableValue::new(root_block)),
-            );
-            Ok(Blocktree {
-                path: btdir,
-                inodes: RwLock::new(inodes),
-                next_inode: AtomicU64::new(sb.next_inode),
-                generation: sb.generation,
-                creds,
-                authorizer,
-            })
-        }
-
-        /// Returns the path to the file storing the given inode's data.
-        fn block_path<P: AsRef<Path>>(parent: P, inode: Inode) -> PathBuf {
-            let group = inode / 0xFF;
-            let mut path = PathBuf::new();
-            path.push(parent);
-            path.push(format!("{group:02x}"));
-            path.push(format!("{inode:x}.blk"));
-            path
-        }
-
-        fn open_block<P: AsRef<Path>>(
-            btdir: P,
-            inode: Inode,
-            creds: C,
-            block_path: BlockPath,
-        ) -> Result<Accessor<FileBlock<C>>> {
-            let path = Self::block_path(&btdir, inode);
-            let dir = path.ancestors().nth(1).unwrap();
-            if let Err(err) = std::fs::create_dir(dir) {
-                match err.kind() {
-                    io::ErrorKind::AlreadyExists => (),
-                    _ => return Err(err.into()),
-                }
-            }
-            let file = std::fs::OpenOptions::new()
-                .read(true)
-                .write(true)
-                .create(true)
-                .open(path)?;
-            Self::open_block_file(file, creds, block_path)
-        }
-
-        fn open_block_file(
-            file: File,
-            creds: C,
-            block_path: BlockPath,
-        ) -> Result<Accessor<FileBlock<C>>> {
-            let block = BlockOpenOptions::new()
-                .with_creds(creds)
-                .with_compress(false)
-                .with_encrypt(true)
-                .with_inner(file)
-                .with_block_path(block_path)
-                .open()?;
-            Ok(block)
-        }
-
-        /// Returns the [Err] variant containing the [io::Error] corresponding to [libc::ENOSYS].
-        fn not_supported<T>() -> io::Result<T> {
-            let err = io::Error::from_raw_os_error(libc::ENOSYS);
-            debug!("{err}");
-            Err(err)
-        }
-
-        fn access_entry<T, F: FnOnce(InodeTableEntry<C>) -> Result<T>>(
-            &self,
-            inode: Inode,
-            cb: F,
-        ) -> Result<T> {
-            let mut inodes = self.inodes.write().display_err()?;
-            let entry = inodes.entry(inode);
-            cb(entry)
-        }
-
-        fn access_value<T, F: FnOnce(&InodeTableValue<C>) -> Result<T>>(
-            &self,
-            inode: Inode,
-            cb: F,
-        ) -> Result<T> {
-            let inodes = self.inodes.read().display_err()?;
-            let guard = inodes
-                .get(&inode)
-                .ok_or_else(|| bterr!(Error::NotOpen(inode)))?
-                .read()
-                .display_err()?;
-            cb(&guard)
-        }
-
-        fn access_value_mut<T, F: FnOnce(&mut InodeTableValue<C>) -> Result<T>>(
-            &self,
-            inode: Inode,
-            cb: F,
-        ) -> Result<T> {
-            let inodes = self.inodes.read().display_err()?;
-            let mut guard = inodes
-                .get(&inode)
-                .ok_or_else(|| bterr!(Error::NotOpen(inode)))?
-                .write()
-                .display_err()?;
-            cb(&mut guard)
-        }
-
-        fn access_block<T, F: FnOnce(&mut Accessor<&FileBlock<C>>) -> Result<T>>(
-            &self,
-            inode: Inode,
-            handle: Handle,
-            cb: F,
-        ) -> Result<T> {
-            self.access_value(inode, |value| value.access_block(handle, cb))
-        }
-
-        fn access_block_mut<T, F: FnOnce(&mut Accessor<&mut FileBlock<C>>) -> Result<T>>(
-            &self,
-            inode: Inode,
-            handle: Handle,
-            cb: F,
-        ) -> Result<T> {
-            self.access_value_mut(inode, |value| value.access_block_mut(handle, cb))
-        }
-
-        fn borrow_block<T, F: FnOnce(&Accessor<FileBlock<C>>) -> Result<T>>(
-            &self,
-            inode: Inode,
-            cb: F,
-        ) -> Result<T> {
-            self.access_value(inode, |value| value.borrow_block(cb))
-        }
-
-        fn borrow_block_mut<T, F: FnOnce(&mut Accessor<FileBlock<C>>) -> Result<T>>(
-            &self,
-            inode: Inode,
-            cb: F,
-        ) -> Result<T> {
-            self.access_value_mut(inode, |value| value.borrow_block_mut(cb))
-        }
-
-        fn take_handle_if_ok<
-            T,
-            F: FnOnce(Handle, &mut Accessor<&mut FileBlock<C>>) -> Result<T>,
-        >(
-            &self,
-            inode: Inode,
-            cb: F,
-        ) -> Result<T> {
-            self.access_value_mut(inode, |value| {
-                let handle = value.new_handle()?;
-                let result = value.access_block_mut(handle, |block| cb(handle, block));
-                if result.is_err() {
-                    value.forget_handle(handle);
-                }
-                result
-            })
-        }
-
-        fn open_value<T, F: FnOnce(&mut InodeTableValue<C>) -> Result<T>>(
-            &self,
-            inode: Inode,
-            block_path: BlockPath,
-            cb: F,
-        ) -> Result<T> {
-            self.access_entry(inode, |entry| match entry {
-                InodeTableEntry::Vacant(entry) => {
-                    let block =
-                        Self::open_block(&self.path, inode, self.creds.clone(), block_path)?;
-                    let mut value = InodeTableValue::new(block);
-                    let result = cb(&mut value);
-                    entry.insert(RwLock::new(value));
-                    result
-                }
-                InodeTableEntry::Occupied(mut entry) => {
-                    let value = entry.get_mut().get_mut().display_err()?;
-                    cb(value)
-                }
-            })
-        }
-
-        fn open_then_take_handle<T, F: FnOnce(Handle, &mut InodeTableValue<C>) -> Result<T>>(
-            &self,
-            inode: Inode,
-            block_path: BlockPath,
-            cb: F,
-        ) -> Result<T> {
-            self.open_value(inode, block_path, |value| {
-                let handle = value.new_handle()?;
-                cb(handle, value)
-            })
-        }
-
-        fn inode_forget(
-            &self,
-            inodes: &mut RwLockWriteGuard<InodeTable<C>>,
-            inode: Inode,
-            count: u64,
-        ) -> io::Result<()> {
-            let lookup_count = {
-                let inode_lock = match inodes.get_mut(&inode) {
-                    Some(inode_lock) => inode_lock,
-                    None => {
-                        warn!("an attempt was made to forget non-existent inode {inode}");
-                        return Ok(());
-                    }
-                };
-                let mut value = inode_lock.write().display_err()?;
-                value.decr_lookup_count(count)
-            };
-            if 0 == lookup_count {
-                let delete = inodes
-                    .remove(&inode)
-                    .unwrap()
-                    .into_inner()
-                    .display_err()?
-                    .delete;
-                if delete {
-                    let path = Self::block_path(&self.path, inode);
-                    std::fs::remove_file(path)?;
-                }
-            }
-            Ok(())
-        }
-
-        /// Returns the next available inode and updates the superblock in one atomic operation.
-        /// TODO: Obviously this strategy won't work when there are multiple servers in this
-        /// generation.
-        fn next_inode(&self) -> Result<Inode> {
-            self.borrow_block_mut(SpecInodes::Sb.into(), |mut block| {
-                // We don't need strict ordering because the lock on the inode table value is already
-                // serializing access.
-                let inode = self.next_inode.fetch_add(1, Ordering::Relaxed);
-                let sb = Superblock {
-                    generation: self.generation,
-                    next_inode: inode + 1,
-                };
-                block.rewind()?;
-                write_to(&sb, &mut block)?;
-                Ok(inode)
-            })
-        }
-
-        fn attr_timeout(&self) -> Duration {
-            Duration::from_secs(5)
-        }
-
-        fn entry_timeout(&self) -> Duration {
-            Duration::from_secs(5)
-        }
-
-        fn unsupported_flag<T>(flag: &str) -> io::Result<T> {
-            Err(io::Error::new(
-                io::ErrorKind::Unsupported,
-                format!("unsupported flag: {flag}"),
-            ))
-        }
-
-        fn fuse_entry(&self, inode: Inode, stat: stat64) -> Entry {
-            Entry {
-                generation: self.generation,
-                inode,
-                attr: stat,
-                attr_flags: 0,
-                attr_timeout: self.attr_timeout(),
-                entry_timeout: self.entry_timeout(),
-            }
-        }
-    }
-
-    unsafe impl<C: Sync, A: Sync> Sync for Blocktree<C, A> {}
-
-    impl<C: Creds + Clone + 'static, A: Authorizer> FileSystem for Blocktree<C, A> {
-        type Inode = Inode;
-        type Handle = u64;
-
-        fn init(&self, _capable: FsOptions) -> io::Result<FsOptions> {
-            debug!("Blocktree::init called");
-            Ok(FsOptions::empty())
-        }
-
-        fn destroy(&self) {
-            debug!("Blocktree::destroy called");
-        }
-
-        fn lookup(&self, ctx: &Context, parent: Self::Inode, name: &CStr) -> io::Result<Entry> {
-            debug!("Blocktree::lookup, parent {parent}, {:?}", name);
-            let name = name.to_str().box_err()?;
-            let (dir, block_path) = match self.borrow_block_mut(parent, |block| {
-                self.authorizer.exec_allowed(ctx, block.meta())?;
-                let dir = block.read_dir()?;
-                let path = block.meta_body().path.to_owned();
-                Ok((dir, path))
-            }) {
-                Ok(pair) => pair,
-                Err(err) => {
-                    error!("Blocktree::lookup failed to borrow inode {parent}: {err}");
-                    return Err(err.into());
-                }
-            };
-            let entry = dir
-                .entries
-                .get(name)
-                .ok_or_else(|| io::Error::from_raw_os_error(libc::ENOENT))?;
-            let inode = entry.inode().ok_or_else(|| {
-                io::Error::new(io::ErrorKind::Unsupported, "can't lookup server entry")
-            })?;
-            let stat = match self.open_value(inode, block_path, |value| {
-                let stat = value.block.meta_body().secrets()?.stat()?;
-                value.incr_lookup_count();
-                Ok(stat)
-            }) {
-                Ok(stat) => stat,
-                Err(err) => {
-                    error!("Blocktree::lookup failed to read stats for '{name}': {err}");
-                    return Err(err.into());
-                }
-            };
-            Ok(self.fuse_entry(inode, stat))
-        }
-
-        fn open(
-            &self,
-            ctx: &Context,
-            inode: Self::Inode,
-            flags: u32,
-            // This is the second field of the `fuse_open_in` struct, which is currently unused
-            // by the kernel. (https://man7.org/linux/man-pages/man4/fuse.4.html)
-            fuse_flags: u32,
-        ) -> io::Result<(Option<Self::Handle>, OpenOptions)> {
-            debug!("Blocktree::open, inode {inode}, flags {flags}, fuse_flags {fuse_flags}");
-            let flags: i32 = flags.try_into().box_err()?;
-            if flags & libc::O_APPEND != 0 {
-                return Self::unsupported_flag("O_APPEND");
-            }
-            if flags & libc::O_CLOEXEC != 0 {
-                return Self::unsupported_flag("O_CLOEXEC");
-            }
-            if flags & libc::O_DIRECTORY != 0 {
-                return Self::unsupported_flag("O_DIRECTORY");
-            }
-            let handle = self.take_handle_if_ok(inode, |handle, block| {
-                let ctx = AuthzContext::new(ctx, block.meta());
-                if flags == libc::O_RDONLY || (flags & libc::O_RDWR) != 0 {
-                    self.authorizer.can_read(&ctx)?;
-                }
-                let write_mask = libc::O_WRONLY | libc::O_RDWR;
-                if write_mask & flags != 0 {
-                    self.authorizer.can_write(&ctx)?;
-                }
-                Ok(handle)
-            })?;
-            Ok((Some(handle), OpenOptions::empty()))
-        }
-
-        fn release(
-            &self,
-            _ctx: &Context,
-            inode: Self::Inode,
-            _flags: u32,
-            handle: Self::Handle,
-            flush: bool,
-            _flock_release: bool,
-            _lock_owner: Option<u64>,
-        ) -> io::Result<()> {
-            debug!("Blocktree::release, inode {inode}, handle {handle}, flush {flush}");
-            self.access_value_mut(inode, |value| {
-                if flush {
-                    value.access_block_mut(handle, |block| block.flush().bterr())?;
-                }
-                value.forget_handle(handle);
-                Ok(())
-            })
-            .io_err()
-        }
-
-        fn opendir(
-            &self,
-            ctx: &Context,
-            inode: Self::Inode,
-            _flags: u32,
-        ) -> io::Result<(Option<Self::Handle>, OpenOptions)> {
-            debug!("Blocktree::opendir, inode {inode}");
-            let handle = self.access_value_mut(inode, |value| {
-                self.authorizer.exec_allowed(ctx, value.block().meta())?;
-                let handle = value.new_handle()?;
-                value.convert_to_dir(handle)?;
-                Ok(handle)
-            })?;
-            debug!("Blockree::opendir, returning handle {handle}");
-            Ok((Some(handle), OpenOptions::empty()))
-        }
-
-        fn releasedir(
-            &self,
-            _ctx: &Context,
-            inode: Self::Inode,
-            _flags: u32,
-            handle: Self::Handle,
-        ) -> io::Result<()> {
-            debug!("Blocktree::releasedir, inode {inode}, handle {handle}");
-            self.access_value_mut(inode, |value| {
-                value.forget_handle(handle);
-                Ok(())
-            })
-            .io_err()
-        }
-
-        fn create(
-            &self,
-            ctx: &Context,
-            parent: Self::Inode,
-            name: &CStr,
-            args: CreateIn,
-        ) -> std::io::Result<(Entry, Option<Self::Handle>, OpenOptions)> {
-            debug!("Blocktree::create, parent {parent}, name {:?}", name);
-            let name = name.to_str().box_err()?.to_owned();
-
-            // Reserve a free inode.
-            let inode = self.next_inode()?;
-
-            // Add a directory entry to the parent for the new inode.
-            let mut block_path = self.borrow_block_mut(parent, |block| {
-                self.authorizer.write_allowed(ctx, block.meta())?;
-
-                let mut dir = block.read_dir()?;
-                dir.add_file(name.clone(), inode)?;
-                block.write_dir(&dir)?;
-
-                Ok(block.meta_body().path.clone())
-            })?;
-            block_path.push_component(name);
-
-            let (handle, stat) =
-                self.open_then_take_handle(inode, block_path, |handle, value| {
-                    value.block_mut().mut_meta_body().access_secrets(|secrets| {
-                        secrets.block_id.generation = self.generation;
-                        secrets.block_id.inode = inode;
-                        secrets.mode = args.mode & !args.umask;
-                        secrets.uid = ctx.uid;
-                        secrets.gid = ctx.gid;
-                        let now = Epoch::now();
-                        secrets.atime = now;
-                        secrets.ctime = now;
-                        secrets.mtime = now;
-                        secrets.nlink = 1;
-                        Ok((handle, secrets.stat()?))
-                    })
-                })?;
-
-            Ok((
-                self.fuse_entry(inode, stat),
-                Some(handle),
-                OpenOptions::empty(),
-            ))
-        }
-
-        fn write(
-            &self,
-            _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,
-            // `flags` and `fuse_flags` are the arguments that were passed to `open` when this
-            // handle was returned.
-            flags: u32,
-            _fuse_flags: u32,
-        ) -> io::Result<usize> {
-            debug!("Blocktree::write, inode {inode}, handle {handle}, offset {offset}, size {size}, flags {flags}");
-            if flags as libc::c_int == libc::O_RDONLY {
-                return Err(io::Error::new(
-                    io::ErrorKind::PermissionDenied,
-                    "file is readonly",
-                ));
-            }
-            let size: usize = size.try_into().box_err()?;
-            self.access_block_mut(inode, handle, |block| {
-                let pos = block.pos() as u64;
-                if offset != pos {
-                    block.seek(SeekFrom::Start(offset))?;
-                }
-                block.write_from(r, size).bterr()
-            })
-            .io_err()
-        }
-
-        fn flush(
-            &self,
-            _ctx: &Context,
-            inode: Self::Inode,
-            handle: Self::Handle,
-            _lock_owner: u64,
-        ) -> io::Result<()> {
-            debug!("Blocktree::flush, inode {inode}, handle {handle}");
-            self.access_block_mut(inode, handle, |block| block.flush().bterr())
-                .io_err()
-        }
-
-        fn read(
-            &self,
-            _ctx: &Context,
-            inode: Self::Inode,
-            handle: Self::Handle,
-            mut w: &mut dyn fuse_backend_rs::api::filesystem::ZeroCopyWriter,
-            size: u32,
-            offset: u64,
-            _lock_owner: Option<u64>,
-            flags: u32,
-        ) -> io::Result<usize> {
-            debug!("Blocktree::read, inode {inode}, handle {handle}, offset {offset}, size {size}, flags {flags}");
-            if (flags as libc::c_int & libc::O_WRONLY) != 0 {
-                return Err(io::Error::new(
-                    io::ErrorKind::PermissionDenied,
-                    "file is write only",
-                ));
-            }
-            let size: usize = size.try_into().box_err()?;
-            let read = self
-                .access_block(inode, handle, |block| {
-                    let pos = block.pos() as u64;
-                    if offset != pos {
-                        if let Err(err) = block.try_seek(SeekFrom::Start(offset)) {
-                            //  An error with `ErrorKind::Unsupported` means that the `SectoredBuf`
-                            // has unflushed data and it needs exclusive access to the block to
-                            // perform this seek because this data needs to be written.
-                            if let io::ErrorKind::Unsupported = err.kind() {
-                                return Ok(None);
-                            } else {
-                                return Err(err.into());
-                            }
-                        }
-                    }
-                    let read = block.read_into(&mut w, size).bterr()?;
-                    Ok(Some(read))
-                })
-                .io_err()?;
-            let read = match read {
-                Some(read) => read,
-                None => {
-                    // The offset of this read requires us to flush data buffered from a previous
-                    // write before seeking to a different sector, so we have to access the block
-                    // mutably.
-                    self.access_block_mut(inode, handle, |block| {
-                        block.seek(SeekFrom::Start(offset))?;
-                        block.read_into(w, size).bterr()
-                    })?
-                }
-            };
-            Ok(read)
-        }
-
-        fn readdir(
-            &self,
-            _ctx: &Context,
-            inode: Self::Inode,
-            handle: Self::Handle,
-            size: u32,
-            offset: u64,
-            add_entry: &mut dyn FnMut(
-                fuse_backend_rs::api::filesystem::DirEntry,
-            ) -> io::Result<usize>,
-        ) -> io::Result<()> {
-            debug!(
-                "Blocktree::readdir, inode {inode}, handle {handle}, size {size}, offset {offset}"
-            );
-            let mut size: usize = size.try_into().box_err()?;
-            self.access_value(inode, |value| {
-                let dir = value
-                    .value(handle)
-                    .map_err(|_| bterr!(Error::InvalidHandle { handle, inode }))?
-                    .directory()?;
-                let mut index: u64 = 0;
-                for (name, entry) in dir.entries() {
-                    index += 1;
-                    if index <= offset {
-                        continue;
-                    }
-                    let inode = match entry.inode() {
-                        Some(inode) => inode,
-                        None => continue,
-                    };
-                    let dir_entry = FuseDirEntry {
-                        ino: inode,
-                        offset: index,
-                        type_: entry.kind() as u32,
-                        name: name.as_bytes(),
-                    };
-                    size = size.saturating_sub(add_entry(dir_entry)?);
-                    if size == 0 {
-                        break;
-                    }
-                }
-                Ok(())
-            })
-            .io_err()
-        }
-
-        fn getattr(
-            &self,
-            _ctx: &Context,
-            inode: Self::Inode,
-            handle: Option<Self::Handle>,
-        ) -> io::Result<(stat64, Duration)> {
-            debug!("Blocktree::getattr, inode {inode}, handle {:?}", handle);
-            let stat = if let Some(handle) = handle {
-                self.access_block(inode, handle, |block| block.meta_body().secrets()?.stat())?
-            } else {
-                self.borrow_block(inode, |block| block.meta_body().secrets()?.stat())?
-            };
-            Ok((stat, self.attr_timeout()))
-        }
-
-        fn forget(&self, _ctx: &Context, inode: Self::Inode, count: u64) {
-            debug!("Blocktree::forget, inode {inode}, count {count}");
-            let mut inodes = match self.inodes.write() {
-                Ok(inodes) => inodes,
-                Err(err) => {
-                    error!("failed to take inode table lock: {err}");
-                    return;
-                }
-            };
-            if let Err(err) = self.inode_forget(&mut inodes, inode, count) {
-                error!("Blocktree::forget failed for inode {inode}: {err}");
-            }
-        }
-
-        fn batch_forget(&self, _ctx: &Context, requests: Vec<(Self::Inode, u64)>) {
-            debug!("Blocktree::batch_forget called");
-            let mut inodes = match self.inodes.write() {
-                Ok(inodes) => inodes,
-                Err(err) => {
-                    error!("failed to take inode table lock: {err}");
-                    return;
-                }
-            };
-            for (inode, count) in requests {
-                if let Err(err) = self.inode_forget(&mut inodes, inode, count) {
-                    error!("failed to forget inode {inode}: {err}");
-                }
-            }
-        }
-
-        /// Seek the given handle to a new location.
-        /// lseek requires exclusive access to the inode because it may need to write buffered
-        /// data before it can move the handle to a new position.
-        fn lseek(
-            &self,
-            _ctx: &Context,
-            inode: Self::Inode,
-            handle: Self::Handle,
-            offset: u64,
-            whence: u32,
-        ) -> io::Result<u64> {
-            debug!("Blocktree::lseek, inode {inode}, handle {handle}, offset {offset}, whence {whence}");
-            let seek_from = SeekFrom::whence_offset(whence, offset)?;
-            self.access_block_mut(inode, handle, |block| block.seek(seek_from).bterr())
-                .io_err()
-        }
-
-        fn unlink(&self, ctx: &Context, parent: Self::Inode, name: &CStr) -> io::Result<()> {
-            debug!("Blocktree::unlink, parent {parent}, name {:?}", name);
-            let name = name.to_str().box_err()?;
-            let (block_path, inode) = self.borrow_block_mut(parent, |block| {
-                self.authorizer.write_allowed(ctx, block.meta())?;
-
-                let mut dir = block.read_dir()?;
-                let entry = match dir.entries.remove(name) {
-                    None => return Err(io::Error::from_raw_os_error(libc::ENOENT).into()),
-                    Some(entry) => entry,
-                };
-                let inode = entry.inode().ok_or_else(|| {
-                    io::Error::new(
-                        io::ErrorKind::Other,
-                        "no inode associated with the given name",
-                    )
-                })?;
-                block.write_dir(&dir)?;
-
-                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.
-                let block = value.block_mut();
-                let nlink = block.mut_meta_body().access_secrets(|secrets| {
-                    secrets.nlink -= 1;
-                    Ok(secrets.nlink)
-                })?;
-                block.flush_meta()?;
-                value.delete = 0 == nlink;
-                Ok(())
-            })
-            .io_err()
-        }
-
-        fn link(
-            &self,
-            ctx: &Context,
-            inode: Self::Inode,
-            newparent: Self::Inode,
-            newname: &CStr,
-        ) -> io::Result<Entry> {
-            debug!(
-                "Blocktree::link, inode {inode}, newparent {newparent}, newname {:?}",
-                newname
-            );
-            let newname = newname.to_str().box_err()?;
-            self.borrow_block_mut(newparent, |block| {
-                self.authorizer.write_allowed(ctx, block.meta())?;
-
-                let mut dir = block.read_dir()?;
-                if dir.entries.contains_key(newname) {
-                    return Err(io::Error::from_raw_os_error(libc::EEXIST).into());
-                }
-
-                let (file_type, stat) = self.access_value_mut(inode, |value| {
-                    let block = value.block_mut();
-                    let meta = block.mut_meta_body();
-                    let (mode, stat) = meta.access_secrets(|secrets| {
-                        secrets.nlink += 1;
-                        Ok((secrets.mode, secrets.stat()?))
-                    })?;
-                    let file_type = FileType::from_value(mode)?;
-                    block.flush_meta()?;
-                    value.incr_lookup_count();
-                    Ok((file_type, stat))
-                })?;
-                let entry = match file_type {
-                    FileType::Reg => DirEntry::File(BlockRecord::new(inode)),
-                    FileType::Dir => DirEntry::Directory(BlockRecord::new(inode)),
-                };
-                dir.entries.insert(newname.to_owned(), entry);
-                block.write_dir(&dir)?;
-                Ok(self.fuse_entry(inode, stat))
-            })
-            .io_err()
-        }
-
-        fn setattr(
-            &self,
-            ctx: &Context,
-            inode: Self::Inode,
-            attr: stat64,
-            handle: Option<Self::Handle>,
-            valid: SetattrValid,
-        ) -> io::Result<(stat64, Duration)> {
-            debug!("Blocktree::setattr, inode {inode}, handle {:?}", handle);
-            let cb = |block: &mut FileBlock<C>| {
-                self.authorizer.write_allowed(ctx, block.meta())?;
-
-                let stat = block.mut_meta_body().access_secrets(|secrets| {
-                    if valid.intersects(SetattrValid::MODE) {
-                        secrets.mode = attr.st_mode;
-                    }
-                    if valid.intersects(SetattrValid::UID) {
-                        secrets.uid = attr.st_uid;
-                    }
-                    if valid.intersects(SetattrValid::GID) {
-                        secrets.gid = attr.st_gid;
-                    }
-                    if valid.intersects(SetattrValid::SIZE) {
-                        warn!("modifying file size with setattr is not supported");
-                        return Err(io::Error::from_raw_os_error(libc::EINVAL).into());
-                    }
-                    if valid.intersects(SetattrValid::ATIME) {
-                        secrets.atime = (attr.st_atime as u64).into();
-                    }
-                    if valid.intersects(SetattrValid::MTIME) {
-                        secrets.mtime = (attr.st_mtime as u64).into();
-                    }
-                    if valid.intersects(SetattrValid::CTIME) {
-                        secrets.ctime = (attr.st_ctime as u64).into();
-                    }
-                    let atime_now = valid.intersects(SetattrValid::ATIME_NOW);
-                    let mtime_now = valid.intersects(SetattrValid::MTIME_NOW);
-                    if atime_now || mtime_now {
-                        let now = Epoch::now();
-                        if atime_now {
-                            secrets.atime = now;
-                        }
-                        if mtime_now {
-                            secrets.mtime = now;
-                        }
-                    }
-                    if valid.intersects(SetattrValid::KILL_SUIDGID) {
-                        secrets.mode &= !(libc::S_ISUID | libc::S_ISGID)
-                    }
-                    secrets.stat()
-                })?;
-                block.flush_meta()?;
-                Ok(stat)
-            };
-            let stat = if let Some(handle) = handle {
-                self.access_block_mut(inode, handle, |block| cb(block.get_mut()))?
-            } else {
-                self.borrow_block_mut(inode, |block| cb(block.get_mut()))?
-            };
-            Ok((stat, self.attr_timeout()))
-        }
-
-        fn mkdir(
-            &self,
-            ctx: &Context,
-            parent: Self::Inode,
-            name: &CStr,
-            mode: u32,
-            umask: u32,
-        ) -> io::Result<Entry> {
-            debug!(
-                "Blocktree::mkdir, parent {parent}, mode {mode}, umask {umask}, name {:?}",
-                name
-            );
-            let name = name.to_str().box_err()?.to_owned();
-            let (inode, mut block_path) = self.borrow_block_mut(parent, |block| {
-                self.authorizer.write_allowed(ctx, block.meta())?;
-
-                let mut dir = block.read_dir()?;
-                if dir.entries.contains_key(&name) {
-                    return Err(io::Error::from_raw_os_error(libc::EEXIST).into());
-                }
-
-                let inode = self.next_inode()?;
-                dir.add_file(name.clone(), inode)?;
-                block.write_dir(&dir)?;
-
-                Ok((inode, block.meta_body().path.clone()))
-            })?;
-            block_path.push_component(name);
-            let stat = self.open_value(inode, block_path, |value| {
-                let stat = value.borrow_block_mut(|block| {
-                    let stat = block.mut_meta_body().access_secrets(|secrets| {
-                        secrets.block_id.generation = self.generation;
-                        secrets.block_id.inode = inode;
-                        secrets.mode = libc::S_IFDIR | mode & !umask;
-                        secrets.uid = ctx.uid;
-                        secrets.gid = ctx.gid;
-                        secrets.stat()
-                    })?;
-                    block.write_dir(&Directory::new())?;
-                    block.flush_meta()?;
-                    Ok(stat)
-                })?;
-                value.incr_lookup_count();
-                Ok(stat)
-            })?;
-            Ok(self.fuse_entry(inode, stat))
-        }
-
-        fn rmdir(&self, ctx: &Context, parent: Self::Inode, name: &CStr) -> io::Result<()> {
-            debug!("Blocktree::rmdir, parent {parent}, name {:?}", name);
-            let name = name.to_str().box_err()?;
-            self.borrow_block_mut(parent, |block| {
-                self.authorizer.write_allowed(ctx, block.meta())?;
-
-                let mut dir = block.read_dir()?;
-                if dir.entries.remove(name).is_none() {
-                    return Err(io::Error::from_raw_os_error(libc::ENOENT).into());
-                }
-
-                block.write_dir(&dir)?;
-                Ok(())
-            })
-            .io_err()
-        }
-
-        fn rename(
-            &self,
-            ctx: &Context,
-            src_dir: Self::Inode,
-            src_name: &CStr,
-            dst_dir: Self::Inode,
-            dst_name: &CStr,
-            flags: u32,
-        ) -> io::Result<()> {
-            debug!("Blocktree::rename, src_dir {src_dir}, src_name {:?}, dst_dir {dst_dir}, dst_name {:?}, flags {flags}", src_name, dst_name);
-
-            fn not_found_err(name: &str) -> crate::Error {
-                bterr!("file named '{name}' was not found").context(io::ErrorKind::NotFound)
-            }
-
-            fn modify_entries(
-                src_entries: Option<&mut BTreeMap<String, DirEntry>>,
-                dst_entries: &mut BTreeMap<String, DirEntry>,
-                entry: DirEntry,
-                src_name: &str,
-                dst_name: String,
-                no_replace: bool,
-                exchange: bool,
-            ) -> Result<()> {
-                if let Some(prev_entry) = dst_entries.insert(dst_name, entry) {
-                    if no_replace {
-                        return Err(bterr!("destination already exists")
-                            .context(io::ErrorKind::AlreadyExists));
-                    }
-                    if exchange {
-                        let entries = src_entries.unwrap_or(dst_entries);
-                        entries.insert(src_name.to_owned(), prev_entry);
-                    }
-                } else if exchange {
-                    return Err(
-                        bterr!("exchange was specified but destination doesn't exist")
-                            .context(io::ErrorKind::NotFound),
-                    );
-                }
-                Ok(())
-            }
-
-            let src_name = src_name.to_str().box_err()?;
-            let dst_name = dst_name.to_str().box_err()?.to_owned();
-            let no_replace = flags & libc::RENAME_NOREPLACE != 0;
-            let exchange = flags & libc::RENAME_EXCHANGE != 0;
-            if no_replace && exchange {
-                return Err(io::Error::new(
-                    io::ErrorKind::InvalidInput,
-                    "Both RENAME_EXCHANGE and RENAME_NOREPLACE were specified, but are incompatible"
-                ));
-            }
-            if src_dir == dst_dir {
-                self.borrow_block_mut(src_dir, |src_dir| {
-                    self.authorizer.write_allowed(ctx, src_dir.meta())?;
-                    let mut dir = src_dir.read_dir()?;
-                    let entry = dir
-                        .entries
-                        .remove(src_name)
-                        .ok_or_else(|| not_found_err(src_name))?;
-                    modify_entries(
-                        None,
-                        &mut dir.entries,
-                        entry,
-                        src_name,
-                        dst_name,
-                        no_replace,
-                        exchange,
-                    )?;
-                    src_dir.write_dir(&dir)
-                })?;
-            } else {
-                self.borrow_block_mut(src_dir, |src_dir| {
-                    self.authorizer.write_allowed(ctx, src_dir.meta())?;
-                    let mut dir_src = src_dir.read_dir()?;
-                    let entry = dir_src
-                        .entries
-                        .remove(src_name)
-                        .ok_or_else(|| not_found_err(src_name))?;
-                    self.borrow_block_mut(dst_dir, |dst_dir| {
-                        self.authorizer.write_allowed(ctx, dst_dir.meta())?;
-                        let mut dir_dst = dst_dir.read_dir()?;
-                        modify_entries(
-                            Some(&mut dir_src.entries),
-                            &mut dir_dst.entries,
-                            entry,
-                            src_name,
-                            dst_name,
-                            no_replace,
-                            exchange,
-                        )?;
-                        dst_dir.write_dir(&dir_dst)
-                    })?;
-                    src_dir.write_dir(&dir_src)
-                })?;
-            }
-            Ok(())
-        }
-
-        //////////////////////////////////
-        // METHODS WHICH ARE NOT SUPPORTED
-        //////////////////////////////////
-
-        fn getxattr(
-            &self,
-            _ctx: &Context,
-            inode: Self::Inode,
-            _name: &CStr,
-            _size: u32,
-        ) -> io::Result<fuse_backend_rs::api::filesystem::GetxattrReply> {
-            debug!("Blocktree::getxattr called for inode {inode}");
-            Self::not_supported()
-        }
-
-        fn ioctl(
-            &self,
-            _ctx: &Context,
-            inode: Self::Inode,
-            _handle: Self::Handle,
-            _flags: u32,
-            _cmd: u32,
-            _data: fuse_backend_rs::api::filesystem::IoctlData,
-            _out_size: u32,
-        ) -> io::Result<fuse_backend_rs::api::filesystem::IoctlData> {
-            debug!("Blocktree::ioctl called for inode {inode}");
-            Self::not_supported()
-        }
-
-        fn access(&self, _ctx: &Context, inode: Self::Inode, _mask: u32) -> io::Result<()> {
-            debug!("Blocktree::access called for inode {inode}");
-            Self::not_supported()
-        }
-
-        fn bmap(
-            &self,
-            _ctx: &Context,
-            inode: Self::Inode,
-            _block: u64,
-            _blocksize: u32,
-        ) -> io::Result<u64> {
-            debug!("Blocktree::bmap called for inode {inode}");
-            Self::not_supported()
-        }
-
-        fn fallocate(
-            &self,
-            _ctx: &Context,
-            inode: Self::Inode,
-            _handle: Self::Handle,
-            _mode: u32,
-            _offset: u64,
-            _length: u64,
-        ) -> io::Result<()> {
-            debug!("Blocktree::fallocate called for inode {inode}");
-            Self::not_supported()
-        }
-
-        fn fsync(
-            &self,
-            _ctx: &Context,
-            inode: Self::Inode,
-            _datasync: bool,
-            _handle: Self::Handle,
-        ) -> io::Result<()> {
-            debug!("Blocktree::fsync called for inode {inode}");
-            Self::not_supported()
-        }
-
-        fn fsyncdir(
-            &self,
-            _ctx: &Context,
-            inode: Self::Inode,
-            _datasync: bool,
-            _handle: Self::Handle,
-        ) -> io::Result<()> {
-            debug!("Blocktree::fsyncdir called for inode {inode}");
-            Self::not_supported()
-        }
-
-        fn getlk(
-            &self,
-            _ctx: &Context,
-            inode: Self::Inode,
-            _handle: Self::Handle,
-            _owner: u64,
-            _lock: fuse_backend_rs::api::filesystem::FileLock,
-            _flags: u32,
-        ) -> io::Result<fuse_backend_rs::api::filesystem::FileLock> {
-            debug!("Blocktree::getlk called for inode {inode}");
-            Self::not_supported()
-        }
-
-        fn listxattr(
-            &self,
-            _ctx: &Context,
-            inode: Self::Inode,
-            _size: u32,
-        ) -> io::Result<fuse_backend_rs::api::filesystem::ListxattrReply> {
-            debug!("Blocktree::listxattr called for inode {inode}");
-            Self::not_supported()
-        }
-
-        fn mknod(
-            &self,
-            _ctx: &Context,
-            inode: Self::Inode,
-            _name: &CStr,
-            _mode: u32,
-            _rdev: u32,
-            _umask: u32,
-        ) -> io::Result<Entry> {
-            debug!("Blocktree::mknod called for inode {inode}");
-            Self::not_supported()
-        }
-
-        fn notify_reply(&self) -> io::Result<()> {
-            debug!("Blocktree::notify_reply called");
-            Self::not_supported()
-        }
-
-        fn poll(
-            &self,
-            _ctx: &Context,
-            inode: Self::Inode,
-            _handle: Self::Handle,
-            _khandle: Self::Handle,
-            _flags: u32,
-            _events: u32,
-        ) -> io::Result<u32> {
-            debug!("Blocktree::poll called for inode {inode}");
-            Self::not_supported()
-        }
-
-        fn readdirplus(
-            &self,
-            _ctx: &Context,
-            _inode: Self::Inode,
-            _handle: Self::Handle,
-            _size: u32,
-            _offset: u64,
-            _add_entry: &mut dyn FnMut(
-                fuse_backend_rs::api::filesystem::DirEntry,
-                Entry,
-            ) -> io::Result<usize>,
-        ) -> io::Result<()> {
-            debug!("Blocktree::readdirplus called");
-            Self::not_supported()
-        }
-
-        fn readlink(&self, _ctx: &Context, inode: Self::Inode) -> io::Result<Vec<u8>> {
-            debug!("Blocktree::readlink called for inode {inode}");
-            Self::not_supported()
-        }
-
-        fn removexattr(&self, _ctx: &Context, inode: Self::Inode, _name: &CStr) -> io::Result<()> {
-            debug!("Blocktree::removexattr called for inode {inode}");
-            Self::not_supported()
-        }
-
-        fn setlk(
-            &self,
-            _ctx: &Context,
-            inode: Self::Inode,
-            _handle: Self::Handle,
-            _owner: u64,
-            _lock: fuse_backend_rs::api::filesystem::FileLock,
-            _flags: u32,
-        ) -> io::Result<()> {
-            debug!("Blocktree::setlk called for inode {inode}");
-            Self::not_supported()
-        }
-
-        fn setlkw(
-            &self,
-            _ctx: &Context,
-            inode: Self::Inode,
-            _handle: Self::Handle,
-            _owner: u64,
-            _lock: fuse_backend_rs::api::filesystem::FileLock,
-            _flags: u32,
-        ) -> io::Result<()> {
-            debug!("Blocktree::setlkw called for inode {inode}");
-            Self::not_supported()
-        }
-
-        fn setxattr(
-            &self,
-            _ctx: &Context,
-            inode: Self::Inode,
-            _name: &CStr,
-            _value: &[u8],
-            _flags: u32,
-        ) -> io::Result<()> {
-            debug!("Blocktree::setxattr called for inode {inode}");
-            Self::not_supported()
-        }
-
-        fn statfs(&self, _ctx: &Context, inode: Self::Inode) -> io::Result<statvfs64> {
-            debug!("Blocktree::statfs called for inode {inode}");
-            Self::not_supported()
-        }
-
-        fn symlink(
-            &self,
-            _ctx: &Context,
-            _linkname: &CStr,
-            _parent: Self::Inode,
-            _name: &CStr,
-        ) -> io::Result<Entry> {
-            debug!("Blocktree::symlink called");
-            Self::not_supported()
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    use fuse_backend_rs::{
-        abi::fuse_abi::CreateIn,
-        api::filesystem::{Context, FileSystem, FsOptions},
-    };
-    use std::{ffi::CString, sync::Arc};
-    use tempdir::TempDir;
-
-    use crate::{
-        crypto::ConcreteCreds,
-        test_helpers::{integer_array, node_creds, BtCursor},
-        BlockMeta, Decompose,
-    };
-
-    /// Tests for the [ModeAuthorizer] struct.
-    mod mode_authorizer_tests {
-        use super::{
-            super::private::{Authorizer, AuthzContext},
-            *,
-        };
-
-        struct TestCase {
-            ctx_uid: u32,
-            ctx_gid: u32,
-            meta: BlockMeta,
-        }
-
-        impl TestCase {
-            const BLOCK_UID: u32 = 1000;
-            const BLOCK_GID: u32 = 1000;
-            const CTX_PID: libc::pid_t = 100;
-
-            fn new(ctx_uid: u32, ctx_gid: u32, mode: u32) -> TestCase {
-                let mut meta =
-                    BlockMeta::new(node_creds()).expect("failed to create block metadata");
-                meta.body
-                    .access_secrets(|secrets| {
-                        secrets.uid = Self::BLOCK_UID;
-                        secrets.gid = Self::BLOCK_GID;
-                        secrets.mode = mode;
-                        Ok(())
-                    })
-                    .expect("failed to update secrets");
-                TestCase {
-                    ctx_uid,
-                    ctx_gid,
-                    meta,
-                }
-            }
-
-            fn context(&self) -> AuthzContext<'_> {
-                AuthzContext {
-                    uid: self.ctx_uid,
-                    gid: self.ctx_gid,
-                    pid: Self::CTX_PID,
-                    meta: &self.meta,
-                }
-            }
-        }
-
-        #[test]
-        fn cant_read_when_no_bits_set() {
-            let case = TestCase::new(TestCase::BLOCK_UID, TestCase::BLOCK_GID, 0);
-            let result = ModeAuthorizer {}.can_read(&case.context());
-            assert!(result.is_err())
-        }
-
-        #[test]
-        fn cant_write_when_no_bits_set() {
-            let case = TestCase::new(TestCase::BLOCK_UID, TestCase::BLOCK_GID, 0);
-            let result = ModeAuthorizer {}.can_write(&case.context());
-            assert!(result.is_err())
-        }
-
-        #[test]
-        fn cant_exec_when_no_bits_set() {
-            let case = TestCase::new(TestCase::BLOCK_UID, TestCase::BLOCK_GID, 0);
-            let result = ModeAuthorizer {}.can_exec(&case.context());
-            assert!(result.is_err())
-        }
-
-        #[test]
-        fn user_can_read_when_bit_set() {
-            let case = TestCase::new(TestCase::BLOCK_UID, TestCase::BLOCK_GID, libc::S_IRUSR);
-            let result = ModeAuthorizer {}.can_read(&case.context());
-            assert!(result.is_ok())
-        }
-
-        #[test]
-        fn user_can_write_when_bit_set() {
-            let case = TestCase::new(TestCase::BLOCK_UID, TestCase::BLOCK_GID, libc::S_IWUSR);
-            let result = ModeAuthorizer {}.can_write(&case.context());
-            assert!(result.is_ok())
-        }
-
-        #[test]
-        fn user_can_exec_when_bit_set() {
-            let case = TestCase::new(TestCase::BLOCK_UID, TestCase::BLOCK_GID, libc::S_IXUSR);
-            let result = ModeAuthorizer {}.can_exec(&case.context());
-            assert!(result.is_ok())
-        }
-
-        #[test]
-        fn group_can_read_when_bit_set() {
-            let case = TestCase::new(TestCase::BLOCK_UID, TestCase::BLOCK_GID, libc::S_IRGRP);
-            let result = ModeAuthorizer {}.can_read(&case.context());
-            assert!(result.is_ok())
-        }
-
-        #[test]
-        fn group_can_write_when_bit_set() {
-            let case = TestCase::new(TestCase::BLOCK_UID, TestCase::BLOCK_GID, libc::S_IWGRP);
-            let result = ModeAuthorizer {}.can_write(&case.context());
-            assert!(result.is_ok())
-        }
-
-        #[test]
-        fn group_can_exec_when_bit_set() {
-            let case = TestCase::new(TestCase::BLOCK_UID, TestCase::BLOCK_GID, libc::S_IXGRP);
-            let result = ModeAuthorizer {}.can_exec(&case.context());
-            assert!(result.is_ok())
-        }
-
-        #[test]
-        fn other_can_read_when_bit_set() {
-            let case = TestCase::new(TestCase::BLOCK_UID, TestCase::BLOCK_GID, libc::S_IROTH);
-            let result = ModeAuthorizer {}.can_read(&case.context());
-            assert!(result.is_ok())
-        }
-
-        #[test]
-        fn other_can_write_when_bit_set() {
-            let case = TestCase::new(TestCase::BLOCK_UID, TestCase::BLOCK_GID, libc::S_IWOTH);
-            let result = ModeAuthorizer {}.can_write(&case.context());
-            assert!(result.is_ok())
-        }
-
-        #[test]
-        fn other_can_exec_when_bit_set() {
-            let case = TestCase::new(TestCase::BLOCK_UID, TestCase::BLOCK_GID, libc::S_IXOTH);
-            let result = ModeAuthorizer {}.can_exec(&case.context());
-            assert!(result.is_ok())
-        }
-
-        #[test]
-        fn other_cant_write_even_if_user_can() {
-            let case = TestCase::new(
-                TestCase::BLOCK_UID + 1,
-                TestCase::BLOCK_GID + 1,
-                libc::S_IWUSR,
-            );
-            let result = ModeAuthorizer {}.can_write(&case.context());
-            assert!(result.is_err())
-        }
-
-        #[test]
-        fn other_cant_write_even_if_group_can() {
-            let case = TestCase::new(
-                TestCase::BLOCK_UID + 1,
-                TestCase::BLOCK_GID + 1,
-                libc::S_IWGRP,
-            );
-            let result = ModeAuthorizer {}.can_write(&case.context());
-            assert!(result.is_err())
-        }
-
-        #[test]
-        fn user_allowed_read_when_only_other_bit_set() {
-            let case = TestCase::new(TestCase::BLOCK_UID, TestCase::BLOCK_GID, libc::S_IROTH);
-            let result = ModeAuthorizer {}.can_read(&case.context());
-            assert!(result.is_ok())
-        }
-
-        #[test]
-        fn root_always_allowed() {
-            let case = TestCase::new(0, 0, 0);
-            let ctx = case.context();
-            let authorizer = ModeAuthorizer {};
-            assert!(authorizer.can_read(&ctx).is_ok());
-            assert!(authorizer.can_write(&ctx).is_ok());
-            assert!(authorizer.can_exec(&ctx).is_ok());
-        }
-    }
-
-    struct BtTestCase {
-        dir: TempDir,
-        bt: Blocktree<ConcreteCreds, ModeAuthorizer>,
-    }
-
-    impl BtTestCase {
-        fn new_empty() -> BtTestCase {
-            let dir = TempDir::new("fuse").expect("failed to create temp dir");
-            let bt =
-                Blocktree::new_empty(dir.path().to_owned(), 0, Self::creds(), ModeAuthorizer {})
-                    .expect("failed to create empty blocktree");
-            bt.init(FsOptions::empty()).expect("init failed");
-            BtTestCase { dir, bt }
-        }
-
-        fn new_existing(dir: TempDir) -> BtTestCase {
-            let bt =
-                Blocktree::new_existing(dir.path().to_owned(), Self::creds(), ModeAuthorizer {})
-                    .expect("failed to create blocktree from existing directory");
-            bt.init(FsOptions::empty()).expect("init failed");
-            BtTestCase { dir, bt }
-        }
-
-        fn creds() -> ConcreteCreds {
-            node_creds().clone()
-        }
-
-        fn context(&self) -> Context {
-            let (stat, ..) = self
-                .bt
-                .getattr(&Default::default(), SpecInodes::RootDir.into(), None)
-                .expect("getattr failed");
-            Context {
-                uid: stat.st_uid,
-                gid: stat.st_gid,
-                pid: 1,
-            }
-        }
-    }
-
-    /// Tests that a new file can be created, written to and the written data can be read from it.
-    #[test]
-    fn create_write_lseek_read() {
-        let case = BtTestCase::new_empty();
-        let bt = &case.bt;
-        let ctx = case.context();
-        let name = CString::new("README.md").unwrap();
-        let flags = libc::O_RDWR as u32;
-
-        let (entry, handle, ..) = bt
-            .create(
-                &ctx,
-                SpecInodes::RootDir.into(),
-                name.as_c_str(),
-                CreateIn {
-                    mode: libc::S_IFREG | 0o644,
-                    umask: 0,
-                    flags,
-                    fuse_flags: 0,
-                },
-            )
-            .expect("failed to create file");
-        let inode = entry.inode;
-        let handle = handle.unwrap();
-
-        const LEN: usize = 32;
-        let mut expected = BtCursor::new([1u8; LEN]);
-        let written = bt
-            .write(
-                &ctx,
-                inode,
-                handle,
-                &mut expected,
-                LEN as u32,
-                0,
-                None,
-                false,
-                flags,
-                0,
-            )
-            .expect("write failed");
-        assert_eq!(LEN, written);
-
-        bt.lseek(&ctx, inode, handle, 0, 0).expect("lseek failed");
-
-        let mut actual = BtCursor::new([0u8; LEN]);
-        let read = bt
-            .read(&ctx, inode, handle, &mut actual, LEN as u32, 0, None, flags)
-            .expect("failed to read");
-        assert_eq!(LEN, read);
-
-        assert_eq!(expected, actual)
-    }
-
-    #[test]
-    fn lookup() {
-        let case = BtTestCase::new_empty();
-        let bt = &case.bt;
-        let ctx = case.context();
-
-        let name = CString::new("README.md").unwrap();
-        let (expected, ..) = bt
-            .create(
-                &ctx,
-                SpecInodes::RootDir.into(),
-                name.as_c_str(),
-                Default::default(),
-            )
-            .expect("failed to create file");
-
-        let actual = bt
-            .lookup(&Default::default(), SpecInodes::RootDir.into(), &name)
-            .expect("lookup failed");
-
-        assert_eq!(expected.generation, actual.generation);
-        assert_eq!(expected.inode, actual.inode);
-    }
-
-    /// Tests that data written by one instance of [Blocktree] can be read by a subsequent
-    /// instance.
-    #[test]
-    fn new_existing() {
-        const EXPECTED: &[u8] = b"cool as cucumbers";
-        let name = CString::new("RESIGNATION.docx").unwrap();
-        let case = BtTestCase::new_empty();
-        let bt = &case.bt;
-        let ctx = case.context();
-        let flags = libc::O_RDWR as u32;
-
-        {
-            let (entry, handle, ..) = bt
-                .create(
-                    &ctx,
-                    SpecInodes::RootDir.into(),
-                    name.as_c_str(),
-                    CreateIn {
-                        mode: libc::S_IFREG | 0o644,
-                        umask: 0,
-                        flags,
-                        fuse_flags: 0,
-                    },
-                )
-                .expect("failed to create file");
-            let inode = entry.inode;
-            let handle = handle.unwrap();
-
-            let mut vec = Vec::with_capacity(EXPECTED.len());
-            vec.extend_from_slice(EXPECTED);
-            let mut cursor = BtCursor::new(vec);
-            let written = bt
-                .write(
-                    &Default::default(),
-                    inode,
-                    handle,
-                    &mut cursor,
-                    EXPECTED.len() as u32,
-                    0,
-                    None,
-                    false,
-                    flags,
-                    0,
-                )
-                .expect("write failed");
-            assert_eq!(EXPECTED.len(), written);
-
-            bt.flush(&Default::default(), inode, handle, 0)
-                .expect("flush failed");
-        }
-
-        let case = BtTestCase::new_existing(case.dir);
-        let bt = &case.bt;
-
-        let entry = bt
-            .lookup(&Default::default(), SpecInodes::RootDir.into(), &name)
-            .expect("lookup failed");
-        let inode = entry.inode;
-
-        let (handle, ..) = bt
-            .open(&Default::default(), entry.inode, 0, 0)
-            .expect("open failed");
-        let handle = handle.unwrap();
-
-        let mut actual = BtCursor::new([0u8; EXPECTED.len()]);
-        let _ = bt
-            .read(
-                &Default::default(),
-                inode,
-                handle,
-                &mut actual,
-                EXPECTED.len() as u32,
-                0,
-                None,
-                flags,
-            )
-            .expect("read failed");
-
-        assert_eq!(EXPECTED, actual.into_inner().as_slice())
-    }
-
-    /// Tests that an error is returned by the `Blocktree::write` method if the file was opened
-    /// read-only.
-    #[test]
-    fn open_read_only_write_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_RDONLY 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.write(
-            &ctx,
-            inode,
-            handle,
-            &mut reader,
-            LEN.try_into().unwrap(),
-            0,
-            None,
-            false,
-            flags,
-            0,
-        );
-
-        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());
-    }
-
-    /// Tests that multiple handles see consistent metadata associated with a block.
-    #[test]
-    fn ensure_metadata_consistency() {
-        let case = BtTestCase::new_empty();
-        let ctx = case.context();
-        let trash = CString::new(".Trash").unwrap();
-        let file = CString::new("file.txt").unwrap();
-        let bt = &case.bt;
-        let root = SpecInodes::RootDir.into();
-
-        let (handle, _) = bt.opendir(&ctx, root, 0).unwrap();
-        // Because the directory is open, this will cause a new handle for this block to be opened.
-        let result = bt.lookup(&ctx, root, &trash);
-        assert_eq!(libc::ENOENT, result.err().unwrap().raw_os_error().unwrap());
-        bt.releasedir(&ctx, root, 0, handle.unwrap()).unwrap();
-        bt.create(&ctx, root, &file, CreateIn::default()).unwrap();
-        bt.opendir(&ctx, root, 0).unwrap();
-        // Since the directory is open, the second handle will be used for this lookup.
-        let result = bt.lookup(&ctx, root, &trash);
-        assert_eq!(libc::ENOENT, result.err().unwrap().raw_os_error().unwrap());
-    }
-
-    /// Tests that the `size` parameter actually limits the number of read bytes.
-    #[test]
-    fn read_with_smaller_size() {
-        const DATA: [u8; 8] = [0, 1, 2, 3, 4, 5, 6, 7];
-        let mut data = BtCursor::new(DATA);
-        let case = BtTestCase::new_empty();
-        let ctx = case.context();
-        let file = CString::new("file.txt").unwrap();
-        let bt = &case.bt;
-        let root = SpecInodes::RootDir.into();
-        let flags = libc::O_RDWR as u32;
-
-        let create_in = CreateIn {
-            flags,
-            mode: 0o644,
-            umask: 0,
-            fuse_flags: 0,
-        };
-        let (entry, handle, ..) = bt.create(&ctx, root, &file, create_in).unwrap();
-        let handle = handle.unwrap();
-        let inode = entry.inode;
-        let written = bt
-            .write(
-                &ctx,
-                inode,
-                handle,
-                &mut data,
-                DATA.len() as u32,
-                0,
-                None,
-                false,
-                flags,
-                0,
-            )
-            .unwrap();
-        assert_eq!(DATA.len(), written);
-        let new_pos = bt
-            .lseek(&ctx, inode, handle, 0, libc::SEEK_SET as u32)
-            .unwrap();
-        assert_eq!(0, new_pos);
-        data.rewind().unwrap();
-        const SIZE: usize = DATA.len() / 2;
-        let mut actual = BtCursor::new([0u8; DATA.len()]);
-        let read = bt
-            .read(
-                &ctx,
-                inode,
-                handle,
-                &mut actual,
-                SIZE as u32,
-                0,
-                None,
-                flags,
-            )
-            .unwrap();
-        assert_eq!(SIZE, read);
-
-        let actual = actual.into_inner();
-        assert_eq!([0, 1, 2, 3, 0, 0, 0, 0], actual);
-    }
-
-    #[test]
-    fn concurrent_reads() {
-        // The size of each of the reads.
-        const SIZE: usize = 4;
-        // The number of concurrent reads.
-        const NREADS: usize = 32;
-        const DATA_LEN: usize = SIZE * NREADS;
-        const DATA: [u8; DATA_LEN] = integer_array::<DATA_LEN>(0);
-        let case = BtTestCase::new_empty();
-        let ctx = case.context();
-        let file = CString::new("file.txt").unwrap();
-        let bt = &case.bt;
-        let root = SpecInodes::RootDir.into();
-        let flags = libc::O_RDWR as u32;
-        let create_in = CreateIn {
-            flags,
-            mode: 0o644,
-            umask: 0,
-            fuse_flags: 0,
-        };
-
-        let (entry, handle, ..) = bt.create(&ctx, root, &file, create_in).unwrap();
-        let handle = handle.unwrap();
-        let inode = entry.inode;
-        let written = bt
-            .write(
-                &ctx,
-                inode,
-                handle,
-                &mut BtCursor::new(DATA),
-                DATA.len() as u32,
-                0,
-                None,
-                false,
-                flags,
-                0,
-            )
-            .unwrap();
-        assert_eq!(DATA.len(), written);
-        let case = Box::new(case);
-        let cb = Arc::new(Box::new(move |offset: usize| {
-            // Notice that we have concurrent reads to different offsets using the same handle.
-            // Without proper synchronization, this shouldn't work.
-            let mut actual = BtCursor::new(Vec::new());
-            let nread = case
-                .bt
-                .read(
-                    &ctx,
-                    inode,
-                    handle,
-                    &mut actual,
-                    SIZE as u32,
-                    offset as u64,
-                    None,
-                    flags,
-                )
-                .unwrap();
-            assert_eq!(SIZE, nread);
-            let expected = integer_array::<SIZE>(offset as u8);
-            assert_eq!(&expected, actual.into_inner().as_slice());
-        }));
-
-        let mut handles = Vec::with_capacity(NREADS);
-        for offset in (0..NREADS).map(|e| e * SIZE) {
-            let thread_cb = cb.clone();
-            handles.push(std::thread::spawn(move || thread_cb(offset)));
-        }
-        for handle in handles {
-            handle.join().unwrap();
-        }
-    }
-
-    #[test]
-    fn rename_in_same_directory() {
-        let case = BtTestCase::new_empty();
-        let bt = &case.bt;
-        let ctx = case.context();
-        let root = SpecInodes::RootDir.into();
-        let src_name = CString::new("src").unwrap();
-        let dst_name = CString::new("dst").unwrap();
-
-        let (entry, ..) = bt
-            .create(&ctx, root, &src_name, CreateIn::default())
-            .unwrap();
-        let inode = entry.inode;
-        bt.rename(&ctx, root, &src_name, root, &dst_name, 0)
-            .unwrap();
-
-        let entry = bt.lookup(&ctx, root, &dst_name).unwrap();
-        assert_eq!(inode, entry.inode);
-        let result = bt.lookup(&ctx, root, &src_name);
-        assert!(result.is_err());
-    }
-
-    #[test]
-    fn rename_to_different_directory() {
-        let case = BtTestCase::new_empty();
-        let bt = &case.bt;
-        let ctx = case.context();
-        let root = SpecInodes::RootDir.into();
-        let dir_name = CString::new("dir").unwrap();
-        let file_name = CString::new("file").unwrap();
-
-        let entry = bt.mkdir(&ctx, root, &dir_name, 0o755, 0).unwrap();
-        let dir = entry.inode;
-        let (entry, ..) = bt
-            .create(&ctx, root, &file_name, CreateIn::default())
-            .unwrap();
-        let file = entry.inode;
-        bt.rename(&ctx, root, &file_name, dir, &file_name, 0)
-            .unwrap();
-
-        let entry = bt.lookup(&ctx, dir, &file_name).unwrap();
-        assert_eq!(file, entry.inode);
-        let result = bt.lookup(&ctx, root, &file_name);
-        assert!(result.is_err());
-    }
-
-    #[test]
-    fn rename_no_replace_same_directory() {
-        let case = BtTestCase::new_empty();
-        let bt = &case.bt;
-        let ctx = case.context();
-        let root = SpecInodes::RootDir.into();
-        let oldname = CString::new("old").unwrap();
-        let newname = CString::new("new").unwrap();
-
-        bt.create(&ctx, root, &oldname, CreateIn::default())
-            .unwrap();
-        bt.create(&ctx, root, &newname, CreateIn::default())
-            .unwrap();
-
-        let result = bt.rename(&ctx, root, &oldname, root, &newname, libc::RENAME_NOREPLACE);
-        let matched = if let Err(err) = result {
-            err.kind() == io::ErrorKind::AlreadyExists
-        } else {
-            false
-        };
-        assert!(matched);
-    }
-
-    #[test]
-    fn rename_no_replace_different_directory() {
-        let case = BtTestCase::new_empty();
-        let bt = &case.bt;
-        let ctx = case.context();
-        let root = SpecInodes::RootDir.into();
-        let dir_name = CString::new("dir").unwrap();
-        let file_name = CString::new("file").unwrap();
-
-        let entry = bt.mkdir(&ctx, root, &dir_name, 0o755, 0).unwrap();
-        let dir = entry.inode;
-        bt.create(&ctx, root, &file_name, CreateIn::default())
-            .unwrap();
-        bt.create(&ctx, dir, &file_name, CreateIn::default())
-            .unwrap();
-
-        let result = bt.rename(
-            &ctx,
-            root,
-            &file_name,
-            dir,
-            &file_name,
-            libc::RENAME_NOREPLACE,
-        );
-        let matched = if let Err(err) = result {
-            err.kind() == io::ErrorKind::AlreadyExists
-        } else {
-            false
-        };
-        assert!(matched);
-    }
-
-    #[test]
-    fn rename_exchange_same_directory() {
-        let case = BtTestCase::new_empty();
-        let bt = &case.bt;
-        let ctx = case.context();
-        let root = SpecInodes::RootDir.into();
-        let name_one = CString::new("one").unwrap();
-        let name_two = CString::new("two").unwrap();
-        let flags = libc::O_RDWR as u32;
-        let create_in = CreateIn {
-            mode: 0o644,
-            umask: 0,
-            flags,
-            fuse_flags: 0,
-        };
-
-        let (entry_one, ..) = bt.create(&ctx, root, &name_one, create_in.clone()).unwrap();
-        let (entry_two, ..) = bt.create(&ctx, root, &name_two, create_in).unwrap();
-        bt.rename(
-            &ctx,
-            root,
-            &name_one,
-            root,
-            &name_two,
-            libc::RENAME_EXCHANGE,
-        )
-        .unwrap();
-
-        let actual_one = bt.lookup(&ctx, root, &name_one).unwrap();
-        assert_eq!(entry_two.inode, actual_one.inode);
-        let actual_two = bt.lookup(&ctx, root, &name_two).unwrap();
-        assert_eq!(entry_one.inode, actual_two.inode);
-    }
-
-    #[test]
-    fn rename_exchange_different_directories() {
-        let case = BtTestCase::new_empty();
-        let bt = &case.bt;
-        let ctx = case.context();
-        let root = SpecInodes::RootDir.into();
-        let dir_name = CString::new("dir").unwrap();
-        let name_one = CString::new("one").unwrap();
-        let name_two = CString::new("two").unwrap();
-        let flags = libc::O_RDWR as u32;
-        let create_in = CreateIn {
-            mode: 0o644,
-            umask: 0,
-            flags,
-            fuse_flags: 0,
-        };
-
-        let (entry_one, ..) = bt.create(&ctx, root, &name_one, create_in.clone()).unwrap();
-        let dir_entry = bt.mkdir(&ctx, root, &dir_name, 0o755, 0).unwrap();
-        let dir = dir_entry.inode;
-        let (entry_two, ..) = bt.create(&ctx, dir, &name_two, create_in).unwrap();
-        bt.rename(&ctx, root, &name_one, dir, &name_two, libc::RENAME_EXCHANGE)
-            .unwrap();
-
-        let actual_one = bt.lookup(&ctx, root, &name_one).unwrap();
-        assert_eq!(entry_two.inode, actual_one.inode);
-        let actual_two = bt.lookup(&ctx, dir, &name_two).unwrap();
-        assert_eq!(entry_one.inode, actual_two.inode);
-    }
-
-    #[test]
-    fn rename_exchange_and_no_replace_is_err() {
-        let case = BtTestCase::new_empty();
-        let bt = &case.bt;
-        let ctx = case.context();
-        let root = SpecInodes::RootDir.into();
-        let name_one = CString::new("one").unwrap();
-        let name_two = CString::new("two").unwrap();
-        let flags = libc::O_RDWR as u32;
-        let create_in = CreateIn {
-            mode: 0o644,
-            umask: 0,
-            flags,
-            fuse_flags: 0,
-        };
-
-        bt.create(&ctx, root, &name_one, create_in.clone()).unwrap();
-        bt.create(&ctx, root, &name_two, create_in).unwrap();
-        let result = bt.rename(
-            &ctx,
-            root,
-            &name_one,
-            root,
-            &name_two,
-            libc::RENAME_EXCHANGE | libc::RENAME_NOREPLACE,
-        );
-
-        let matched = if let Err(err) = result {
-            err.kind() == io::ErrorKind::InvalidInput
-        } else {
-            false
-        };
-        assert!(matched);
-    }
-}

+ 6 - 0
crates/btlib/src/collections.rs

@@ -0,0 +1,6 @@
+//! A collection of collection types.
+
+pub mod hash_map_with_default;
+pub use hash_map_with_default::HashMapWithDefault;
+pub mod bijection;
+pub use bijection::Bijection;

+ 64 - 0
crates/btlib/src/collections/bijection.rs

@@ -0,0 +1,64 @@
+//! This module contains the [Bijection] type.
+
+use super::HashMapWithDefault;
+use std::{
+    borrow::Borrow,
+    hash::Hash,
+};
+use log::error;
+ 
+pub struct Bijection<K, V> {
+    k2v: HashMapWithDefault<K, V>,
+    v2k: HashMapWithDefault<V, K>,
+}
+
+impl<K, V> Bijection<K, V> {
+    pub fn new(default_key: K, default_value: V) -> Self {
+        let k2v = HashMapWithDefault::new(default_value);
+        let v2k = HashMapWithDefault::new(default_key);
+        Self { k2v, v2k }
+    }
+
+    pub fn k2v(&self) -> &HashMapWithDefault<K, V> {
+        &self.k2v
+    }
+
+    pub fn v2k(&self) -> &HashMapWithDefault<V, K> {
+        &self.v2k
+    }
+}
+
+impl<K: Clone + Hash + Eq, V: Clone + Hash + Eq> Bijection<K, V> {
+    pub fn insert(&mut self, key: K, value: V) -> Option<(K, V)> {
+        let key_clone = key.clone();
+        let value_clone = value.clone();
+        let old_value = self.k2v.get_mut().insert(key, value);
+        let old_key = self.v2k.get_mut().insert(value_clone, key_clone);
+        match (old_key, old_value) {
+            (Some(old_key), Some(old_value)) => Some((old_key, old_value)),
+            (None, None) => None,
+            _ => {
+                error!("logic error: unmatched key-value pair encountered in bijection insert");
+                None
+            }
+        }
+    }
+}
+
+impl<K: Hash + Eq, V> Bijection<K, V> {
+    pub fn value<Q: ?Sized + Hash + Eq>(&self, key: &Q) -> &V
+    where
+        K: Borrow<Q>
+    {
+        self.k2v.get(key)
+    }
+}
+
+impl<K, V: Hash + Eq> Bijection<K, V> {
+    pub fn key<R: ?Sized + Hash + Eq>(&self, value: &R) -> &K
+    where
+        V: Borrow<R>
+    {
+        self.v2k.get(value)
+    }
+}

+ 53 - 0
crates/btlib/src/collections/hash_map_with_default.rs

@@ -0,0 +1,53 @@
+//! This module defines [HashMapWithDefault], which is a newtype wrapping a [HashMap] which allows
+//! a default value to be defined.
+//! When a key is not found in the [HashMap] the default value is returned, instead of [None].
+
+use std::{
+    borrow::Borrow,
+    collections::HashMap,
+    hash::Hash,
+    ops::Index,
+};
+
+pub struct HashMapWithDefault<K, V> {
+    hash_map: HashMap<K, V>,
+    default: V,
+}
+
+impl<K, V> HashMapWithDefault<K, V> {
+    pub fn new(default: V) -> Self {
+        Self { hash_map: HashMap::new(), default }
+    }
+
+    pub fn get_ref(&self) -> &HashMap<K, V> {
+        &self.hash_map
+    }
+
+    pub fn get_mut(&mut self) -> &mut HashMap<K, V> {
+        &mut self.hash_map
+    }
+
+    pub fn default(&self) -> &V {
+        &self.default
+    }
+
+    pub fn mut_default(&mut self) -> &mut V {
+        &mut self.default
+    }
+}
+
+impl<K: Hash + Eq, V> HashMapWithDefault<K, V> {
+    pub fn get<Q: ?Sized + Hash + Eq>(&self, index: &Q) -> &V
+    where
+        K: Borrow<Q>,
+    {
+        self.hash_map.get(index).unwrap_or(&self.default)
+    }
+}
+
+impl<Q: ?Sized + Hash + Eq, K: Borrow<Q> + Hash + Eq, V> Index<&Q> for HashMapWithDefault<K, V> {
+    type Output = V;
+    fn index(&self, index: &Q) -> &Self::Output {
+        self.get(index)
+    }
+}

+ 99 - 2
crates/btlib/src/crypto.rs

@@ -32,6 +32,7 @@ use serde::{
     ser::{SerializeStruct, Serializer},
 };
 use std::{
+    cell::RefCell,
     fmt::Display,
     io::{Read, Write},
     marker::PhantomData,
@@ -530,6 +531,85 @@ impl HashKind {
     }
 }
 
+/// An implementation of [std::hash::Hasher] which allows cryptographic hash algorithms to be used.
+pub struct BtHasher {
+    hasher: RefCell<Hasher>,
+}
+
+impl BtHasher {
+    pub fn new(kind: HashKind) -> Result<Self> {
+        btensure!(
+            kind.len() >= 8,
+            bterr!("only digests which produce at least 8 bytes are supported")
+        );
+        let hasher = RefCell::new(Hasher::new(kind.into())?);
+        Ok(Self { hasher })
+    }
+}
+
+impl std::hash::Hasher for BtHasher {
+    fn write(&mut self, bytes: &[u8]) {
+        let hasher = self.hasher.get_mut();
+        hasher.update(bytes).unwrap();
+    }
+
+    fn finish(&self) -> u64 {
+        let mut hasher = self.hasher.borrow_mut();
+        let hash = hasher.finish().unwrap();
+        let mut buf = [0u8; 8];
+        buf.copy_from_slice(&hash[..8]);
+        u64::from_le_bytes(buf)
+    }
+
+    fn write_u8(&mut self, i: u8) {
+        self.write(&[i])
+    }
+
+    fn write_u16(&mut self, i: u16) {
+        self.write(&i.to_le_bytes())
+    }
+
+    fn write_u32(&mut self, i: u32) {
+        self.write(&i.to_le_bytes())
+    }
+
+    fn write_u64(&mut self, i: u64) {
+        self.write(&i.to_le_bytes())
+    }
+
+    fn write_u128(&mut self, i: u128) {
+        self.write(&i.to_le_bytes())
+    }
+
+    fn write_usize(&mut self, i: usize) {
+        self.write(&i.to_le_bytes())
+    }
+
+    fn write_i8(&mut self, i: i8) {
+        self.write_u8(i as u8)
+    }
+
+    fn write_i16(&mut self, i: i16) {
+        self.write_u16(i as u16)
+    }
+
+    fn write_i32(&mut self, i: i32) {
+        self.write_u32(i as u32)
+    }
+
+    fn write_i64(&mut self, i: i64) {
+        self.write_u64(i as u64)
+    }
+
+    fn write_i128(&mut self, i: i128) {
+        self.write_u128(i as u128)
+    }
+
+    fn write_isize(&mut self, i: isize) {
+        self.write_usize(i as usize)
+    }
+}
+
 impl TryFrom<MessageDigest> for HashKind {
     type Error = crate::Error;
 
@@ -1560,8 +1640,8 @@ impl Verifier for AsymKeyPair<Sign> {
 
 #[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct ConcretePub {
-    sign: AsymKeyPub<Sign>,
-    enc: AsymKeyPub<Encrypt>,
+    pub sign: AsymKeyPub<Sign>,
+    pub enc: AsymKeyPub<Encrypt>,
 }
 
 impl Principaled for ConcretePub {
@@ -1596,6 +1676,10 @@ impl CredsPub for ConcretePub {
     fn public_sign(&self) -> &AsymKey<Public, Sign> {
         &self.sign
     }
+
+    fn concrete_pub(&self) -> ConcretePub {
+        self.clone()
+    }
 }
 
 impl PartialEq for ConcretePub {
@@ -1675,6 +1759,13 @@ impl CredsPub for ConcreteCreds {
     fn public_sign(&self) -> &AsymKey<Public, Sign> {
         &self.sign.public
     }
+
+    fn concrete_pub(&self) -> ConcretePub {
+        ConcretePub {
+            sign: self.sign.public.clone(),
+            enc: self.encrypt.public.clone(),
+        }
+    }
 }
 
 impl Signer for ConcreteCreds {
@@ -2000,6 +2091,8 @@ 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>;
 
+    fn concrete_pub(&self) -> ConcretePub;
+
     fn sign_kind(&self) -> Sign {
         Verifier::kind(self)
     }
@@ -2009,6 +2102,10 @@ impl<T: CredsPub> CredsPub for &T {
     fn public_sign(&self) -> &AsymKey<Public, Sign> {
         (*self).public_sign()
     }
+
+    fn concrete_pub(&self) -> ConcretePub {
+        (*self).concrete_pub()
+    }
 }
 
 /// Trait for types which contain private credentials.

+ 9 - 5
crates/btlib/src/crypto/tpm.rs

@@ -1375,6 +1375,13 @@ impl CredsPub for TpmCreds {
     fn public_sign(&self) -> &AsymKeyPub<Sign> {
         &self.sign.public
     }
+
+    fn concrete_pub(&self) -> ConcretePub {
+        ConcretePub {
+            sign: self.sign.public.clone(),
+            enc: self.enc.public.clone(),
+        }
+    }
 }
 
 pub struct TpmSignOp<'a> {
@@ -1527,13 +1534,10 @@ impl HasResponseCode for TSS2_RC {
 mod test {
     use super::*;
 
-    use crate::{
-        test_helpers::BtCursor,
-        error::AnyhowErrorExt,
-    };
-    use swtpm_harness::SwtpmHarness;
+    use crate::{error::AnyhowErrorExt, test_helpers::BtCursor};
     use ctor::ctor;
     use std::{fs::File, io::SeekFrom};
+    use swtpm_harness::SwtpmHarness;
     use tss_esapi::{
         interface_types::ecc::EccCurve,
         structures::{EccPoint, EccScheme, KeyDerivationFunctionScheme, PublicEccParameters},

+ 97 - 57
crates/btlib/src/lib.rs

@@ -1,6 +1,5 @@
 pub mod accessor;
 mod block_path;
-pub mod blocktree;
 pub mod buf_reader;
 /// Code which enables cryptographic operations.
 pub mod crypto;
@@ -10,6 +9,7 @@ pub mod sectored_buf;
 #[cfg(test)]
 mod test_helpers;
 mod trailered;
+pub mod collections;
 
 #[macro_use]
 extern crate static_assertions;
@@ -23,10 +23,10 @@ use brotli::{CompressorWriter, Decompressor};
 use btserde::{read_from, write_to};
 use fuse_backend_rs::abi::fuse_abi::{stat64, Attr};
 use positioned_io::{ReadAt, Size, WriteAt};
-use serde::{Deserialize, Serialize};
+use serde::{de::DeserializeOwned, Deserialize, Serialize};
 use serde_big_array::BigArray;
 use std::{
-    collections::{btree_map, BTreeMap, HashMap},
+    collections::{btree_map, BTreeMap},
     convert::{Infallible, TryFrom},
     fmt::{self, Display, Formatter},
     hash::Hash as Hashable,
@@ -55,6 +55,8 @@ pub enum BlockError {
     NoBlockKey,
     NoBlockPath,
     UnknownSize,
+    ProcRecNotIssued,
+    ProcRecRevoked,
 }
 
 impl Display for BlockError {
@@ -67,6 +69,10 @@ impl Display for BlockError {
             BlockError::NoBlockKey => write!(f, "no block key is present"),
             BlockError::NoBlockPath => write!(f, "no block path was specified"),
             BlockError::UnknownSize => write!(f, "size could not be determined"),
+            BlockError::ProcRecNotIssued => {
+                write!(f, "a process record was requested by not yet issued")
+            }
+            BlockError::ProcRecRevoked => write!(f, "this process record has been revoked"),
         }
     }
 }
@@ -132,24 +138,41 @@ pub trait Block: ReadAt + WriteAt + MetaAccess + Sectored + FlushMeta {}
 
 impl<T: ReadAt + WriteAt + MetaAccess + Sectored + FlushMeta + ?Sized> Block for T {}
 
+/// Deserializes an instance of the given type from the given block.
+fn read_from_block<T: DeserializeOwned, B: BlockReader + ?Sized>(block: &mut B) -> Result<T> {
+    block.rewind()?;
+    let mut block = block;
+    let dir: T = read_from(&mut block)?;
+    Ok(dir)
+}
+
 pub trait BlockReader: Read + Seek + AsRef<BlockMeta> + Size + Sectored {
     fn read_dir(&mut self) -> Result<Directory> {
-        self.rewind()?;
-        let mut sich = self;
-        let dir: Directory = read_from(&mut sich)?;
-        Ok(dir)
+        read_from_block::<Directory, _>(self)
+    }
+
+    fn read_proc(&mut self) -> Result<ProcRec> {
+        read_from_block::<ProcRec, _>(self)
     }
 }
 
 impl<T: Read + Seek + AsRef<BlockMeta> + Size + Sectored + ?Sized> BlockReader for T {}
 
+fn write_to_block<T: Serialize, B: BlockAccessor + ?Sized>(block: &mut B, value: &T) -> Result<()> {
+    block.rewind()?;
+    let mut sich = block;
+    write_to(value, &mut sich)?;
+    sich.flush()?;
+    Ok(())
+}
+
 pub trait BlockAccessor: BlockReader + Write + MetaAccess {
     fn write_dir(&mut self, dir: &Directory) -> Result<()> {
-        self.rewind()?;
-        let mut sich = self;
-        write_to(dir, &mut sich)?;
-        sich.flush()?;
-        Ok(())
+        write_to_block(self, dir)
+    }
+
+    fn write_proc_rec(&mut self, proc_rec: &ProcRec) -> Result<()> {
+        write_to_block(self, proc_rec)
     }
 }
 
@@ -500,15 +523,18 @@ impl SeekFromExt for SeekFrom {
     }
 }
 
+/// The type used to identify blocks.
+pub type Inode = u64;
+
 /// A unique identifier for a block.
 #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Hash)]
 pub struct BlockId {
     pub generation: u64,
-    pub inode: u64,
+    pub inode: Inode,
 }
 
 impl BlockId {
-    pub fn new(generation: u64, inode: u64) -> BlockId {
+    pub fn new(generation: u64, inode: Inode) -> BlockId {
         BlockId { generation, inode }
     }
 }
@@ -734,10 +760,8 @@ impl BlockMetaBody {
     }
 
     pub fn use_readcap_for<C: Creds>(&mut self, creds: &C) -> Result<&SymKey> {
-        let readcap = self
-            .readcaps
-            .get(&creds.principal())
-            .ok_or(crypto::Error::NoReadCap)?;
+        let owner = creds.principal();
+        let readcap = self.readcaps.get(&owner).ok_or(crypto::Error::NoReadCap)?;
         let block_key = creds.ser_decrypt(readcap)?;
         self.block_key = Some(block_key);
         self.block_key()
@@ -1156,51 +1180,72 @@ impl Fragment {
     }
 }
 
-#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
-/// Structure for keeping track of block information in a directory.
-pub struct BlockRecord {
-    /// The ID of the block. Note that this is only guaranteed to be unique in the directory this
-    /// record is stored in.
-    pub inode: u64,
-    /// A mapping from fragment serial numbers to fragment records.
-    /// TODO: Does this need to have a consistent order?
-    /// TODO: Move this into the block metadata.
-    pub frags: HashMap<FragmentSerial, FragmentRecord>,
-}
-
-impl BlockRecord {
-    pub fn new(inode: u64) -> BlockRecord {
-        BlockRecord {
-            inode,
-            frags: HashMap::new(),
-        }
-    }
-}
-
 #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
 /// Structure for keeping track of server information in a directory.
 pub struct ServerRecord {
     /// The most up-to-date address for this server.
-    addr: SocketAddr,
+    pub addr: SocketAddr,
     /// The public credentials for this server.
-    pub_creds: ConcretePub,
+    pub pub_creds: ConcretePub,
+}
+
+#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
+/// Attributes associated with a principal which are used for authorization decisions.
+pub struct AuthzAttrs {
+    /// The user ID of the process being authorized.
+    pub uid: u32,
+    /// The group ID of the process being authorized.
+    pub gid: u32,
+    /// The group IDs of the supplemental groups in which a process is a member.
+    pub supp_gids: Vec<u32>,
+}
+
+#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
+pub struct IssuedProcRec {
+    pub addr: SocketAddr,
+    pub pub_creds: ConcretePub,
+    pub writecap: Writecap,
+    pub authz_attrs: AuthzAttrs,
+}
+
+#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
+/// Structure stored in process blocks for keeping track of process credentials and location.
+pub enum ProcRec {
+    Requested {
+        addr: SocketAddr,
+        pub_creds: ConcretePub,
+    },
+    Valid(IssuedProcRec),
+    Revoked(IssuedProcRec),
+}
+
+impl ProcRec {
+    pub fn validate(self) -> Result<IssuedProcRec> {
+        match self {
+            Self::Requested { .. } => Err(BlockError::ProcRecNotIssued.into()),
+            Self::Valid(valid) => Ok(valid),
+            Self::Revoked(..) => Err(BlockError::ProcRecRevoked.into()),
+        }
+    }
 }
 
 #[derive(Debug, PartialEq, Serialize, Deserialize, EnumDiscriminants, Clone)]
 #[strum_discriminants(derive(FromRepr, Display, Serialize, Deserialize))]
 #[strum_discriminants(name(DirEntryKind))]
 pub enum DirEntry {
-    Directory(BlockRecord),
-    File(BlockRecord),
-    Server(ServerRecord),
+    Directory(Inode),
+    File(Inode),
+    Server(Inode),
+    Process(Inode),
 }
 
 impl DirEntry {
-    pub fn inode(&self) -> Option<u64> {
+    pub fn inode(&self) -> Inode {
         match self {
-            Self::Directory(record) => Some(record.inode),
-            Self::File(record) => Some(record.inode),
-            Self::Server(..) => None,
+            Self::Directory(inode) => *inode,
+            Self::File(inode) => *inode,
+            Self::Server(inode) => *inode,
+            Self::Process(inode) => *inode,
         }
     }
 
@@ -1209,6 +1254,7 @@ impl DirEntry {
             Self::Directory(..) => libc::DT_DIR,
             Self::File(..) => libc::DT_REG,
             Self::Server(..) => libc::DT_UNKNOWN,
+            Self::Process(..) => libc::DT_UNKNOWN,
         }
     }
 }
@@ -1227,21 +1273,15 @@ impl Directory {
         }
     }
 
-    pub fn add_file(&mut self, name: String, inode: u64) -> Result<&mut BlockRecord> {
+    pub fn add_file(&mut self, name: String, inode: Inode) -> Result<()> {
         let entry = match self.entries.entry(name) {
             btree_map::Entry::Occupied(entry) => {
                 return Err(bterr!("directory already contains entry '{}'", entry.key()));
             }
             btree_map::Entry::Vacant(entry) => entry,
         };
-        let dir_entry = entry.insert(DirEntry::File(BlockRecord {
-            inode,
-            frags: HashMap::new(),
-        }));
-        match dir_entry {
-            DirEntry::File(record) => Ok(record),
-            _ => Err(bterr!("DirEntry was not the File variant")),
-        }
+        entry.insert(DirEntry::File(inode));
+        Ok(())
     }
 
     pub fn num_entries(&self) -> usize {

+ 7 - 16
crates/btmsg/src/lib.rs

@@ -21,8 +21,7 @@ use quinn::{Connection, Endpoint, RecvStream, SendStream};
 use serde::{de::DeserializeOwned, Deserialize, Serialize};
 use std::{
     any::Any,
-    collections::hash_map::DefaultHasher,
-    hash::{Hash, Hasher},
+    hash::Hash,
     marker::PhantomData,
     net::{IpAddr, Ipv6Addr, SocketAddr},
     sync::Arc,
@@ -104,21 +103,13 @@ impl BlockAddr {
         self.path.as_ref()
     }
 
-    fn port(&self) -> u16 {
-        // TODO: We should probably choose a stable hasher, as the standard library devs could
-        // change `DefaultHasher` at any time.
-        let mut hasher = DefaultHasher::new();
-        self.path.hash(&mut hasher);
-        let hash = hasher.finish();
-        // We compute a port in the dynamic range [49152, 65535] as defined by RFC 6335.
-        const NUM_RES_PORTS: u16 = 49153;
-        const PORTS_AVAIL: u64 = (u16::MAX - NUM_RES_PORTS) as u64;
-        NUM_RES_PORTS + (hash % PORTS_AVAIL) as u16
+    fn port(&self) -> Result<u16> {
+        self.path.port()
     }
 
     /// Returns the socket address of the block this instance refers to.
-    pub fn socket_addr(&self) -> SocketAddr {
-        SocketAddr::new(self.ip_addr, self.port())
+    pub fn socket_addr(&self) -> Result<SocketAddr> {
+        Ok(SocketAddr::new(self.ip_addr, self.port()?))
     }
 }
 
@@ -431,7 +422,7 @@ impl QuicReceiver {
         resolver: Arc<CertResolver>,
         callback: F,
     ) -> Result<Self> {
-        let socket_addr = recv_addr.socket_addr();
+        let socket_addr = recv_addr.socket_addr()?;
         let endpoint = Endpoint::server(server_config(resolver.clone())?, socket_addr)?;
         let (stop_tx, stop_rx) = broadcast::channel(1);
         tokio::spawn(Self::server_loop(endpoint.clone(), callback, stop_rx));
@@ -582,7 +573,7 @@ impl QuicTransmitter {
         addr: Arc<BlockAddr>,
         resolver: Arc<CertResolver>,
     ) -> Result<Self> {
-        let socket_addr = addr.socket_addr();
+        let socket_addr = addr.socket_addr()?;
         let connecting = endpoint.connect_with(
             client_config(addr.path.clone(), resolver)?,
             socket_addr,

+ 13 - 1
crates/btserde/src/de.rs

@@ -456,7 +456,7 @@ impl<'de, T: Reader<'de>> de::EnumAccess<'de> for &mut Deserializer<T> {
 
 #[cfg(test)]
 mod test {
-    use crate::from_slice;
+    use crate::{from_slice, to_vec};
 
     use super::{from_vec, num_bytes_for_char, Result};
     use serde::Deserialize;
@@ -758,4 +758,16 @@ mod test {
         assert_eq!(expected, result?);
         Ok(())
     }
+
+    #[test]
+    fn deserialize_socket_addr() -> Result<()> {
+        use std::net::{IpAddr, Ipv6Addr, SocketAddr};
+        let expected = SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 1337);
+        let vec = to_vec(&expected)?;
+
+        let actual = from_vec(&vec)?;
+
+        assert_eq!(expected, actual);
+        Ok(())
+    }
 }

+ 4 - 0
crates/btserde/src/ser.rs

@@ -289,6 +289,10 @@ impl<'a, T: Write> ser::Serializer for &'a mut Serializer<T> {
         self.serialize_u16(index)?;
         Ok(self)
     }
+
+    fn is_human_readable(&self) -> bool {
+        false
+    }
 }
 
 impl<'a, W: Write> SerializeSeq for &'a mut Serializer<W> {