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

Started working on adding FUSE support.

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

+ 1 - 1
.vscode/launch.json

@@ -19,7 +19,7 @@
             "request": "launch",
             "name": "Debug unit tests",
             "cargo": {
-                "args": [ "test" ]
+                "args": [ "+nightly", "test" ]
             },
             "args": [],
             "cwd": "${workspaceFolder}"

+ 211 - 1
Cargo.lock

@@ -35,6 +35,12 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "arc-swap"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "983cd8b9d4b02a6dc6ffa557262eb5858a27a0038ffffe21a0f133eaa819a164"
+
 [[package]]
 name = "atty"
 version = "0.2.14"
@@ -134,10 +140,11 @@ dependencies = [
  "dbus",
  "env_logger",
  "foreign-types",
+ "fuse-backend-rs",
  "harness",
  "lazy_static",
  "log",
- "nix",
+ "nix 0.25.0",
  "openssl",
  "os_pipe",
  "serde",
@@ -148,6 +155,7 @@ dependencies = [
  "tempdir",
  "tss-esapi",
  "tss-esapi-sys",
+ "vm-memory",
  "zerocopy",
  "zeroize",
 ]
@@ -177,6 +185,16 @@ version = "1.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
 
+[[package]]
+name = "caps"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "938c50180feacea622ef3b8f4a496057c868dcf8ac7a64d781dd8f3f51a9c143"
+dependencies = [
+ "libc",
+ "thiserror",
+]
+
 [[package]]
 name = "cc"
 version = "1.0.73"
@@ -224,6 +242,12 @@ dependencies = [
  "vec_map",
 ]
 
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
+
 [[package]]
 name = "ctor"
 version = "0.1.23"
@@ -305,6 +329,30 @@ version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
 
+[[package]]
+name = "fuse-backend-rs"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "994a3bfb694ee52bf8f3bca80d784b723f150810998219337e429cc5dbe92717"
+dependencies = [
+ "arc-swap",
+ "bitflags",
+ "caps",
+ "core-foundation-sys",
+ "io-uring",
+ "lazy_static",
+ "libc",
+ "log",
+ "mio",
+ "nix 0.24.2",
+ "scoped-tls",
+ "slab",
+ "socket2",
+ "tokio-uring",
+ "vm-memory",
+ "vmm-sys-util",
+]
+
 [[package]]
 name = "glob"
 version = "0.3.0"
@@ -349,6 +397,16 @@ version = "2.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
 
+[[package]]
+name = "io-uring"
+version = "0.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d32c9c053ad47572e11da8bce622ed4c9ae9dedac5b7f678a2e876d1494d4c4"
+dependencies = [
+ "bitflags",
+ "libc",
+]
+
 [[package]]
 name = "lazy_static"
 version = "1.4.0"
@@ -427,6 +485,30 @@ version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
 
+[[package]]
+name = "mio"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf"
+dependencies = [
+ "libc",
+ "log",
+ "wasi",
+ "windows-sys",
+]
+
+[[package]]
+name = "nix"
+version = "0.24.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "195cdbc1741b8134346d515b3a56a1c94b0912758009cfd53f99ea0f57b065fc"
+dependencies = [
+ "bitflags",
+ "cfg-if",
+ "libc",
+ "memoffset",
+]
+
 [[package]]
 name = "nix"
 version = "0.25.0"
@@ -596,6 +678,12 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "pin-project-lite"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
+
 [[package]]
 name = "pin-utils"
 version = "0.1.0"
@@ -710,6 +798,12 @@ version = "1.0.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8"
 
+[[package]]
+name = "scoped-tls"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
+
 [[package]]
 name = "semver"
 version = "0.11.0"
@@ -772,6 +866,25 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
 
+[[package]]
+name = "slab"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "socket2"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
 [[package]]
 name = "stable_deref_trait"
 version = "1.2.0"
@@ -897,6 +1010,34 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "tokio"
+version = "1.21.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099"
+dependencies = [
+ "autocfg",
+ "libc",
+ "mio",
+ "pin-project-lite",
+ "socket2",
+ "winapi",
+]
+
+[[package]]
+name = "tokio-uring"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3ad494f39874984d990ade7f6319dafbcd3301ff0b1841f8a55a1ebb3e742c8"
+dependencies = [
+ "io-uring",
+ "libc",
+ "scoped-tls",
+ "slab",
+ "socket2",
+ "tokio",
+]
+
 [[package]]
 name = "tss-esapi"
 version = "7.1.0"
@@ -966,6 +1107,32 @@ version = "0.8.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
 
+[[package]]
+name = "vm-memory"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "583f213899e8a5eea23d9c507252d4bed5bc88f0ecbe0783262f80034630744b"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "vmm-sys-util"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08604d7be03eb26e33b3cee3ed4aef2bf550b305d1cca60e84da5d28d3790b62"
+dependencies = [
+ "bitflags",
+ "libc",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
 [[package]]
 name = "which"
 version = "4.3.0"
@@ -1008,6 +1175,49 @@ version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
 
+[[package]]
+name = "windows-sys"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
+dependencies = [
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
+
 [[package]]
 name = "zerocopy"
 version = "0.6.1"

+ 4 - 0
TODO.txt

@@ -75,3 +75,7 @@ independently know by any verified. This will ensure that path names are not sto
 
 - 19, 21, mdcarr941@gmail.com, 8665339,
 Integrate with tokio and add async methods to all of the stream types.
+
+- 20, 5, mdcarr941@gmail.com, ef1d43,
+Rewrite BlockPath to be more efficient by ensuring that all characters in a path are contiguous
+in memory.

+ 2 - 0
crates/btlib/Cargo.toml

@@ -24,6 +24,7 @@ static_assertions = "1.1.0"
 brotli = "3.3.4"
 os_pipe = { version = "1.1.1", features = ["io_safety"] }
 zerocopy = "0.6.1"
+fuse-backend-rs = "0.9.6"
 
 [dev-dependencies]
 tempdir = "0.3.7"
@@ -32,3 +33,4 @@ nix = "0.25.0"
 env_logger = "0.9.0"
 lazy_static = "1.4.0"
 dbus = "0.9.6"
+vm-memory = "0.9.0"

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

@@ -0,0 +1,412 @@
+pub use private::Blocktree;
+
+mod private {
+    use btserde::{read_from, write_to};
+    use fuse_backend_rs::{
+        abi::fuse_abi::{Attr, CreateIn},
+        api::filesystem::{
+            Context, Entry, FileSystem, OpenOptions, ZeroCopyReader, ZeroCopyWriter,
+        },
+    };
+    use serde::{Deserialize, Serialize};
+    use std::{
+        collections::hash_map::{self, HashMap},
+        ffi::CStr,
+        fs::File,
+        io::{self, SeekFrom, Write},
+        path::{Path, PathBuf},
+        sync::{
+            atomic::{AtomicU64, Ordering},
+            RwLock,
+        },
+        time::Duration,
+    };
+
+    use crate::{crypto::Creds, Block, BlockOpenOptions, BoxInIoErr, Directory, Error, Result};
+
+    type Inode = u64;
+
+    #[repr(u64)]
+    pub enum SpecInodes {
+        Sb = 1,
+        RootDir = 2,
+        FirstFree = 11,
+    }
+
+    impl From<SpecInodes> for Inode {
+        fn from(special: SpecInodes) -> Self {
+            special as Inode
+        }
+    }
+
+    type InodeTableValue = Box<dyn Block>;
+    type InodeTable = HashMap<Inode, InodeTableValue>;
+    type InodeTableEntry<'a> = hash_map::Entry<'a, Inode, InodeTableValue>;
+
+    /// 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> {
+        /// 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>,
+        /// 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,
+    }
+
+    impl<C: Creds + Clone + 'static> Blocktree<C> {
+        /// Creates a new empty blocktree at the given path.
+        pub fn new_empty(btdir: PathBuf, generation: u64, creds: C) -> Result<Blocktree<C>> {
+            let mut sb_block = Self::open_block(&btdir, SpecInodes::Sb.into(), creds.clone())?;
+            let sb = Superblock {
+                generation,
+                next_inode: SpecInodes::FirstFree.into(),
+            };
+            write_to(&sb, &mut sb_block)?;
+            sb_block.flush()?;
+
+            // Initialize the root directory.
+            let mut root_block =
+                Self::open_block(&btdir, SpecInodes::RootDir.into(), creds.clone())?;
+            write_to(&Directory::new(), &mut root_block)?;
+            root_block.flush()?;
+
+            Self::new(btdir, sb, sb_block, root_block, creds)
+        }
+
+        /// Opens an existing blocktree stored at the given path.
+        pub fn new_existing(btdir: PathBuf, creds: C) -> Result<Blocktree<C>> {
+            let mut sb_block = Self::open_block(&btdir, SpecInodes::Sb.into(), creds.clone())?;
+            let sb = read_from(&mut sb_block)?;
+            let root_block = Self::open_block(&btdir, SpecInodes::RootDir.into(), creds.clone())?;
+            Self::new(btdir, sb, sb_block, root_block, creds)
+        }
+
+        fn new(
+            btdir: PathBuf,
+            sb: Superblock,
+            sb_block: Box<dyn Block>,
+            root_block: Box<dyn Block>,
+            creds: C,
+        ) -> Result<Blocktree<C>> {
+            let mut inodes = HashMap::with_capacity(1);
+            inodes.insert(SpecInodes::Sb.into(), sb_block);
+            inodes.insert(SpecInodes::RootDir.into(), root_block);
+            Ok(Blocktree {
+                path: btdir,
+                inodes: RwLock::new(inodes),
+                next_inode: AtomicU64::new(sb.next_inode),
+                generation: sb.generation,
+                creds,
+            })
+        }
+
+        /// 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) -> Result<Box<dyn Block>> {
+            let path = Self::block_path(&btdir, inode);
+            let dir = path.ancestors().skip(1).next().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)
+        }
+
+        fn open_block_file(file: File, creds: C) -> Result<Box<dyn Block>> {
+            BlockOpenOptions::new()
+                .with_creds(creds)
+                .with_compress(false)
+                .with_encrypt(true)
+                .with_inner(file)
+                .open()
+        }
+
+        fn inode_release(&self, inode: Inode) -> io::Result<()> {
+            let mut inodes = self
+                .inodes
+                .write()
+                .map_err(|err| Error::custom(err.to_string()))?;
+            inodes.remove(&inode);
+            Ok(())
+        }
+
+        fn access_entry<T, F: FnOnce(InodeTableEntry) -> io::Result<T>>(
+            &self,
+            inode: Inode,
+            cb: F,
+        ) -> io::Result<T> {
+            let mut inodes = self
+                .inodes
+                .write()
+                .map_err(|err| Error::custom(err.to_string()))?;
+            let entry = inodes.entry(inode);
+            cb(entry)
+        }
+
+        fn access_block<T, F: FnOnce(&mut Box<dyn Block>) -> io::Result<T>>(
+            &self,
+            inode: Inode,
+            cb: F,
+        ) -> io::Result<T> {
+            self.access_entry(inode, |mut entry| match &mut entry {
+                InodeTableEntry::Vacant(_) => Err(Error::custom("Inode is not open").into()),
+                InodeTableEntry::Occupied(entry) => cb(entry.get_mut()),
+            })
+        }
+
+        /// Updates the superblock file with the next valid inode value.
+        fn update_sb(&self) -> io::Result<()> {
+            let sb = Superblock {
+                generation: self.generation,
+                next_inode: self.next_inode.load(Ordering::SeqCst),
+            };
+            self.ser_write(SpecInodes::Sb.into(), &sb)
+        }
+
+        fn open(&self, inode: Inode) -> io::Result<()> {
+            self.access_entry(inode, |entry| match entry {
+                InodeTableEntry::Vacant(entry) => {
+                    let block = Self::open_block(&self.path, inode, self.creds.clone())?;
+                    entry.insert(block);
+                    Ok(())
+                }
+                InodeTableEntry::Occupied(_) => Ok(()),
+            })
+        }
+
+        fn inode_write(
+            &self,
+            inode: Inode,
+            reader: &mut dyn ZeroCopyReader,
+            count: usize,
+            off: u64,
+        ) -> io::Result<usize> {
+            self.access_block(inode, |block| {
+                reader.read_to(&mut block.as_mut(), count, off)
+            })
+        }
+
+        fn ser_write<T: Serialize>(&self, inode: Inode, value: &T) -> io::Result<()> {
+            self.access_block(inode, |block| {
+                block.seek(SeekFrom::Start(0))?;
+                write_to(&value, block)?;
+                Ok(())
+            })
+        }
+
+        fn inode_read(
+            &self,
+            inode: Inode,
+            writer: &mut dyn ZeroCopyWriter,
+            count: usize,
+            off: u64,
+        ) -> io::Result<usize> {
+            self.access_block(inode, |block| {
+                writer.write_from(&mut block.as_mut(), count, off)
+            })
+        }
+    }
+
+    impl<C: Creds + Clone + 'static> FileSystem for Blocktree<C> {
+        type Inode = Inode;
+        type Handle = u64;
+
+        fn open(
+            &self,
+            _ctx: &Context,
+            inode: Self::Inode,
+            _flags: u32,
+            _fuse_flags: u32,
+        ) -> io::Result<(Option<Self::Handle>, OpenOptions)> {
+            self.open(inode)?;
+            Ok((None, OpenOptions::empty()))
+        }
+
+        fn create(
+            &self,
+            _ctx: &Context,
+            parent: Self::Inode,
+            name: &CStr,
+            _args: CreateIn,
+        ) -> std::io::Result<(Entry, Option<Self::Handle>, OpenOptions)> {
+            // Reserve a free inode.
+            // TODO: Obviously this strategy won't work when there are multiples servers in this
+            // generation.
+            let inode = self.next_inode.fetch_add(1, Ordering::SeqCst);
+            self.update_sb()?;
+
+            // Create the new file block.
+            self.open(inode)?;
+
+            // Add a directory entry to the parent for the new inode.
+            self.access_block(parent, |dir_block| {
+                dir_block.seek(SeekFrom::Start(0))?;
+                let mut dir: Directory = read_from(dir_block).box_err()?;
+                let name_str = name
+                    .to_str()
+                    .map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))?;
+                dir.add_file(name_str.to_owned(), inode)?;
+                dir_block.seek(SeekFrom::Start(0))?;
+                write_to(&dir, dir_block).box_err()?;
+                Ok(())
+            })?;
+
+            let entry = Entry {
+                inode,
+                generation: self.generation,
+                // TODO: These fields need to be initialized properly.
+                attr: Attr::default().into(),
+                attr_flags: 0,
+                attr_timeout: Duration::from_secs(5),
+                entry_timeout: Duration::from_secs(5),
+            };
+            // TODO: What is OpenOptions used for?
+            let options = OpenOptions::empty();
+            Ok((entry, None, options))
+        }
+
+        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: u32,
+            _fuse_flags: u32,
+        ) -> io::Result<usize> {
+            let count = size.try_into().box_err()?;
+            self.inode_write(inode, r, count, offset)
+        }
+
+        fn read(
+            &self,
+            _ctx: &Context,
+            inode: Self::Inode,
+            _handle: Self::Handle,
+            w: &mut dyn fuse_backend_rs::api::filesystem::ZeroCopyWriter,
+            size: u32,
+            offset: u64,
+            _lock_owner: Option<u64>,
+            _flags: u32,
+        ) -> io::Result<usize> {
+            let count = size.try_into().box_err()?;
+            self.inode_read(inode, w, count, offset)
+        }
+
+        fn release(
+            &self,
+            _ctx: &Context,
+            inode: Self::Inode,
+            _flags: u32,
+            _handle: Self::Handle,
+            flush: bool,
+            _flock_release: bool,
+            _lock_owner: Option<u64>,
+        ) -> io::Result<()> {
+            if flush {
+                self.access_block(inode, |entry| entry.flush())?;
+            };
+            self.inode_release(inode)
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::ffi::CString;
+
+    use fuse_backend_rs::api::filesystem::FileSystem;
+    use tempdir::TempDir;
+
+    use test_helpers::*;
+
+    use crate::test_helpers;
+
+    use super::{private::SpecInodes, *};
+
+    #[test]
+    fn create_write_open_read() {
+        let dir = TempDir::new("fuse").expect("failed to create temp dir");
+        let creds = test_helpers::NODE_CREDS.clone();
+        let bt = Blocktree::new_empty(dir.path().to_owned(), 0, creds)
+            .expect("failed to create empty blocktree");
+        let name = CString::new("README.md").unwrap();
+        let (entry, ..) = bt
+            .create(
+                &Default::default(),
+                SpecInodes::RootDir.into(),
+                name.as_c_str(),
+                Default::default(),
+            )
+            .expect("failed to create file");
+        bt.open(&Default::default(), entry.inode, 0, 0)
+            .expect("failed to open file");
+        const LEN: usize = 32;
+        let mut expected = BtCursor::new([1u8; LEN]);
+        let written = bt
+            .write(
+                &Default::default(),
+                entry.inode,
+                Default::default(),
+                &mut expected,
+                LEN as u32,
+                0,
+                None,
+                false,
+                0,
+                0,
+            )
+            .expect("write failed");
+        assert_eq!(LEN, written);
+
+        let mut actual = BtCursor::new([0u8; LEN]);
+        let read = bt
+            .read(
+                &Default::default(),
+                entry.inode,
+                Default::default(),
+                &mut actual,
+                LEN as u32,
+                0,
+                None,
+                0,
+            )
+            .expect("failed to read");
+        assert_eq!(LEN, read);
+
+        assert_eq!(expected, actual)
+    }
+}

+ 33 - 0
crates/btlib/src/crypto/merkle_stream.rs

@@ -10,6 +10,7 @@ mod private {
         BlockPath, BoxInIoErr, Decompose, MetaAccess, Principal, Sectored, TryCompose, WriteInteg,
         SECTOR_SZ_DEFAULT,
     };
+    use fuse_backend_rs::file_traits::FileReadWriteVolatile;
     use serde::{Deserialize, Serialize};
     use std::io::{self, Read, Seek, Write};
     use strum::EnumDiscriminants;
@@ -660,6 +661,38 @@ mod private {
             self.trailered.set_path(path)
         }
     }
+
+    impl<T: FileReadWriteVolatile> FileReadWriteVolatile for MerkleStream<T> {
+        fn read_volatile(
+            &mut self,
+            slice: fuse_backend_rs::file_buf::FileVolatileSlice,
+        ) -> io::Result<usize> {
+            self.trailered.read_volatile(slice)
+        }
+
+        fn write_volatile(
+            &mut self,
+            slice: fuse_backend_rs::file_buf::FileVolatileSlice,
+        ) -> io::Result<usize> {
+            self.trailered.write_volatile(slice)
+        }
+
+        fn read_at_volatile(
+            &mut self,
+            slice: fuse_backend_rs::file_buf::FileVolatileSlice,
+            offset: u64,
+        ) -> io::Result<usize> {
+            self.trailered.read_at_volatile(slice, offset)
+        }
+
+        fn write_at_volatile(
+            &mut self,
+            slice: fuse_backend_rs::file_buf::FileVolatileSlice,
+            offset: u64,
+        ) -> io::Result<usize> {
+            self.trailered.write_at_volatile(slice, offset)
+        }
+    }
 }
 
 #[cfg(test)]

+ 50 - 13
crates/btlib/src/crypto/mod.rs

@@ -25,8 +25,8 @@ use serde::{
     de::{self, DeserializeOwned, Deserializer, SeqAccess, Visitor},
     ser::{SerializeStruct, Serializer},
 };
-use std::{marker::PhantomData, num::TryFromIntError, str::FromStr};
-use strum_macros::{Display, EnumDiscriminants, EnumString};
+use std::{marker::PhantomData, num::TryFromIntError};
+use strum_macros::{Display, EnumDiscriminants, FromRepr};
 use zeroize::ZeroizeOnDrop;
 
 #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
@@ -219,7 +219,7 @@ pub fn rand_vec(len: usize) -> Result<Vec<u8>> {
     PartialOrd,
     Ord,
 )]
-#[strum_discriminants(derive(EnumString, Display, Serialize, Deserialize))]
+#[strum_discriminants(derive(FromRepr, Display, Serialize, Deserialize))]
 #[strum_discriminants(name(HashKind))]
 pub enum Hash {
     Sha2_256([u8; HashKind::Sha2_256.len()]),
@@ -277,7 +277,7 @@ impl From<HashKind> for MessageDigest {
 impl Hash {
     /// The character that's used to separate a hash type from its value in its string
     /// representation.
-    const HASH_SEP: char = ':';
+    const HASH_SEP: char = '!';
 
     pub fn kind(&self) -> HashKind {
         HashKind::from(self)
@@ -319,8 +319,12 @@ impl TryFrom<&str> for Hash {
             return Err(Error::InvalidHashFormat);
         };
         let second = split.pop().ok_or(Error::InvalidHashFormat)?;
-        let first = split.pop().ok_or(Error::InvalidHashFormat)?;
-        let mut hash = Hash::from(HashKind::from_str(first).map_err(|_| Error::InvalidHashFormat)?);
+        let first = split
+            .pop()
+            .ok_or(Error::InvalidHashFormat)?
+            .parse::<usize>()
+            .map_err(|_| Error::InvalidHashFormat)?;
+        let mut hash = Hash::from(HashKind::from_repr(first).ok_or(Error::InvalidHashFormat)?);
         base64_url::decode_to_slice(second, hash.as_mut()).map_err(|_| Error::InvalidHashFormat)?;
         Ok(hash)
     }
@@ -328,9 +332,9 @@ impl TryFrom<&str> for Hash {
 
 impl Display for Hash {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        let hash_type: HashKind = self.into();
+        let hash_kind: HashKind = self.into();
         let hash_data = base64_url::encode(self.as_ref());
-        write!(f, "{}{}{}", hash_type, Hash::HASH_SEP, hash_data)
+        write!(f, "{}{}{}", hash_kind as u32, Hash::HASH_SEP, hash_data)
     }
 }
 
@@ -1104,6 +1108,42 @@ impl Verifier for AsymKeyPair<Sign> {
     }
 }
 
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct PublicCreds {
+    sign: AsymKeyPub<Sign>,
+    enc: AsymKeyPub<Encrypt>,
+}
+
+impl Principaled for PublicCreds {
+    fn principal_of_kind(&self, kind: HashKind) -> Principal {
+        self.sign.principal_of_kind(kind)
+    }
+}
+
+impl Encrypter for PublicCreds {
+    fn encrypt(&self, slice: &[u8]) -> Result<Vec<u8>> {
+        self.enc.encrypt(slice)
+    }
+}
+
+impl Verifier for PublicCreds {
+    fn verify<'a, I: Iterator<Item = &'a [u8]>>(&self, parts: I, signature: &[u8]) -> Result<()> {
+        self.sign.verify(parts, signature)
+    }
+}
+
+impl CredsPub for PublicCreds {
+    fn public_sign(&self) -> &AsymKey<Public, Sign> {
+        &self.sign
+    }
+}
+
+impl PartialEq for PublicCreds {
+    fn eq(&self, other: &Self) -> bool {
+        self.principal() == other.principal()
+    }
+}
+
 #[derive(Clone)]
 pub struct ConcreteCreds {
     sign: AsymKeyPair<Sign>,
@@ -1259,7 +1299,7 @@ pub trait CredsPriv: Decrypter + Signer {
 }
 
 /// Trait for types which contain both public and private credentials.
-pub trait Creds: CredsPriv + CredsPub {
+pub trait Creds: CredsPriv + CredsPub + Clone {
     fn issue_writecap(
         &self,
         issued_to: Principal,
@@ -1445,10 +1485,7 @@ mod tests {
     fn hash_to_string() {
         let hash = make_principal().0;
         let string = hash.to_string();
-        assert_eq!(
-            "Sha2_256:dSip4J0kurN5VhVo_aTipM-ywOOWrqJuRRVQ7aa-bew",
-            string
-        )
+        assert_eq!("0!dSip4J0kurN5VhVo_aTipM-ywOOWrqJuRRVQ7aa-bew", string)
     }
 
     #[test]

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

@@ -1,6 +1,8 @@
 pub use private::SecretStream;
 
 mod private {
+    use fuse_backend_rs::file_traits::FileReadWriteVolatile;
+
     use crate::{
         crypto::{Encrypter, Error, Result, SymKey},
         Block, BlockPath, Decompose, MetaAccess, Principal, Sectored, TryCompose,
@@ -182,7 +184,39 @@ mod private {
         }
     }
 
-    impl<T: Read + Write + Seek + MetaAccess> Block for SecretStream<T> {}
+    impl<T: FileReadWriteVolatile> FileReadWriteVolatile for SecretStream<T> {
+        fn read_volatile(
+            &mut self,
+            slice: fuse_backend_rs::file_buf::FileVolatileSlice,
+        ) -> io::Result<usize> {
+            self.inner.read_volatile(slice)
+        }
+
+        fn write_volatile(
+            &mut self,
+            slice: fuse_backend_rs::file_buf::FileVolatileSlice,
+        ) -> io::Result<usize> {
+            self.inner.write_volatile(slice)
+        }
+
+        fn read_at_volatile(
+            &mut self,
+            slice: fuse_backend_rs::file_buf::FileVolatileSlice,
+            offset: u64,
+        ) -> io::Result<usize> {
+            self.inner.read_at_volatile(slice, offset)
+        }
+
+        fn write_at_volatile(
+            &mut self,
+            slice: fuse_backend_rs::file_buf::FileVolatileSlice,
+            offset: u64,
+        ) -> io::Result<usize> {
+            self.inner.write_at_volatile(slice, offset)
+        }
+    }
+
+    impl<T: Read + Write + Seek + MetaAccess + FileReadWriteVolatile> Block for SecretStream<T> {}
 }
 
 #[cfg(test)]

+ 131 - 11
crates/btlib/src/lib.rs

@@ -1,5 +1,9 @@
+#![allow(incomplete_features)]
+#![feature(trait_upcasting)]
+
 mod block_path;
 pub use block_path::{BlockPath, BlockPathError};
+pub mod blocktree;
 /// Code which enables cryptographic operations.
 pub mod crypto;
 pub mod msg;
@@ -20,19 +24,21 @@ use brotli::{CompressorWriter, Decompressor};
 use btserde::{self, write_to};
 use crypto::{
     AsymKeyPub, Ciphertext, Creds, Decrypter, DecrypterExt, Encrypter, EncrypterExt, Hash,
-    HashKind, MerkleStream, SecretStream, Sign, Signature, Signer, SymKey, SymKeyKind,
+    HashKind, MerkleStream, PublicCreds, SecretStream, Sign, Signature, Signer, SymKey, SymKeyKind,
 };
 
+use fuse_backend_rs::file_traits::FileReadWriteVolatile;
 use log::error;
 use sectored_buf::SectoredBuf;
 use serde::{Deserialize, Serialize};
 use serde_big_array::BigArray;
 use std::{
-    collections::{BTreeMap, HashMap},
+    collections::{btree_map, BTreeMap, HashMap},
     convert::{Infallible, TryFrom},
     fmt::{self, Display, Formatter},
     hash::Hash as Hashable,
     io::{self, Read, Seek, SeekFrom, Write},
+    net::SocketAddr,
     ops::{Add, Sub},
     sync::PoisonError,
     time::{Duration, SystemTime},
@@ -136,7 +142,7 @@ impl<T, E: Display> StrInIoErr<T> for std::result::Result<T, E> {
 const SECTOR_SZ_DEFAULT: usize = 4096;
 
 /// Trait for types which provide read and write access to blocks.
-pub trait Block: Read + Write + Seek + MetaAccess {}
+pub trait Block: Read + Write + Seek + MetaAccess + FileReadWriteVolatile {}
 
 // A trait for streams which only allow reads and writes in fixed sized units called sectors.
 pub trait Sectored {
@@ -203,7 +209,7 @@ trait ReadExt: Read {
                     if dest_len_start == dest.len() {
                         return Err(err);
                     } else {
-                        // We're not allowed to return an error if we've already read from self.
+                        // We're not allowed to return an error since we've already read from self.
                         error!("an error occurred in fill_buf: {}", err);
                         break;
                     }
@@ -371,6 +377,43 @@ impl<T, C: Decrypter + Principaled> MetaAccess for BlockStream<T, C> {
     }
 }
 
+impl<T: FileReadWriteVolatile, C> FileReadWriteVolatile for BlockStream<T, C> {
+    fn read_volatile(
+        &mut self,
+        slice: fuse_backend_rs::file_buf::FileVolatileSlice,
+    ) -> io::Result<usize> {
+        self.trailered.read_volatile(slice)
+    }
+
+    fn write_volatile(
+        &mut self,
+        slice: fuse_backend_rs::file_buf::FileVolatileSlice,
+    ) -> io::Result<usize> {
+        self.trailered.write_volatile(slice)
+    }
+
+    fn read_at_volatile(
+        &mut self,
+        slice: fuse_backend_rs::file_buf::FileVolatileSlice,
+        offset: u64,
+    ) -> io::Result<usize> {
+        self.trailered.read_at_volatile(slice, offset)
+    }
+
+    fn write_at_volatile(
+        &mut self,
+        slice: fuse_backend_rs::file_buf::FileVolatileSlice,
+        offset: u64,
+    ) -> io::Result<usize> {
+        self.trailered.write_at_volatile(slice, offset)
+    }
+}
+
+impl<T: Read + Write + Seek + MetaAccess + FileReadWriteVolatile, C: Creds> Block
+    for BlockStream<T, C>
+{
+}
+
 pub struct BlockOpenOptions<T, C> {
     inner: T,
     creds: C,
@@ -419,7 +462,9 @@ impl<T, C> BlockOpenOptions<T, C> {
     }
 }
 
-impl<T: Read + Write + Seek + 'static, C: Creds + 'static> BlockOpenOptions<T, C> {
+impl<T: Read + Write + Seek + FileReadWriteVolatile + 'static, C: Creds + 'static>
+    BlockOpenOptions<T, C>
+{
     pub fn open(self) -> Result<Box<dyn Block>> {
         let stream = BlockStream::new(self.inner, self.creds)?;
         let block_key = stream.block_key()?;
@@ -560,13 +605,66 @@ impl Fragment {
     }
 }
 
-/// The body of every non-leaf node in a tree contains this data structure.
 #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
+/// 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?
+    pub frags: HashMap<FragmentSerial, FragmentRecord>,
+}
+
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+/// Structure for keeping track of server information in a directory.
+pub struct ServerRecord {
+    /// The most up-to-date address for this server.
+    addr: SocketAddr,
+    /// The public credentials for this server.
+    pub_creds: PublicCreds,
+}
+
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+pub enum DirEntry {
+    Directory(BlockRecord),
+    File(BlockRecord),
+    Server(ServerRecord),
+}
+
+/// This is the body contained in directory blocks.
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
 pub struct Directory {
-    /// The nodes that are attached to the tree at this block.
-    nodes: Vec<Principal>,
     /// This block's descendants.
-    children: HashMap<String, HashMap<FragmentSerial, FragmentRecord>>,
+    entries: BTreeMap<String, DirEntry>,
+}
+
+impl Directory {
+    pub fn new() -> Directory {
+        Directory {
+            entries: BTreeMap::new(),
+        }
+    }
+
+    pub fn add_file(&mut self, name: String, inode: u64) -> Result<&mut BlockRecord> {
+        let entry = match self.entries.entry(name) {
+            btree_map::Entry::Occupied(entry) => {
+                return Err(Error::custom(format!(
+                    "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(Error::custom("DirEntry was not the File variant")),
+        }
+    }
 }
 
 /// Keeps track of which principal is storing a fragment.
@@ -650,7 +748,29 @@ impl Sub<Duration> for Epoch {
 
 /// The serial number of a block fragment.
 #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Hashable)]
-struct FragmentSerial(u32);
+pub struct FragmentSerial(u32);
+
+/// A struct which may contain a closure. When this struct is dropped, if it contains a closure
+/// then that closure is called. This closure can be removed by calling `disarm`.
+pub struct DropTrigger<F: FnOnce()>(Option<F>);
+
+impl<F: FnOnce()> DropTrigger<F> {
+    pub fn new(trigger: F) -> DropTrigger<F> {
+        DropTrigger(Some(trigger))
+    }
+
+    pub fn disarm(&mut self) {
+        self.0.take();
+    }
+}
+
+impl<F: FnOnce()> Drop for DropTrigger<F> {
+    fn drop(&mut self) {
+        if let Some(trigger) = self.0.take() {
+            trigger()
+        }
+    }
+}
 
 #[cfg(test)]
 mod tests {
@@ -695,7 +815,7 @@ mod tests {
             .expect("failed to create TpmCredStore");
         let creds = cred_store.node_creds().expect("failed to get node creds");
         BlockOpenOptions::new()
-            .with_inner(Cursor::new(Vec::<u8>::new()))
+            .with_inner(BtCursor::new(Vec::<u8>::new()))
             .with_creds(creds)
             .with_encrypt(true)
             .open()

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

@@ -1,6 +1,7 @@
 pub use private::SectoredBuf;
 
 mod private {
+    use fuse_backend_rs::file_traits::FileReadWriteVolatile;
     use log::{error, warn};
     use std::io::{self, Read, Seek, SeekFrom, Write};
 
@@ -328,7 +329,39 @@ mod private {
         }
     }
 
-    impl<T: Read + Write + Seek + MetaAccess> Block for SectoredBuf<T> {}
+    impl<T: FileReadWriteVolatile> FileReadWriteVolatile for SectoredBuf<T> {
+        fn read_volatile(
+            &mut self,
+            slice: fuse_backend_rs::file_buf::FileVolatileSlice,
+        ) -> io::Result<usize> {
+            self.inner.read_volatile(slice)
+        }
+
+        fn write_volatile(
+            &mut self,
+            slice: fuse_backend_rs::file_buf::FileVolatileSlice,
+        ) -> io::Result<usize> {
+            self.inner.write_volatile(slice)
+        }
+
+        fn read_at_volatile(
+            &mut self,
+            slice: fuse_backend_rs::file_buf::FileVolatileSlice,
+            offset: u64,
+        ) -> io::Result<usize> {
+            self.inner.read_at_volatile(slice, offset)
+        }
+
+        fn write_at_volatile(
+            &mut self,
+            slice: fuse_backend_rs::file_buf::FileVolatileSlice,
+            offset: u64,
+        ) -> io::Result<usize> {
+            self.inner.write_at_volatile(slice, offset)
+        }
+    }
+
+    impl<T: Read + Write + Seek + MetaAccess + FileReadWriteVolatile> Block for SectoredBuf<T> {}
 }
 
 #[cfg(test)]

+ 115 - 0
crates/btlib/src/test_helpers.rs

@@ -2,6 +2,10 @@
 use super::*;
 use btserde::{Error, Result};
 use crypto::{self, *};
+use fuse_backend_rs::{
+    api::filesystem::{ZeroCopyReader, ZeroCopyWriter},
+    file_buf::FileVolatileSlice,
+};
 use nix::{
     sys::signal::{self, Signal},
     unistd::Pid,
@@ -24,6 +28,7 @@ use tss_esapi::{
     tcti_ldr::{TabrmdConfig, TctiNameConf},
     Context,
 };
+use vm_memory::bytes::Bytes;
 
 pub const PRINCIPAL: [u8; 32] = [
     0x75, 0x28, 0xA9, 0xE0, 0x9D, 0x24, 0xBA, 0xB3, 0x79, 0x56, 0x15, 0x68, 0xFD, 0xA4, 0xE2, 0xA4,
@@ -417,6 +422,29 @@ impl<T: FromVec> BtCursor<T> {
     }
 }
 
+impl<T: FromVec + AsMut<[u8]>> BtCursor<T> {
+    /// Access the data in this cursor as a mutable slice of bytes. The callback which access the
+    /// bytes is expected to return the number of bytes to advance the cursor position by.
+    fn access_advance<F: FnOnce(&mut [u8]) -> u64>(&mut self, limit: usize, accessor: F) -> u64 {
+        let mut cursor = self.cursor.borrow_mut();
+        let pos = cursor.position();
+        let byte_slice = cursor.get_mut().as_mut();
+        let max = byte_slice.len().max(limit);
+        let pos_usize: usize = pos.try_into().unwrap();
+        let byte_slice = &mut byte_slice[pos_usize..max];
+        let advance_by = accessor(byte_slice);
+        cursor.set_position(pos + advance_by);
+        advance_by
+    }
+
+    /// Gives a `accessor` access to a slice containing all of the data in this cursor.
+    fn access_whole<U, F: FnOnce(&mut [u8]) -> U>(&mut self, accessor: F) -> U {
+        let mut cursor = self.cursor.borrow_mut();
+        let byte_slice = cursor.get_mut().as_mut();
+        accessor(byte_slice)
+    }
+}
+
 impl Write for BtCursor<Vec<u8>> {
     fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
         self.cursor.get_mut().write(buf)
@@ -461,6 +489,93 @@ impl<const N: usize> WriteInteg for BtCursor<[u8; N]> {
     }
 }
 
+impl<T: AsMut<[u8]> + FromVec> FileReadWriteVolatile for BtCursor<T> {
+    fn read_volatile(
+        &mut self,
+        slice: fuse_backend_rs::file_buf::FileVolatileSlice,
+    ) -> io::Result<usize> {
+        let read = self.access_advance(usize::MAX, |byte_slice| {
+            let written = slice.as_volatile_slice().write(byte_slice, 0).unwrap();
+            written.try_into().unwrap()
+        });
+        Ok(read.try_into().unwrap())
+    }
+
+    fn write_volatile(
+        &mut self,
+        slice: fuse_backend_rs::file_buf::FileVolatileSlice,
+    ) -> io::Result<usize> {
+        let written = self.access_advance(usize::MAX, |byte_slice| {
+            let read = slice.as_volatile_slice().read(byte_slice, 0).unwrap();
+            read.try_into().unwrap()
+        });
+        Ok(written.try_into().unwrap())
+    }
+
+    fn read_at_volatile(
+        &mut self,
+        slice: fuse_backend_rs::file_buf::FileVolatileSlice,
+        offset: u64,
+    ) -> io::Result<usize> {
+        Ok(self.access_whole(|byte_slice| {
+            let offset = offset.try_into().unwrap();
+            let byte_slice = &mut byte_slice[offset..];
+            slice.as_volatile_slice().write(byte_slice, 0).unwrap()
+        }))
+    }
+
+    fn write_at_volatile(
+        &mut self,
+        slice: fuse_backend_rs::file_buf::FileVolatileSlice,
+        offset: u64,
+    ) -> io::Result<usize> {
+        Ok(self.access_whole(|byte_slice| {
+            let offset = offset.try_into().unwrap();
+            let byte_slice = &mut byte_slice[offset..];
+            slice.as_volatile_slice().read(byte_slice, 0).unwrap()
+        }))
+    }
+}
+
+impl<T: AsMut<[u8]> + FromVec> ZeroCopyReader for BtCursor<T> {
+    fn read_to(
+        &mut self,
+        f: &mut dyn FileReadWriteVolatile,
+        count: usize,
+        off: u64,
+    ) -> io::Result<usize> {
+        let written = self.access_advance(count, |byte_slice| {
+            // Safety: The mut borrow of cursor is guaranteed to be the only one in existence
+            // because this method takes &mut self.
+            let slice = unsafe {
+                FileVolatileSlice::from_raw_ptr(byte_slice.as_mut_ptr(), byte_slice.len())
+            };
+            let written = f.write_at_volatile(slice, off).expect("write failed");
+            let written_u64: u64 = written.try_into().unwrap();
+            written_u64
+        });
+        Ok(written.try_into().unwrap())
+    }
+}
+
+impl<const N: usize> ZeroCopyWriter for BtCursor<[u8; N]> {
+    fn write_from(
+        &mut self,
+        f: &mut dyn FileReadWriteVolatile,
+        count: usize,
+        off: u64,
+    ) -> io::Result<usize> {
+        let read = self.access_advance(count, |byte_slice| {
+            let slice = unsafe {
+                FileVolatileSlice::from_raw_ptr(byte_slice.as_mut_ptr(), byte_slice.len())
+            };
+            let read = f.read_at_volatile(slice, off).expect("read failed");
+            read.try_into().unwrap()
+        });
+        Ok(read.try_into().unwrap())
+    }
+}
+
 #[derive(Debug, PartialEq, Serialize, Deserialize)]
 pub struct SectoredCursor<T: FromVec> {
     cursor: BtCursor<T>,

+ 33 - 0
crates/btlib/src/trailered.rs

@@ -7,6 +7,7 @@ mod private {
     };
 
     use btserde::{read_from, write_to};
+    use fuse_backend_rs::file_traits::FileReadWriteVolatile;
     use serde::{de::DeserializeOwned, Serialize};
 
     use crate::{BoxInIoErr, Decompose, MetaAccess, Result, WriteInteg};
@@ -161,6 +162,38 @@ mod private {
             self.inner.set_path(path)
         }
     }
+
+    impl<T: FileReadWriteVolatile, D> FileReadWriteVolatile for Trailered<T, D> {
+        fn read_volatile(
+            &mut self,
+            slice: fuse_backend_rs::file_buf::FileVolatileSlice,
+        ) -> io::Result<usize> {
+            self.inner.read_volatile(slice)
+        }
+
+        fn write_volatile(
+            &mut self,
+            slice: fuse_backend_rs::file_buf::FileVolatileSlice,
+        ) -> io::Result<usize> {
+            self.inner.write_volatile(slice)
+        }
+
+        fn read_at_volatile(
+            &mut self,
+            slice: fuse_backend_rs::file_buf::FileVolatileSlice,
+            offset: u64,
+        ) -> io::Result<usize> {
+            self.inner.read_at_volatile(slice, offset)
+        }
+
+        fn write_at_volatile(
+            &mut self,
+            slice: fuse_backend_rs::file_buf::FileVolatileSlice,
+            offset: u64,
+        ) -> io::Result<usize> {
+            self.inner.write_at_volatile(slice, offset)
+        }
+    }
 }
 
 #[cfg(test)]

+ 10 - 1
crates/btserde/src/error.rs

@@ -1,5 +1,8 @@
 use serde::{de, ser};
-use std::fmt::{self, Display};
+use std::{
+    fmt::{self, Display},
+    io,
+};
 
 pub type Result<T> = std::result::Result<T, Error>;
 
@@ -64,6 +67,12 @@ impl de::Error for Error {
     }
 }
 
+impl From<Error> for io::Error {
+    fn from(err: Error) -> Self {
+        io::Error::new(io::ErrorKind::Other, err)
+    }
+}
+
 pub trait MapError<T> {
     /// Returns self if no error occurred, otherwise converts the error to a serde_blocktree error.
     fn map_error(self) -> Result<T>;

+ 2 - 2
doc/Book/example_bt.gv

@@ -11,9 +11,9 @@ digraph {
     avatar [shape = box, label = ""];
     none -> root [label = "<root>"]
     root -> phone [label = "phone"];
-    phone -> viewer [label = "<editor>"];
+    phone -> viewer [label = "<editor1>"];
     root -> laptop [label = "laptop"];
-    laptop -> editor [label = "<editor>"];
+    laptop -> editor [label = "<editor2>"];
     root -> docs [label = "docs"];
     docs -> resignation [label = "resignation.docx"];
     docs -> contract [label = "contract.docx"];

+ 38 - 38
doc/Book/example_bt.svg

@@ -1,13 +1,13 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
  "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<!-- Generated by graphviz version 5.0.1 (0)
+<!-- Generated by graphviz version 6.0.2 (0)
  -->
 <!-- Pages: 1 -->
-<svg width="355pt" height="305pt"
- viewBox="0.00 0.00 355.00 305.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<svg width="361pt" height="305pt"
+ viewBox="0.00 0.00 361.00 305.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
 <g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 301)">
-<polygon fill="white" stroke="none" points="-4,4 -4,-301 351,-301 351,4 -4,4"/>
+<polygon fill="white" stroke="none" points="-4,4 -4,-301 357,-301 357,4 -4,4"/>
 <!-- none -->
 <g id="node1" class="node">
 <title>none</title>
@@ -15,14 +15,14 @@
 <!-- root -->
 <g id="node2" class="node">
 <title>root</title>
-<polygon fill="none" stroke="black" points="162,-210 159,-214 138,-214 135,-210 108,-210 108,-174 162,-174 162,-210"/>
+<polygon fill="none" stroke="black" points="165,-210 162,-214 141,-214 138,-210 111,-210 111,-174 165,-174 165,-210"/>
 </g>
 <!-- none&#45;&gt;root -->
 <g id="edge1" class="edge">
 <title>none&#45;&gt;root</title>
-<path fill="none" stroke="black" d="M135,-260.8C135,-249.16 135,-233.55 135,-220.24"/>
-<polygon fill="black" stroke="black" points="138.5,-220.18 135,-210.18 131.5,-220.18 138.5,-220.18"/>
-<text text-anchor="middle" x="154.5" y="-231.8" font-family="Times,serif" font-size="14.00">&lt;root&gt;</text>
+<path fill="none" stroke="black" d="M138,-260.8C138,-249.16 138,-233.55 138,-220.24"/>
+<polygon fill="black" stroke="black" points="141.5,-220.18 138,-210.18 134.5,-220.18 141.5,-220.18"/>
+<text text-anchor="middle" x="157.5" y="-231.8" font-family="Times,serif" font-size="14.00">&lt;root&gt;</text>
 </g>
 <!-- phone -->
 <g id="node3" class="node">
@@ -32,57 +32,57 @@
 <!-- root&#45;&gt;phone -->
 <g id="edge2" class="edge">
 <title>root&#45;&gt;phone</title>
-<path fill="none" stroke="black" d="M107.93,-180.25C94.78,-174.27 79.23,-166 67,-156 55.42,-146.53 44.96,-133.2 37.65,-122.68"/>
-<polygon fill="black" stroke="black" points="40.54,-120.71 32.07,-114.33 34.71,-124.59 40.54,-120.71"/>
-<text text-anchor="middle" x="84" y="-144.8" font-family="Times,serif" font-size="14.00">phone</text>
+<path fill="none" stroke="black" d="M110.75,-179.91C97.71,-173.9 82.3,-165.7 70,-156 57.9,-146.46 46.64,-133.13 38.68,-122.63"/>
+<polygon fill="black" stroke="black" points="41.32,-120.32 32.59,-114.31 35.67,-124.44 41.32,-120.32"/>
+<text text-anchor="middle" x="87" y="-144.8" font-family="Times,serif" font-size="14.00">phone</text>
 </g>
 <!-- laptop -->
 <g id="node5" class="node">
 <title>laptop</title>
-<polygon fill="none" stroke="black" points="99,-87 126,-114 72,-114 99,-87"/>
+<polygon fill="none" stroke="black" points="102,-87 129,-114 75,-114 102,-87"/>
 </g>
 <!-- root&#45;&gt;laptop -->
 <g id="edge4" class="edge">
 <title>root&#45;&gt;laptop</title>
-<path fill="none" stroke="black" d="M121.46,-173.94C117.63,-168.46 113.75,-162.19 111,-156 106.52,-145.92 103.57,-133.91 101.71,-124.19"/>
-<polygon fill="black" stroke="black" points="105.12,-123.37 100.01,-114.09 98.22,-124.53 105.12,-123.37"/>
-<text text-anchor="middle" x="128" y="-144.8" font-family="Times,serif" font-size="14.00">laptop</text>
+<path fill="none" stroke="black" d="M124.46,-173.94C120.63,-168.46 116.75,-162.19 114,-156 109.52,-145.92 106.57,-133.91 104.71,-124.19"/>
+<polygon fill="black" stroke="black" points="108.12,-123.37 103.01,-114.09 101.22,-124.53 108.12,-123.37"/>
+<text text-anchor="middle" x="131" y="-144.8" font-family="Times,serif" font-size="14.00">laptop</text>
 </g>
 <!-- docs -->
 <g id="node7" class="node">
 <title>docs</title>
-<polygon fill="none" stroke="black" points="198,-123 195,-127 174,-127 171,-123 144,-123 144,-87 198,-87 198,-123"/>
+<polygon fill="none" stroke="black" points="202,-123 199,-127 178,-127 175,-123 148,-123 148,-87 202,-87 202,-123"/>
 </g>
 <!-- root&#45;&gt;docs -->
 <g id="edge6" class="edge">
 <title>root&#45;&gt;docs</title>
-<path fill="none" stroke="black" d="M142.29,-173.8C147.31,-161.93 154.09,-145.93 159.8,-132.45"/>
-<polygon fill="black" stroke="black" points="163.05,-133.75 163.73,-123.18 156.6,-131.02 163.05,-133.75"/>
-<text text-anchor="middle" x="167.5" y="-144.8" font-family="Times,serif" font-size="14.00">docs</text>
+<path fill="none" stroke="black" d="M145.49,-173.8C150.65,-161.93 157.62,-145.93 163.49,-132.45"/>
+<polygon fill="black" stroke="black" points="166.74,-133.74 167.52,-123.18 160.32,-130.95 166.74,-133.74"/>
+<text text-anchor="middle" x="170.5" y="-144.8" font-family="Times,serif" font-size="14.00">docs</text>
 </g>
 <!-- avatar -->
 <g id="node10" class="node">
 <title>avatar</title>
-<polygon fill="none" stroke="black" points="270,-123 216,-123 216,-87 270,-87 270,-123"/>
+<polygon fill="none" stroke="black" points="274,-123 220,-123 220,-87 274,-87 274,-123"/>
 </g>
 <!-- root&#45;&gt;avatar -->
 <g id="edge9" class="edge">
 <title>root&#45;&gt;avatar</title>
-<path fill="none" stroke="black" d="M159.72,-173.94C167.58,-168.36 176.25,-162.04 184,-156 194.7,-147.67 206.14,-138.13 216.07,-129.63"/>
-<polygon fill="black" stroke="black" points="218.38,-132.26 223.67,-123.08 213.81,-126.96 218.38,-132.26"/>
-<text text-anchor="middle" x="229.5" y="-144.8" font-family="Times,serif" font-size="14.00">avatar.usd</text>
+<path fill="none" stroke="black" d="M162.69,-173.91C170.55,-168.32 179.22,-162.01 187,-156 197.84,-147.63 209.47,-138.08 219.57,-129.59"/>
+<polygon fill="black" stroke="black" points="221.93,-132.18 227.3,-123.04 217.41,-126.83 221.93,-132.18"/>
+<text text-anchor="middle" x="232.5" y="-144.8" font-family="Times,serif" font-size="14.00">avatar.usd</text>
 </g>
 <!-- viewer -->
 <g id="node6" class="node">
 <title>viewer</title>
-<ellipse fill="none" stroke="black" cx="27" cy="-18" rx="18" ry="18"/>
+<ellipse fill="none" stroke="black" cx="21" cy="-18" rx="18" ry="18"/>
 </g>
 <!-- phone&#45;&gt;viewer -->
 <g id="edge3" class="edge">
 <title>phone&#45;&gt;viewer</title>
-<path fill="none" stroke="black" d="M27,-86.8C27,-75.16 27,-59.55 27,-46.24"/>
-<polygon fill="black" stroke="black" points="30.5,-46.18 27,-36.18 23.5,-46.18 30.5,-46.18"/>
-<text text-anchor="middle" x="51.5" y="-57.8" font-family="Times,serif" font-size="14.00">&lt;editor&gt;</text>
+<path fill="none" stroke="black" d="M25.87,-88.01C25.05,-76.32 23.91,-60.18 22.94,-46.45"/>
+<polygon fill="black" stroke="black" points="26.4,-45.81 22.21,-36.08 19.42,-46.3 26.4,-45.81"/>
+<text text-anchor="middle" x="52.5" y="-57.8" font-family="Times,serif" font-size="14.00">&lt;editor1&gt;</text>
 </g>
 <!-- editor -->
 <g id="node4" class="node">
@@ -92,33 +92,33 @@
 <!-- laptop&#45;&gt;editor -->
 <g id="edge5" class="edge">
 <title>laptop&#45;&gt;editor</title>
-<path fill="none" stroke="black" d="M99,-86.8C99,-75.16 99,-59.55 99,-46.24"/>
-<polygon fill="black" stroke="black" points="102.5,-46.18 99,-36.18 95.5,-46.18 102.5,-46.18"/>
-<text text-anchor="middle" x="123.5" y="-57.8" font-family="Times,serif" font-size="14.00">&lt;editor&gt;</text>
+<path fill="none" stroke="black" d="M101.41,-87.21C100.99,-75.52 100.44,-59.69 99.96,-46.22"/>
+<polygon fill="black" stroke="black" points="103.45,-45.92 99.6,-36.05 96.46,-46.17 103.45,-45.92"/>
+<text text-anchor="middle" x="127.5" y="-57.8" font-family="Times,serif" font-size="14.00">&lt;editor2&gt;</text>
 </g>
 <!-- resignation -->
 <g id="node8" class="node">
 <title>resignation</title>
-<polygon fill="none" stroke="black" points="198,-36 144,-36 144,0 198,0 198,-36"/>
+<polygon fill="none" stroke="black" points="204,-36 150,-36 150,0 204,0 204,-36"/>
 </g>
 <!-- docs&#45;&gt;resignation -->
 <g id="edge7" class="edge">
 <title>docs&#45;&gt;resignation</title>
-<path fill="none" stroke="black" d="M171,-86.8C171,-75.16 171,-59.55 171,-46.24"/>
-<polygon fill="black" stroke="black" points="174.5,-46.18 171,-36.18 167.5,-46.18 174.5,-46.18"/>
-<text text-anchor="middle" x="216.5" y="-57.8" font-family="Times,serif" font-size="14.00">resignation.docx</text>
+<path fill="none" stroke="black" d="M175.4,-86.8C175.68,-75.16 176.05,-59.55 176.36,-46.24"/>
+<polygon fill="black" stroke="black" points="179.86,-46.26 176.6,-36.18 172.86,-46.09 179.86,-46.26"/>
+<text text-anchor="middle" x="221.5" y="-57.8" font-family="Times,serif" font-size="14.00">resignation.docx</text>
 </g>
 <!-- contract -->
 <g id="node9" class="node">
 <title>contract</title>
-<polygon fill="none" stroke="black" points="312,-36 258,-36 258,0 312,0 312,-36"/>
+<polygon fill="none" stroke="black" points="318,-36 264,-36 264,0 318,0 318,-36"/>
 </g>
 <!-- docs&#45;&gt;contract -->
 <g id="edge8" class="edge">
 <title>docs&#45;&gt;contract</title>
-<path fill="none" stroke="black" d="M198.36,-90.63C201.25,-89.35 204.17,-88.12 207,-87 231.32,-77.4 243.58,-86.5 263,-69 269.78,-62.88 274.58,-54.29 277.92,-45.95"/>
-<polygon fill="black" stroke="black" points="281.25,-47.04 281.2,-36.45 274.63,-44.75 281.25,-47.04"/>
-<text text-anchor="middle" x="310" y="-57.8" font-family="Times,serif" font-size="14.00">contract.docx</text>
+<path fill="none" stroke="black" d="M202.34,-90.6C205.24,-89.33 208.17,-88.1 211,-87 236.15,-77.21 248.86,-86.97 269,-69 275.81,-62.92 280.62,-54.34 283.95,-45.99"/>
+<polygon fill="black" stroke="black" points="287.28,-47.07 287.23,-36.48 280.67,-44.79 287.28,-47.07"/>
+<text text-anchor="middle" x="316" y="-57.8" font-family="Times,serif" font-size="14.00">contract.docx</text>
 </g>
 </g>
 </svg>

+ 47 - 0
doc/project_plan.md

@@ -0,0 +1,47 @@
+## Blocktree: Be free.
+
+## Mission
+To create software which asserts user sovereignty over their devices.
+
+## Priorities
+0. Crypo: Create a cryptographic library for managing credentials securely using a TPM, and providing
+confidentiality and integrity protections.
+
+1. FS: Create a POSIX compatible file system interface for interacting with blocks.
+
+2. Msg: Build a message passing interface which can efficiently deliver messages to processes on the same
+machine and transparently forward them over the network.
+
+3. Raft: Integrate the Raft consensus algorithm to allow multiple servers to serve the same data.
+
+4. Run: Create a runtime for processes, both native and Wasm.
+
+## Objectives
+The initial development of priority 0 is complete, but there are several enhancements in the
+backlog.
+
+Priority 1 Objectives:
+0. Create a block event log (journal) to synchronize local file system operations.
+1. Document the file system interface and specify its concurrency semantics.
+2. Build a storage daemon which handles all RW messages to a server.
+3. Implement a FUSE server to allow the kernel to mount a BT.
+
+Priority 2 Objectives:
+0. Specify the routing semantics of messages.
+1. Build a network daemon which is responsible for routing all messages off of a machine.
+2. Implement local message passing using an efficient IPC mechanism, preferably one which is zero
+copy and lock free.
+
+Priority 3 Objectives:
+0. Specify the semantics of data location and the messages exchanged in the Raft implementation.
+1. Use the Raft crate to synchronize the block event log over multiple servers. Use the message
+passing interface for all communication.
+
+Priority 4 Objectives:
+0. Specify the package format and the file system interface used to control the scheduling of
+processes.
+1. Implement an init daemon to schedule processes according to server configuration. This
+process will be responsible for starting and sandboxing both native and Wasm processes. It will
+make placement decisions about which native host will run Wasm processes and the operating system
+services which are accessible to them.
+2. Implement a daemon to efficiently host many Wasm processes.