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

Revamped error handling in the `btlib` crate.

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

+ 70 - 0
Cargo.lock

@@ -2,6 +2,21 @@
 # It is not intended for manual editing.
 version = 3
 
+[[package]]
+name = "addr2line"
+version = "0.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
 [[package]]
 name = "aho-corasick"
 version = "0.7.19"
@@ -50,6 +65,15 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "anyhow"
+version = "1.0.66"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6"
+dependencies = [
+ "backtrace",
+]
+
 [[package]]
 name = "arc-swap"
 version = "1.5.1"
@@ -73,6 +97,21 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
 
+[[package]]
+name = "backtrace"
+version = "0.3.66"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7"
+dependencies = [
+ "addr2line",
+ "cc",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+]
+
 [[package]]
 name = "base64"
 version = "0.13.0"
@@ -162,6 +201,7 @@ dependencies = [
 name = "btlib"
 version = "0.1.0"
 dependencies = [
+ "anyhow",
  "base64-url",
  "brotli",
  "btserde",
@@ -592,6 +632,12 @@ dependencies = [
  "vmm-sys-util",
 ]
 
+[[package]]
+name = "gimli"
+version = "0.26.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d"
+
 [[package]]
 name = "glob"
 version = "0.3.0"
@@ -812,6 +858,15 @@ version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
 
+[[package]]
+name = "miniz_oxide"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34"
+dependencies = [
+ "adler",
+]
+
 [[package]]
 name = "mio"
 version = "0.8.4"
@@ -900,6 +955,15 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "object"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53"
+dependencies = [
+ "memchr",
+]
+
 [[package]]
 name = "oid"
 version = "0.2.1"
@@ -1187,6 +1251,12 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "rustc-demangle"
+version = "0.1.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
+
 [[package]]
 name = "rustc-hash"
 version = "1.1.0"

+ 1 - 0
crates/btlib/Cargo.toml

@@ -29,6 +29,7 @@ fuse-backend-rs = "0.9.6"
 libc = "0.2.137"
 env_logger = { version = "0.9.0" }
 chrono = "0.4.23"
+anyhow = { version = "1.0.66", features = ["std", "backtrace"] }
 
 [dev-dependencies]
 tempdir = { version = "0.3.7" }

+ 62 - 28
crates/btlib/src/blocktree.rs

@@ -14,6 +14,7 @@ mod private {
     use std::{
         collections::hash_map::{self, HashMap},
         ffi::CStr,
+        fmt::{Display, Formatter},
         fs::File,
         io::{self, SeekFrom, Write},
         path::{Path, PathBuf},
@@ -25,13 +26,41 @@ mod private {
     };
 
     use crate::{
-        crypto::Creds, Block, BlockMeta, BlockOpenOptions, BlockPath, BlockRecord, BoxInIoErr,
-        DirEntry, DirEntryKind, Directory, Epoch, Error, Result, ToStringInIoErr,
+        bterr,
+        crypto::Creds,
+        error::{DisplayErr, IoErr},
+        Block, BlockError, BlockMeta, BlockOpenOptions, BlockPath, BlockRecord, BoxInIoErr,
+        DirEntry, DirEntryKind, Directory, Epoch, Result,
     };
 
     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,
@@ -69,7 +98,7 @@ mod private {
             if (value & libc::S_IFREG) != 0 {
                 return Ok(FileType::Reg);
             }
-            Err(Error::custom(format!("unknown file type: 0o{value:0o}")))
+            Err(bterr!("unknown file type: 0o{value:0o}"))
         }
 
         fn dir_entry_kind(self) -> DirEntryKind {
@@ -241,7 +270,7 @@ mod private {
         fn convert_to_dir(self) -> io::Result<HandleValue> {
             let lock = self.take_block();
             let dir = {
-                let mut guard = lock.write().err_to_string()?;
+                let mut guard = lock.write().display_err()?;
                 guard.seek(SeekFrom::Start(0))?;
                 read_from(&mut *guard)?
             };
@@ -267,14 +296,14 @@ mod private {
                 Self::File { block, .. } => block,
                 Self::Directory { block, .. } => block,
             };
-            lock.get_mut().err_to_string()
+            lock.get_mut().display_err().io_err()
         }
 
         fn access_block<T, F: FnOnce(&Box<dyn Block>) -> io::Result<T>>(
             &self,
             cb: F,
         ) -> io::Result<T> {
-            let guard = self.block().read().err_to_string()?;
+            let guard = self.block().read().display_err()?;
             cb(&guard)
         }
 
@@ -282,7 +311,7 @@ mod private {
             &self,
             cb: F,
         ) -> io::Result<T> {
-            let mut guard = self.block().write().err_to_string()?;
+            let mut guard = self.block().write().display_err()?;
             cb(&mut guard)
         }
 
@@ -368,7 +397,7 @@ mod private {
             let handle = self
                 .unclaimed_handles
                 .last()
-                .ok_or_else(|| Error::custom("no handles available"))?;
+                .ok_or_else(|| bterr!("no handles available"))?;
             let value = self.value_mut(*handle)?;
             let block = value.block_mut()?;
             cb(block)
@@ -452,7 +481,7 @@ mod private {
         ) -> Result<Blocktree<C, A>> {
             let root_block_path = creds
                 .writecap()
-                .ok_or(Error::MissingWritecap)?
+                .ok_or(BlockError::MissingWritecap)?
                 .root_block_path();
 
             // Initialize the superblock.
@@ -504,7 +533,7 @@ mod private {
         pub fn new_existing(btdir: PathBuf, creds: C, authorizer: A) -> Result<Blocktree<C, A>> {
             let root_block_path = creds
                 .writecap()
-                .ok_or(Error::MissingWritecap)?
+                .ok_or(BlockError::MissingWritecap)?
                 .root_block_path();
             let mut sb_block = Self::open_block(
                 &btdir,
@@ -564,13 +593,13 @@ mod private {
             inode: Inode,
             creds: C,
             block_path: BlockPath,
-        ) -> Result<Box<dyn Block>> {
+        ) -> io::Result<Box<dyn Block>> {
             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()),
+                    _ => return Err(err),
                 }
             }
             let file = std::fs::OpenOptions::new()
@@ -581,7 +610,11 @@ mod private {
             Self::open_block_file(file, creds, block_path)
         }
 
-        fn open_block_file(file: File, creds: C, block_path: BlockPath) -> Result<Box<dyn Block>> {
+        fn open_block_file(
+            file: File,
+            creds: C,
+            block_path: BlockPath,
+        ) -> io::Result<Box<dyn Block>> {
             BlockOpenOptions::new()
                 .with_creds(creds)
                 .with_compress(false)
@@ -589,6 +622,7 @@ mod private {
                 .with_inner(file)
                 .with_block_path(block_path)
                 .open()
+                .io_err()
         }
 
         /// Returns the [Err] variant containing the [io::Error] corresponding to [libc::ENOSYS].
@@ -603,7 +637,7 @@ mod private {
             inode: Inode,
             cb: F,
         ) -> io::Result<T> {
-            let mut inodes = self.inodes.write().err_to_string()?;
+            let mut inodes = self.inodes.write().display_err()?;
             let entry = inodes.entry(inode);
             cb(entry)
         }
@@ -613,12 +647,12 @@ mod private {
             inode: Inode,
             cb: F,
         ) -> io::Result<T> {
-            let inodes = self.inodes.read().err_to_string()?;
+            let inodes = self.inodes.read().display_err()?;
             let guard = inodes
                 .get(&inode)
-                .ok_or_else(|| Error::NotOpen(inode))?
+                .ok_or_else(|| bterr!(Error::NotOpen(inode)))?
                 .read()
-                .err_to_string()?;
+                .display_err()?;
             cb(&guard)
         }
 
@@ -627,12 +661,12 @@ mod private {
             inode: Inode,
             cb: F,
         ) -> io::Result<T> {
-            let inodes = self.inodes.read().err_to_string()?;
+            let inodes = self.inodes.read().display_err()?;
             let mut guard = inodes
                 .get(&inode)
-                .ok_or_else(|| Error::NotOpen(inode))?
+                .ok_or_else(|| bterr!(Error::NotOpen(inode)))?
                 .write()
-                .err_to_string()?;
+                .display_err()?;
             cb(&mut guard)
         }
 
@@ -655,7 +689,7 @@ mod private {
                     .handle_values
                     .values()
                     .next()
-                    .ok_or_else(|| Error::NotOpen(inode))?;
+                    .ok_or_else(|| bterr!(Error::NotOpen(inode)))?;
                 // Because we're using any of the meta data structs we need to ensure that any
                 // modification of meta data is performed on all open blocks.
                 handle_value.access_block(|block| cb(block.meta()))
@@ -675,7 +709,7 @@ mod private {
                             .handle_values
                             .values()
                             .next()
-                            .ok_or_else(|| Error::NotOpen(inode))?
+                            .ok_or_else(|| bterr!(Error::NotOpen(inode)))?
                             .access_block(|block| Ok(block.meta_body().path.clone()))?;
                         let block =
                             Self::open_block(&self.path, inode, self.creds.clone(), block_path)?;
@@ -694,7 +728,7 @@ mod private {
             self.access_value_mut(inode, |value| {
                 let handle = value
                     .take_handle()
-                    .map_err(|_| Error::NoHandlesAvailable(inode))?;
+                    .map_err(|_| bterr!(Error::NoHandlesAvailable(inode)))?;
                 let block = value.block_mut(handle)?;
                 let result = cb(handle, block);
                 if result.is_err() {
@@ -720,7 +754,7 @@ mod private {
                     result
                 }
                 InodeTableEntry::Occupied(mut entry) => {
-                    let value = entry.get_mut().get_mut().err_to_string()?;
+                    let value = entry.get_mut().get_mut().display_err()?;
                     if value.unclaimed_handles.is_empty() {
                         let block =
                             Self::open_block(&self.path, inode, self.creds.clone(), block_path)?;
@@ -744,7 +778,7 @@ mod private {
         }
 
         fn inode_forget(&self, inode: Inode, count: u64) -> io::Result<()> {
-            let mut inodes = self.inodes.write().err_to_string()?;
+            let mut inodes = self.inodes.write().display_err()?;
             let lookup_count = {
                 let inode_lock = match inodes.get_mut(&inode) {
                     Some(inode_lock) => inode_lock,
@@ -753,7 +787,7 @@ mod private {
                         return Ok(());
                     }
                 };
-                let mut value = inode_lock.write().err_to_string()?;
+                let mut value = inode_lock.write().display_err()?;
                 value.decr_lookup_count(count)
             };
             if 0 == lookup_count {
@@ -761,7 +795,7 @@ mod private {
                     .remove(&inode)
                     .unwrap()
                     .into_inner()
-                    .err_to_string()?
+                    .display_err()?
                     .delete;
                 if delete {
                     let path = Self::block_path(&self.path, inode);
@@ -1137,7 +1171,7 @@ mod private {
             self.access_value(inode, |value| {
                 let dir = value
                     .value(handle)
-                    .map_err(|_| Error::InvalidHandle { handle, inode })?
+                    .map_err(|_| bterr!(Error::InvalidHandle { handle, inode }))?
                     .directory()?;
                 let mut index: u64 = 0;
                 for (name, entry) in dir.entries() {

+ 36 - 25
crates/btlib/src/crypto/merkle_stream.rs

@@ -5,6 +5,7 @@ pub use private::{
 
 mod private {
     use crate::{
+        bterr,
         crypto::{Error, HashKind, Result},
         trailered::Trailered,
         Block, BlockMeta, BoxInIoErr, Decompose, MetaAccess, Sectored, TryCompose, WriteInteg,
@@ -149,7 +150,7 @@ mod private {
             let left = left.and_then(|e| e.as_slice());
             let right = right.and_then(|e| e.as_slice());
             Self::combine_hash_data(self.mut_or_init(), prefix, left, right, || {
-                Err(Error::custom(
+                Err(bterr!(
                     "at least one argument to combine needs to supply data",
                 ))
             })
@@ -159,7 +160,7 @@ mod private {
             if self.as_slice() == hash_data {
                 Ok(())
             } else {
-                Err(Error::HashCmpFailure)
+                Err(bterr!(Error::HashCmpFailure))
             }
         }
 
@@ -180,21 +181,23 @@ mod private {
         ) -> Result<()> {
             let slice = match self.as_slice() {
                 Some(slice) => slice,
-                None => return Err(Error::HashCmpFailure),
+                None => return Err(bterr!(Error::HashCmpFailure)),
             };
             let buf = {
                 let mut buf = [0u8; Self::KIND.len()];
                 let left = left.and_then(|e| e.as_slice());
                 let right = right.and_then(|e| e.as_slice());
                 Self::combine_hash_data(&mut buf, prefix, left, right, || {
-                    Err(Error::custom("logic error encountered"))
+                    Err(bterr!(
+                        "logic error encountered, left or right should have been Some"
+                    ))
                 })?;
                 buf
             };
             if slice == buf {
                 Ok(())
             } else {
-                Err(Error::HashCmpFailure)
+                Err(bterr!(Error::HashCmpFailure))
             }
         }
 
@@ -202,7 +205,7 @@ mod private {
             self.0
                 .as_ref()
                 .map(|arr| arr.as_slice())
-                .ok_or_else(|| Error::custom("this merkle node is empty"))
+                .ok_or_else(|| bterr!("this merkle node is empty"))
         }
     }
 
@@ -323,9 +326,11 @@ mod private {
         /// Returns a reference to the hash stored in the given node, or `Error::IndexOutOfBounds` if
         /// the given index doesn't exist.
         fn hash_at(&self, index: BinTreeIndex) -> Result<&T> {
-            self.nodes.get(index.0).ok_or(Error::IndexOutOfBounds {
-                index: index.0,
-                limit: self.nodes.len(),
+            self.nodes.get(index.0).ok_or_else(|| {
+                bterr!(Error::IndexOutOfBounds {
+                    index: index.0,
+                    limit: self.nodes.len(),
+                })
             })
         }
 
@@ -335,10 +340,10 @@ mod private {
             let sector_index = offset / self.sector_sz;
             let index_limit = exp2(gens);
             if sector_index >= index_limit {
-                return Err(Error::InvalidOffset {
+                return Err(bterr!(Error::InvalidOffset {
                     actual: offset,
                     limit: index_limit * self.sector_sz,
-                });
+                }));
             }
             Ok(BinTreeIndex(exp2(gens) - 1 + sector_index))
         }
@@ -376,9 +381,11 @@ mod private {
             let left = back.get(left.0 - split);
             let right = back.get(right.0 - split);
             dest.combine(Self::interior_prefix(), left, right)
-                .map_err(|_| Error::IndexOutOfBounds {
-                    index: index.0,
-                    limit: Self::len(self.generations() - 1),
+                .map_err(|_| {
+                    bterr!(Error::IndexOutOfBounds {
+                        index: index.0,
+                        limit: Self::len(self.generations() - 1),
+                    })
                 })
         }
     }
@@ -391,14 +398,19 @@ mod private {
                     self.root_verified = true;
                     Ok(())
                 }
-                Err(Error::IndexOutOfBounds { .. }) => {
-                    if hash_data.is_none() {
-                        Ok(())
-                    } else {
-                        Err(Error::HashCmpFailure)
+                Err(err) => {
+                    let err = err.downcast::<Error>()?;
+                    match err {
+                        Error::IndexOutOfBounds { .. } => {
+                            if hash_data.is_none() {
+                                Ok(())
+                            } else {
+                                Err(bterr!(Error::HashCmpFailure))
+                            }
+                        }
+                        _ => Err(bterr!(err)),
                     }
                 }
-                Err(err) => Err(err),
             }
         }
 
@@ -455,7 +467,7 @@ mod private {
         /// been modified.
         fn verify(&self, offset: usize, data: &[u8]) -> Result<()> {
             if !self.root_verified {
-                return Err(Error::RootHashNotVerified);
+                return Err(bterr!(Error::RootHashNotVerified));
             }
             self.assert_sector_sz(data.len())?;
             let start = self.offset_to_index(offset)?;
@@ -474,7 +486,7 @@ mod private {
             self.nodes
                 .first()
                 .map(|node| node.try_as_slice())
-                .ok_or_else(|| Error::custom("the tree is empty"))?
+                .ok_or_else(|| bterr!("the Merkle tree is empty"))?
         }
     }
 
@@ -576,9 +588,7 @@ mod private {
         pub fn with_tree(inner: T, tree: VariantMerkleTree) -> Result<MerkleStream<T>> {
             let (trailered, trailer) = Trailered::new(inner)?;
             if trailer.is_some() {
-                return Err(Error::custom(
-                    "stream already contained a serialized merkle tree",
-                ));
+                return Err(bterr!("stream already contained a serialized merkle tree",));
             }
             Ok(MerkleStream {
                 trailered,
@@ -596,6 +606,7 @@ mod private {
 
     impl<T: Read + Seek> TryCompose<T, MerkleStream<T>> for MerkleStream<()> {
         type Error = crate::Error;
+
         fn try_compose(self, inner: T) -> std::result::Result<MerkleStream<T>, Self::Error> {
             let (trailered, tree) = Trailered::new(inner)?;
             Ok(MerkleStream {

+ 81 - 105
crates/btlib/src/crypto/mod.rs

@@ -8,8 +8,8 @@ mod sign_stream;
 pub use sign_stream::SignStream;
 
 use crate::{
-    fmt, io, BigArray, BlockMeta, BlockPath, Deserialize, Epoch, Formatter, Hashable, Principal,
-    Principaled, Serialize, Writecap, WritecapBody,
+    btensure, bterr, fmt, io, BigArray, BlockMeta, BlockPath, Deserialize, Epoch, Formatter,
+    Hashable, Principal, Principaled, Result, Serialize, Writecap, WritecapBody,
 };
 
 use btserde::{self, from_vec, to_vec, write_to};
@@ -34,7 +34,6 @@ use std::{
     fmt::Display,
     io::{Read, Write},
     marker::PhantomData,
-    num::TryFromIntError,
 };
 use strum_macros::{Display, EnumDiscriminants, FromRepr};
 use zeroize::ZeroizeOnDrop;
@@ -80,27 +79,37 @@ pub enum Error {
     BlockNotEncrypted,
     InvalidHashFormat,
     InvalidSignature,
-    IncorrectSize { expected: usize, actual: usize },
-    IndexOutOfBounds { index: usize, limit: usize },
-    IndivisibleSize { divisor: usize, actual: usize },
-    InvalidOffset { actual: usize, limit: usize },
+    IncorrectSize {
+        expected: usize,
+        actual: usize,
+    },
+    IndexOutOfBounds {
+        index: usize,
+        limit: usize,
+    },
+    IndivisibleSize {
+        divisor: usize,
+        actual: usize,
+    },
+    InvalidOffset {
+        actual: usize,
+        limit: usize,
+    },
     HashCmpFailure,
     RootHashNotVerified,
     SignatureMismatch(Box<SignatureMismatch>),
-    WritecapAuthzErr(WritecapAuthzErr),
-    Serde(btserde::Error),
-    Io(std::io::Error),
-    Custom(Box<dyn std::fmt::Debug + Send + Sync>),
+    /// This variant is used to convey errors that originated in an underlying library.
+    Library(Box<dyn ::std::error::Error + Send + Sync + 'static>),
 }
 
 impl Error {
-    pub(crate) fn custom<E: std::fmt::Debug + Send + Sync + 'static>(err: E) -> Error {
-        Error::Custom(Box::new(err))
-    }
-
     fn signature_mismatch(expected: Principal, actual: Principal) -> Error {
         Error::SignatureMismatch(Box::new(SignatureMismatch { expected, actual }))
     }
+
+    fn library<E: std::error::Error + Send + Sync + 'static>(err: E) -> Error {
+        Error::Library(Box::new(err))
+    }
 }
 
 impl Display for Error {
@@ -141,60 +150,19 @@ impl Display for Error {
                     "expected a signature from {expected} but found one from {actual}"
                 )
             }
-            Error::WritecapAuthzErr(err) => err.fmt(f),
-            Error::Serde(err) => err.fmt(f),
-            Error::Io(err) => err.fmt(f),
-            Error::Custom(cus) => cus.fmt(f),
+            Error::Library(err) => err.fmt(f),
         }
     }
 }
 
 impl std::error::Error for Error {}
 
-impl From<WritecapAuthzErr> for Error {
-    fn from(err: WritecapAuthzErr) -> Self {
-        Error::WritecapAuthzErr(err)
-    }
-}
-
 impl From<ErrorStack> for Error {
     fn from(error: ErrorStack) -> Error {
-        Error::custom(error)
-    }
-}
-
-impl From<btserde::Error> for Error {
-    fn from(error: btserde::Error) -> Error {
-        Error::Serde(error)
-    }
-}
-
-impl From<std::io::Error> for Error {
-    fn from(error: std::io::Error) -> Error {
-        Error::Io(error)
-    }
-}
-
-impl From<TryFromIntError> for Error {
-    fn from(err: TryFromIntError) -> Self {
-        Error::custom(err)
-    }
-}
-
-impl From<Error> for io::Error {
-    fn from(err: Error) -> Self {
-        io::Error::new(io::ErrorKind::Other, err)
-    }
-}
-
-impl From<crate::Error> for Error {
-    fn from(err: crate::Error) -> Self {
-        Error::custom(err)
+        Error::library(error)
     }
 }
 
-pub(crate) type Result<T> = std::result::Result<T, Error>;
-
 #[derive(Debug)]
 pub struct SignatureMismatch {
     pub actual: Principal,
@@ -269,10 +237,10 @@ impl<H> Op for OsslHashOp<H> {
 
     fn finish_into(mut self, buf: &mut [u8]) -> Result<usize> {
         if buf.len() < self.kind.len() {
-            return Err(Error::IncorrectSize {
+            return Err(bterr!(Error::IncorrectSize {
                 expected: self.kind.len(),
                 actual: buf.len(),
-            });
+            }));
         }
         let digest = self.hasher.finish()?;
         let slice = digest.as_ref();
@@ -315,7 +283,7 @@ impl<T, Op: HashOp> HashStream<T, Op> {
     /// written is returned.
     pub fn finish_into(self, buf: &mut [u8]) -> Result<usize> {
         if self.update_failed {
-            return Err(Error::custom(
+            return Err(bterr!(
                 "HashStream::finish_into can't produce result due to HashOp update failure",
             ));
         }
@@ -325,7 +293,7 @@ impl<T, Op: HashOp> HashStream<T, Op> {
     /// Finish this hash operation and return the resulting hash.
     pub fn finish(self) -> Result<Op::Hash> {
         if self.update_failed {
-            return Err(Error::custom(
+            return Err(bterr!(
                 "HashStream::finish can't produce result due to HashOp update failure",
             ));
         }
@@ -336,7 +304,7 @@ impl<T, Op: HashOp> HashStream<T, Op> {
 impl<T: Read, Op: HashOp> Read for HashStream<T, Op> {
     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
         if self.update_failed {
-            return Err(Error::custom(
+            return Err(bterr!(
                 "HashStream::read can't continue due to previous HashOp update failure",
             )
             .into());
@@ -546,12 +514,13 @@ impl HashKind {
     }
 
     pub fn digest<'a, I: Iterator<Item = &'a [u8]>>(self, dest: &mut [u8], parts: I) -> Result<()> {
-        if dest.len() != self.len() {
-            return Err(Error::IncorrectSize {
+        btensure!(
+            dest.len() == self.len(),
+            Error::IncorrectSize {
                 expected: self.len(),
                 actual: dest.len(),
-            });
-        }
+            }
+        );
         let mut hasher = Hasher::new(self.into())?;
         for part in parts {
             hasher.update(part)?;
@@ -563,7 +532,8 @@ impl HashKind {
 }
 
 impl TryFrom<MessageDigest> for HashKind {
-    type Error = Error;
+    type Error = crate::Error;
+
     fn try_from(value: MessageDigest) -> Result<Self> {
         let nid = value.type_();
         if Nid::SHA256 == nid {
@@ -571,10 +541,7 @@ impl TryFrom<MessageDigest> for HashKind {
         } else if Nid::SHA512 == nid {
             Ok(HashKind::Sha2_512)
         } else {
-            Err(Error::custom(format!(
-                "Unsupported MessageDigest with NID: {:?}",
-                nid
-            )))
+            Err(bterr!("Unsupported MessageDigest with NID: {:?}", nid))
         }
     }
 }
@@ -634,7 +601,8 @@ impl AsMut<[u8]> for VarHash {
 }
 
 impl TryFrom<MessageDigest> for VarHash {
-    type Error = Error;
+    type Error = crate::Error;
+
     fn try_from(value: MessageDigest) -> Result<Self> {
         let kind: HashKind = value.try_into()?;
         Ok(kind.into())
@@ -659,11 +627,12 @@ impl Hash for VarHash {
 }
 
 impl TryFrom<&str> for VarHash {
-    type Error = Error;
+    type Error = crate::Error;
+
     fn try_from(string: &str) -> Result<VarHash> {
         let mut split: Vec<&str> = string.split(Self::HASH_SEP).collect();
         if split.len() != 2 {
-            return Err(Error::InvalidHashFormat);
+            return Err(bterr!(Error::InvalidHashFormat));
         };
         let second = split.pop().ok_or(Error::InvalidHashFormat)?;
         let first = split
@@ -703,12 +672,13 @@ impl Op for VarHashOp {
     }
 
     fn finish_into(mut self, buf: &mut [u8]) -> Result<usize> {
-        if buf.len() < self.kind.len() {
-            return Err(Error::IncorrectSize {
+        btensure!(
+            buf.len() >= self.kind.len(),
+            bterr!(Error::IncorrectSize {
                 expected: self.kind.len(),
                 actual: buf.len(),
-            });
-        }
+            })
+        );
         let digest = self.hasher.finish()?;
         let slice = digest.as_ref();
         buf.copy_from_slice(slice);
@@ -805,12 +775,13 @@ impl AeadKeyKind {
 
 fn array_from<const N: usize>(slice: &[u8]) -> Result<[u8; N]> {
     let slice_len = slice.len();
-    if N != slice_len {
-        return Err(Error::IncorrectSize {
+    btensure!(
+        N == slice_len,
+        Error::IncorrectSize {
             actual: slice_len,
             expected: N,
-        });
-    }
+        }
+    );
     let mut array = [0u8; N];
     array.copy_from_slice(slice);
     Ok(array)
@@ -1469,7 +1440,7 @@ impl Verifier for AsymKey<Public, Sign> {
         if verifier.verify(signature)? {
             Ok(())
         } else {
-            Err(Error::InvalidSignature)
+            Err(bterr!(Error::InvalidSignature))
         }
     }
 }
@@ -1854,7 +1825,7 @@ impl<'a> VerifyOp for OsslVerifyOp<'a> {
     fn finish(self, sig: &[u8]) -> Result<()> {
         match self.verifier.verify(sig) {
             Ok(true) => Ok(()),
-            Ok(false) => Err(Error::InvalidSignature),
+            Ok(false) => Err(bterr!(Error::InvalidSignature)),
             Err(err) => Err(err.into()),
         }
     }
@@ -1879,11 +1850,11 @@ impl<T: Read, Op: VerifyOp> VerifyRead<T, Op> {
         }
     }
 
-    pub fn finish(self, sig: &[u8]) -> std::result::Result<T, (T, Error)> {
+    pub fn finish(self, sig: &[u8]) -> std::result::Result<T, (T, crate::Error)> {
         if self.update_failed {
             return Err((
                 self.inner,
-                Error::custom("VerifyRead::finish: update_failed was true"),
+                bterr!("VerifyRead::finish: update_failed was true"),
             ));
         }
         match self.op.finish(sig) {
@@ -1896,7 +1867,7 @@ impl<T: Read, Op: VerifyOp> VerifyRead<T, Op> {
 impl<T: Read, Op: VerifyOp> Read for VerifyRead<T, Op> {
     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
         if self.update_failed {
-            return Err(Error::custom("VerifyRead::read update previously failed").into());
+            return Err(bterr!("VerifyRead::read update previously failed").into());
         }
         let read = self.inner.read(buf)?;
         if read > 0 {
@@ -2035,14 +2006,14 @@ impl BlockMeta {
         let writecap = body
             .writecap
             .as_ref()
-            .ok_or(crate::Error::MissingWritecap)?;
+            .ok_or(crate::BlockError::MissingWritecap)?;
         writecap.assert_valid_for(path)?;
         let signed_by = body.signing_key.principal();
         if writecap.body.issued_to != signed_by {
-            return Err(Error::signature_mismatch(
+            return Err(bterr!(Error::signature_mismatch(
                 writecap.body.issued_to.clone(),
                 signed_by,
-            ));
+            )));
         }
         body.signing_key.ser_verify(&body, self.sig.as_slice())
     }
@@ -2077,10 +2048,10 @@ impl Writecap {
         let now = Epoch::now();
         for _ in 0..CHAIN_LEN_LIMIT {
             if !writecap.body.path.contains(path) {
-                return Err(WritecapAuthzErr::UnauthorizedPath.into());
+                return Err(bterr!(WritecapAuthzErr::UnauthorizedPath));
             }
             if writecap.body.expires <= now {
-                return Err(WritecapAuthzErr::Expired.into());
+                return Err(bterr!(WritecapAuthzErr::Expired));
             }
             if let Some(prev) = &prev {
                 if prev
@@ -2089,12 +2060,12 @@ impl Writecap {
                     .principal_of_kind(writecap.body.issued_to.kind())
                     != writecap.body.issued_to
                 {
-                    return Err(WritecapAuthzErr::NotChained.into());
+                    return Err(bterr!(WritecapAuthzErr::NotChained));
                 }
             }
             sig_input_buf.clear();
             write_to(&writecap.body, &mut sig_input_buf)
-                .map_err(|e| WritecapAuthzErr::Serde(e.to_string()))?;
+                .map_err(|e| bterr!(WritecapAuthzErr::Serde(e.to_string())))?;
             writecap.body.signing_key.verify(
                 std::iter::once(sig_input_buf.as_slice()),
                 writecap.signature.as_slice(),
@@ -2115,12 +2086,12 @@ impl Writecap {
                     {
                         return Ok(());
                     } else {
-                        return Err(WritecapAuthzErr::RootDoesNotOwnPath.into());
+                        return Err(bterr!(WritecapAuthzErr::RootDoesNotOwnPath));
                     }
                 }
             }
         }
-        Err(WritecapAuthzErr::ChainTooLong(CHAIN_LEN_LIMIT).into())
+        Err(bterr!(WritecapAuthzErr::ChainTooLong(CHAIN_LEN_LIMIT)))
     }
 }
 
@@ -2186,23 +2157,28 @@ mod tests {
         let mut writecap = make_writecap(vec!["apps", "verse"]);
         writecap.signature = Signature::empty(Sign::RSA_PSS_3072_SHA_256);
         let result = writecap.assert_valid_for(&writecap.body.path);
-        if let Err(Error::InvalidSignature) = result {
-            Ok(())
-        } else {
-            Err(Error::custom(format!("unexpected result {:?}", result)))
+        if let Err(ref err) = result {
+            if let Some(err) = err.downcast_ref::<Error>() {
+                if let Error::InvalidSignature = err {
+                    return Ok(());
+                }
+            }
         }
+        Err(bterr!("unexpected result {:?}", result))
     }
 
     fn assert_authz_err<T: std::fmt::Debug>(
         expected: WritecapAuthzErr,
         result: Result<T>,
     ) -> Result<()> {
-        if let Err(Error::WritecapAuthzErr(actual)) = &result {
-            if *actual == expected {
-                return Ok(());
+        if let Some(err) = result.as_ref().err() {
+            if let Some(actual) = err.downcast_ref::<WritecapAuthzErr>() {
+                if *actual == expected {
+                    return Ok(());
+                }
             }
         }
-        Err(Error::custom(format!("unexpected result: {:?}", result)))
+        Err(bterr!("unexpected result: {:?}", result))
     }
 
     #[test]

+ 4 - 3
crates/btlib/src/crypto/secret_stream.rs

@@ -2,6 +2,7 @@ pub use private::SecretStream;
 
 mod private {
     use crate::{
+        bterr,
         crypto::{Error, Result, SymKey},
         Block, BlockMeta, Decompose, MetaAccess, Sectored, TryCompose,
     };
@@ -59,17 +60,17 @@ mod private {
     }
 
     impl<T, U: Sectored> TryCompose<U, SecretStream<U>> for SecretStream<T> {
-        type Error = Error;
+        type Error = crate::Error;
         fn try_compose(mut self, inner: U) -> Result<SecretStream<U>> {
             let inner_sect_sz = inner.sector_sz();
             let expansion_sz = self.key.expansion_sz();
             let sect_sz = inner_sect_sz - expansion_sz;
             let block_sz = self.key.block_size();
             if 0 != sect_sz % block_sz {
-                return Err(Error::IndivisibleSize {
+                return Err(bterr!(Error::IndivisibleSize {
                     divisor: block_sz,
                     actual: sect_sz,
-                });
+                }));
             }
             self.pt_buf.resize(inner_sect_sz, 0);
             self.pt_buf.resize(inner_sect_sz + block_sz, 0);

+ 15 - 17
crates/btlib/src/crypto/sign_stream.rs

@@ -6,6 +6,7 @@ mod private {
         crypto::{Error, Result, Signature, Signer, Verifier},
         Block, BlockMeta, Decompose, MetaAccess, ReadExt, Sectored,
     };
+    use anyhow::anyhow;
     use btserde::{read_from, to_vec, write_to};
 
     use std::io::{self, Read, Seek, SeekFrom, Write};
@@ -49,7 +50,7 @@ mod private {
             };
             let in_sz = inner.sector_sz();
             if in_sz < extra {
-                return Err(bterr!(Error::custom("sector size is too small")));
+                return Err(bterr!("sector size is too small"));
             }
             let out_sz = in_sz - extra;
             Ok(SignStream {
@@ -73,10 +74,10 @@ mod private {
         fn assert_sig_len(&self, sig: &Signature) -> Result<()> {
             let actual = sig.data.len();
             if self.sig_len != actual {
-                Err(Error::IncorrectSize {
+                Err(bterr!(Error::IncorrectSize {
                     expected: self.sig_len,
                     actual,
-                })
+                }))
             } else {
                 Ok(())
             }
@@ -102,7 +103,7 @@ mod private {
             let id_bytes = self
                 .id_bytes
                 .as_ref()
-                .ok_or_else(|| Error::custom("id_bytes has not been initialized"))?;
+                .ok_or_else(|| bterr!("id_bytes has not been initialized"))?;
             Ok([id_bytes, self.index_bytes.as_slice(), buf].into_iter())
         }
 
@@ -175,11 +176,11 @@ mod private {
             let result = self.creds.verify(self.sig_input(buf)?, sig.as_slice());
             if let Err(err) = result {
                 self.reset_inner_pos()?;
-                return Err(err.into());
+                return Err(bterr!(err).into());
             }
             if let Err(err) = self.incr_index() {
                 self.reset_inner_pos()?;
-                return Err(err.into());
+                return Err(bterr!(err).into());
             }
             Ok(read)
         }
@@ -193,10 +194,11 @@ mod private {
                     self.index_to_out(self.index).wrapping_add_signed(from_curr)
                 }
                 SeekFrom::End(_) => {
-                    return Err(bterr!(io::Error::new(
-                        io::ErrorKind::Unsupported,
-                        "seeking from end is not supported"
-                    )))
+                    return Err(crate::Error::new(
+                        anyhow!("seek from end is not supported")
+                            .context(io::ErrorKind::Unsupported),
+                    )
+                    .into());
                 }
             };
             let index = self.out_to_index(out_pos);
@@ -240,7 +242,7 @@ mod tests {
 
     use super::*;
     use crate::{
-        crypto::{ConcreteCreds, Error},
+        crypto::ConcreteCreds,
         test_helpers::{node_creds, Randomizer, SectoredCursor},
         Decompose, Sectored, SECTOR_SZ_DEFAULT,
     };
@@ -395,11 +397,7 @@ mod tests {
         let result = stream.read(&mut buf);
 
         let actual_err = result.err().unwrap().into_inner().unwrap();
-        let actual: &Error = actual_err.downcast_ref().unwrap();
-        let is_match = match actual {
-            Error::InvalidSignature => true,
-            _ => false,
-        };
-        assert!(is_match);
+        let actual = format!("{actual_err}");
+        assert_eq!("invalid signature", actual);
     }
 }

+ 42 - 53
crates/btlib/src/crypto/tpm.rs

@@ -1,3 +1,5 @@
+use crate::error::{DisplayErr, StringError};
+
 use super::*;
 
 use btserde::read_from;
@@ -50,23 +52,17 @@ use zeroize::Zeroizing;
 impl From<tss_esapi::Error> for Error {
     fn from(err: tss_esapi::Error) -> Self {
         match err {
-            tss_esapi::Error::WrapperError(err) => Error::custom(err),
+            tss_esapi::Error::WrapperError(err) => Error::library(err),
             tss_esapi::Error::Tss2Error(err) => {
                 let rc = err.tss2_rc();
                 let text = tss2_rc_decode(err);
                 let string = format!("response code: {rc}, response text: {text}");
-                Error::custom(string)
+                Error::library(StringError::new(string))
             }
         }
     }
 }
 
-impl<Guard> From<std::sync::PoisonError<Guard>> for Error {
-    fn from(error: std::sync::PoisonError<Guard>) -> Self {
-        Error::custom(error.to_string())
-    }
-}
-
 /// Trait which extends the `tss_esapi::Context`.
 trait ContextExt {
     fn persistent_handles(&mut self) -> Result<Vec<PersistentTpmHandle>>;
@@ -123,10 +119,10 @@ impl ContextExt for Context {
             let list = match handles {
                 CapabilityData::Handles(list) => list.into_inner(),
                 _ => {
-                    return Err(Error::custom(format!(
+                    return Err(bterr!(
                         "Unexpected capability type returned by TPM: {:?}",
                         handles
-                    )))
+                    ))
                 }
             };
             all_handles.reserve(list.len());
@@ -138,7 +134,7 @@ impl ContextExt for Context {
             }
         }
         if more {
-            return Err(Error::custom(
+            return Err(bterr!(
                 "hit iteration limit before retrieving all persistent handles from the TPM",
             ));
         }
@@ -175,7 +171,7 @@ impl ContextExt for Context {
         }
         match unused {
             Some(unused) => Ok(Persistent::Persistent(unused)),
-            None => Err(Error::custom("failed to find an unused persistent handle")),
+            None => Err(bterr!("failed to find an unused persistent handle")),
         }
     }
 
@@ -204,7 +200,7 @@ impl ContextExt for Context {
         }
         match persistent {
             Some(Persistent::Persistent(inner)) => Ok(inner.into()),
-            None => Err(Error::custom("retry limit reached")),
+            None => Err(bterr!("retry limit reached")),
         }
     }
 
@@ -238,7 +234,7 @@ impl ContextExt for Context {
                 SymmetricDefinition::AES_256_CFB,
                 HashingAlgorithm::Sha256,
             )?
-            .ok_or_else(|| Error::custom("empty session handle received from TPM"))?;
+            .ok_or_else(|| bterr!("empty session handle received from TPM"))?;
         self.set_encrypt_decrypt(session)?;
         Ok(session)
     }
@@ -253,7 +249,7 @@ impl ContextExt for Context {
                 SymmetricDefinition::AES_256_CFB,
                 HashingAlgorithm::Sha256,
             )?
-            .ok_or_else(|| Error::custom("empty session handle received from TPM"))?;
+            .ok_or_else(|| bterr!("empty session handle received from TPM"))?;
         self.set_encrypt_decrypt(session)?;
         Ok(session.try_into()?)
     }
@@ -595,7 +591,8 @@ impl From<HashKind> for HashingAlgorithm {
 }
 
 impl TryInto<RsaScheme> for SchemeKind {
-    type Error = Error;
+    type Error = crate::Error;
+
     fn try_into(self) -> Result<RsaScheme> {
         match self {
             SchemeKind::Sign(sign) => match sign {
@@ -613,14 +610,14 @@ impl TryInto<RsaScheme> for SchemeKind {
 }
 
 impl TryFrom<KeyLen> for RsaKeyBits {
-    type Error = Error;
+    type Error = crate::Error;
 
     fn try_from(len: KeyLen) -> Result<RsaKeyBits> {
         match len {
             KeyLen::Bits2048 => Ok(RsaKeyBits::Rsa2048),
             KeyLen::Bits3072 => Ok(RsaKeyBits::Rsa3072),
             KeyLen::Bits4096 => Ok(RsaKeyBits::Rsa4096),
-            _ => Err(Error::custom(format!("unsupported key len: {len}"))),
+            _ => Err(bterr!("unsupported key len: {len}")),
         }
     }
 }
@@ -863,7 +860,7 @@ impl TpmCredStore {
         let cookie = storage.cookie.clone();
         let state = Arc::new(RwLock::new(State::new(context, storage)?));
         {
-            let mut guard = state.write()?;
+            let mut guard = state.write().display_err()?;
             guard.init_node_creds(&state)?;
         }
         Ok(TpmCredStore {
@@ -886,7 +883,7 @@ impl TpmCredStore {
         creds: &TpmCreds,
         update_storage: F,
     ) -> Result<()> {
-        let mut guard = self.state.write()?;
+        let mut guard = self.state.write().display_err()?;
         let sign_handle = guard.context.persist_key(creds.sign.private)?;
         let enc_handle = match guard.context.persist_key(creds.enc.private) {
             Ok(handle) => handle,
@@ -923,14 +920,14 @@ impl TpmCredStore {
 
     fn get_or_init_storage_key(&self) -> Result<KeyPair<Encrypt>> {
         {
-            let guard = self.state.read()?;
+            let guard = self.state.read().display_err()?;
             if let Some(storage_key) = &guard.storage_key {
                 // We take this path if the storage key was generated and loaded.
                 return Ok(storage_key.clone());
             }
         }
         {
-            let mut guard = self.state.write()?;
+            let mut guard = self.state.write().display_err()?;
             if let Some(storage_key) = guard.storage.storage_key.take() {
                 let result = KeyPair::from_stored(&mut guard.context, &storage_key);
                 guard.storage.storage_key = Some(storage_key);
@@ -941,7 +938,7 @@ impl TpmCredStore {
         let params = KeyBuilder::for_storage_key(Self::ENCRYPT_SCHEME, self.cookie.as_slice())
             .with_parent(Parent::Seed(Hierarchy::Owner))
             .with_auth(self.cookie.auth());
-        let mut guard = self.state.write()?;
+        let mut guard = self.state.write().display_err()?;
         let storage_key = params.build(&mut guard.context)?;
         guard.storage_key = Some(storage_key.clone());
         let tpm_handle = guard.context.persist_key(storage_key.private)?;
@@ -958,7 +955,7 @@ impl TpmCredStore {
     }
 
     fn gen_key<S: Scheme>(&self, params: KeyBuilder<S>) -> Result<KeyPair<S>> {
-        let mut guard = self.state.write()?;
+        let mut guard = self.state.write().display_err()?;
         params.build(&mut guard.context)
     }
 
@@ -1062,7 +1059,7 @@ impl CredStore for TpmCredStore {
 
     fn node_creds(&self) -> Result<TpmCreds> {
         {
-            let guard = self.state.read()?;
+            let guard = self.state.read().display_err()?;
             if let Some(creds) = &guard.node_creds {
                 return Ok(creds.clone());
             }
@@ -1072,15 +1069,15 @@ impl CredStore for TpmCredStore {
 
     fn root_creds(&self, password: &str) -> Result<Self::CredHandle> {
         let root_handles = {
-            let guard = self.state.read()?;
+            let guard = self.state.read().display_err()?;
             guard
                 .storage
                 .root
                 .as_ref()
-                .ok_or_else(|| Error::custom("root creds have not yet been generated"))?
+                .ok_or_else(|| bterr!("root creds have not yet been generated"))?
                 .clone()
         };
-        let mut guard = self.state.write()?;
+        let mut guard = self.state.write().display_err()?;
         let key_handles = root_handles.to_cred_data(&mut guard.context)?;
         guard.context.set_auth(&key_handles, password)?;
         Ok(TpmCreds::new(key_handles, &self.state))
@@ -1088,13 +1085,13 @@ impl CredStore for TpmCredStore {
 
     fn gen_root_creds(&self, password: &str) -> Result<Self::CredHandle> {
         {
-            let guard = self.state.read()?;
+            let guard = self.state.read().display_err()?;
             if guard.storage.root.is_some() {
-                return Err(Error::custom("root creds have already been generated"));
+                return Err(bterr!("root creds have already been generated"));
             }
         }
         let policy = {
-            let mut guard = self.state.write()?;
+            let mut guard = self.state.write().display_err()?;
             guard.context.dup_with_password_policy()?
         };
         let sign = self.gen_root_sign_key(password, policy.clone())?;
@@ -1105,7 +1102,7 @@ impl CredStore for TpmCredStore {
             writecap: None,
         };
         {
-            let mut guard = self.state.write()?;
+            let mut guard = self.state.write().display_err()?;
             guard.context.set_auth(&cred_data, password)?;
         }
         let mut creds = TpmCreds::new(cred_data, &self.state);
@@ -1128,7 +1125,7 @@ impl CredStore for TpmCredStore {
         let params = DerivationParams::new()?;
         let aead_key = params.derive_key(password)?;
         let new_parent = new_parent.storage_key_public()?;
-        let mut guard = self.state.write()?;
+        let mut guard = self.state.write().display_err()?;
         if let Some(storage_key) = guard.storage_key.take() {
             // Save memory by flushing the storage key from the TPM's RAM.
             guard.context.flush_context(storage_key.private.into())?;
@@ -1165,7 +1162,7 @@ impl CredStore for TpmCredStore {
         let auth = Auth::try_from(password.as_bytes())?;
         let storage_key = self.get_or_init_storage_key()?;
         let creds = {
-            let mut guard = self.state.write()?;
+            let mut guard = self.state.write().display_err()?;
             let sign = guard.context.import_key(
                 exported.sign,
                 storage_key.private,
@@ -1193,7 +1190,7 @@ impl CredStore for TpmCredStore {
         writecap: Writecap,
     ) -> Result<()> {
         handle.writecap = Some(writecap.clone());
-        let mut state = self.state.write()?;
+        let mut state = self.state.write().display_err()?;
         if let Some(creds) = state.node_creds.as_mut() {
             creds.writecap = Some(writecap.clone());
         }
@@ -1217,7 +1214,7 @@ impl<S: Scheme> AsymKeyPub<S> {
                 let pkey = PKey::from_rsa(rsa)?.conv_pub();
                 Ok(AsymKey { pkey, scheme })
             }
-            _ => Err(Error::custom("Unsupported key type returned by TPM")),
+            key => Err(bterr!("Unsupported key type returned by TPM: {:?}", key)),
         }
     }
 }
@@ -1260,7 +1257,7 @@ impl BigNumRefExt for &BigNumRef {
     fn try_into_u32(self) -> Result<u32> {
         let data = self.to_vec();
         if data.len() > 4 {
-            return Err(Error::custom(format!("data was too long: {}", data.len())));
+            return Err(bterr!("data was too long: {}", data.len()));
         }
         let mut buf = [0u8; 4];
         // Note that BigNum data is stored in big endian format, so padding zeros go at the
@@ -1297,10 +1294,7 @@ impl MessageDigestExt for MessageDigest {
         } else if Nid::sha3_512() == nid {
             HashingAlgorithm::Sha3_512
         } else {
-            return Err(Error::custom(format!(
-                "Unsupported hash algorithm with NID: {:?}",
-                nid
-            )));
+            return Err(bterr!("Unsupported hash algorithm with NID: {:?}", nid));
         };
         Ok(algo)
     }
@@ -1405,7 +1399,7 @@ impl<'a> Op for TpmSignOp<'a> {
         let scheme = SignatureScheme::Null;
 
         let sig = {
-            let mut guard = self.creds.state.write()?;
+            let mut guard = self.creds.state.write().display_err()?;
             guard
                 .context
                 .sign(self.creds.sign.private, digest, scheme, validation)?
@@ -1413,19 +1407,14 @@ impl<'a> Op for TpmSignOp<'a> {
         let slice: &[u8] = match &sig {
             tss_esapi::structures::Signature::RsaSsa(inner) => inner.signature(),
             tss_esapi::structures::Signature::RsaPss(inner) => inner.signature(),
-            _ => {
-                return Err(Error::custom(format!(
-                    "Unexpected signature type: {:?}",
-                    sig.algorithm()
-                )))
-            }
+            _ => return Err(bterr!("Unexpected signature type: {:?}", sig.algorithm())),
         };
 
         if buf.len() < slice.len() {
-            return Err(Error::IncorrectSize {
+            return Err(bterr!(Error::IncorrectSize {
                 expected: slice.len(),
                 actual: buf.len(),
-            });
+            }));
         }
         buf.copy_from_slice(slice);
         Ok(slice.len())
@@ -1462,7 +1451,7 @@ impl Decrypter for TpmCreds {
         let empty = [0u8; 0];
         let label = Data::try_from(empty.as_slice())?;
         let plain_text = {
-            let mut guard = self.state.write()?;
+            let mut guard = self.state.write().display_err()?;
             guard
                 .context
                 .rsa_decrypt(self.enc.private, cipher_text, in_scheme, label)?
@@ -1706,7 +1695,7 @@ mod test {
         if expected.as_slice() == actual {
             Ok(())
         } else {
-            Err(Error::custom("decrypted data did not match input"))
+            Err(bterr!("decrypted data did not match input"))
         }
     }
 
@@ -1745,7 +1734,7 @@ mod test {
         let cookie = Cookie::random()?;
         let params = KeyBuilder::new(Sign::RSA_PSS_3072_SHA_256, cookie.as_slice());
         let pair = store.gen_key(params)?;
-        let mut guard = store.state.write()?;
+        let mut guard = store.state.write().display_err()?;
         guard.context.persist_key(pair.private)?;
         Ok(())
     }

+ 208 - 0
crates/btlib/src/error.rs

@@ -0,0 +1,208 @@
+//! This module defines the `Error` and `Result` types used in this crate. It also defines macros,
+//! `bterr!` and `btensure!`. The first accepts either a type which is
+//! `StdError + Send + Sync + 'static` or the same arguments as `format!`, allowing you to create
+//! one-off string errors. `btensure` takes a boolean expression as its first argument, and its
+//! remaining arguments are the same as those to `bterr`.
+
+use anyhow::anyhow;
+use serde::{Deserialize, Serialize};
+use std::{fmt::Display, io};
+
+use crate::Decompose;
+
+#[macro_export]
+macro_rules! bterr {
+    ($msg:literal $(,)?) => { $crate::Error::new(anyhow::anyhow!($msg)) };
+    ($err:expr $(,)?) => { $crate::Error::new(anyhow::anyhow!($err)) };
+    ($fmt:expr, $($arg:tt)*) => { $crate::Error::new(anyhow::anyhow!($fmt, $($arg)*)) };
+}
+
+#[macro_export]
+macro_rules! btensure {
+    ($cond:expr, $msg:literal $(,)?) => {
+        if !cond {
+            return Err($crate::bterr!($msg));
+        }
+    };
+    ($cond:expr, $err:expr $(,)?) => {
+        if !$cond {
+            return Err($crate::bterr!($err));
+        }
+    };
+    ($cond:expr, $fmt:expr, $($arg:tt)*) => {
+        if !cond {
+            return Err($crate::bterr!($msg, $($arg)*));
+        }
+    };
+}
+
+pub type Result<T> = std::result::Result<T, Error>;
+
+/// The top-level error type used by this crate. This is just a newtype wrapper around
+/// [anyhow::Error]. A newtype is used so that additional traits can be defined
+/// (most importantly `From<Error> for std::io::Error`).
+#[derive(Debug)]
+pub struct Error(anyhow::Error);
+
+impl Error {
+    pub fn new(err: anyhow::Error) -> Self {
+        Self(err)
+    }
+
+    pub fn downcast<E: std::error::Error + Send + Sync + 'static>(
+        self,
+    ) -> std::result::Result<E, Self> {
+        self.0.downcast::<E>().bterr()
+    }
+
+    pub fn downcast_ref<E: Display + ::std::fmt::Debug + Send + Sync + 'static>(
+        &self,
+    ) -> Option<&E> {
+        self.0.downcast_ref::<E>()
+    }
+
+    pub fn downcast_mut<E: Display + ::std::fmt::Debug + Send + Sync + 'static>(
+        &mut self,
+    ) -> Option<&mut E> {
+        self.0.downcast_mut::<E>()
+    }
+}
+
+impl Display for Error {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        self.0.fmt(f)
+    }
+}
+
+impl AsRef<anyhow::Error> for Error {
+    fn as_ref(&self) -> &anyhow::Error {
+        &self.0
+    }
+}
+
+impl AsMut<anyhow::Error> for Error {
+    fn as_mut(&mut self) -> &mut anyhow::Error {
+        &mut self.0
+    }
+}
+
+impl Decompose<anyhow::Error> for Error {
+    fn into_inner(self) -> anyhow::Error {
+        self.0
+    }
+}
+
+impl From<Error> for io::Error {
+    fn from(value: Error) -> Self {
+        let kind = value
+            .0
+            .downcast_ref::<io::ErrorKind>()
+            .copied()
+            .unwrap_or(io::ErrorKind::Other);
+        io::Error::new(kind, format!("{value}"))
+    }
+}
+
+impl<E: std::error::Error + Send + Sync + 'static> From<E> for Error {
+    fn from(value: E) -> Self {
+        Self::new(anyhow!(value))
+    }
+}
+
+/// A wrapper for `String` which implements `std::error::Error`.
+#[derive(Debug, PartialEq, Eq, Clone, PartialOrd, Ord, Serialize, Deserialize, Hash)]
+pub struct StringError(String);
+
+impl StringError {
+    pub fn new(inner: String) -> StringError {
+        Self(inner)
+    }
+
+    pub fn take_value(self) -> String {
+        self.0
+    }
+
+    pub fn ref_value(&self) -> &String {
+        &self.0
+    }
+
+    pub fn mut_value(&mut self) -> &mut String {
+        &mut self.0
+    }
+}
+
+impl Display for StringError {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}", self.0)
+    }
+}
+
+impl ::std::error::Error for StringError {}
+
+impl From<String> for StringError {
+    fn from(value: String) -> Self {
+        Self::new(value)
+    }
+}
+
+impl From<StringError> for String {
+    fn from(value: StringError) -> Self {
+        value.take_value()
+    }
+}
+
+impl AsRef<String> for StringError {
+    fn as_ref(&self) -> &String {
+        self.ref_value()
+    }
+}
+
+impl AsMut<String> for StringError {
+    fn as_mut(&mut self) -> &mut String {
+        self.mut_value()
+    }
+}
+
+pub trait BtErr<T> {
+    /// Maps the error in the given result to a `btlib::Error`.
+    fn bterr(self) -> Result<T>;
+}
+
+impl<T> BtErr<T> for std::result::Result<T, anyhow::Error> {
+    fn bterr(self) -> Result<T> {
+        self.map_err(Error::new)
+    }
+}
+
+pub trait IoErr<T> {
+    /// Maps the error in this result to an `io::Error`.
+    fn io_err(self) -> io::Result<T>;
+}
+
+impl<T> IoErr<T> for Result<T> {
+    fn io_err(self) -> io::Result<T> {
+        self.map_err(|err| err.into())
+    }
+}
+
+pub trait DisplayErr<T> {
+    /// Uses the `Display` trait to convert the error in a `Result` to a string.
+    fn display_err(self) -> Result<T>;
+}
+
+impl<T, E: Display> DisplayErr<T> for std::result::Result<T, E> {
+    fn display_err(self) -> Result<T> {
+        self.map_err(|err| bterr!("{err}"))
+    }
+}
+
+pub trait BoxInIoErr<T> {
+    /// Boxes the error in a `Result` into an `io::Error`.
+    fn box_err(self) -> std::result::Result<T, io::Error>;
+}
+
+impl<T, E: std::error::Error + Send + Sync + 'static> BoxInIoErr<T> for std::result::Result<T, E> {
+    fn box_err(self) -> std::result::Result<T, io::Error> {
+        self.map_err(|err| bterr!(err).into())
+    }
+}

+ 34 - 130
crates/btlib/src/lib.rs

@@ -2,8 +2,10 @@ pub use block_path::{BlockPath, BlockPathError};
 pub mod blocktree;
 /// Code which enables cryptographic operations.
 pub mod crypto;
+pub mod error;
 pub mod log;
 pub mod msg;
+pub use error::{Error, Result};
 
 mod block_path;
 mod sectored_buf;
@@ -23,6 +25,7 @@ use crypto::{
     AsymKeyPub, Ciphertext, Creds, Decrypter, DecrypterExt, Encrypter, EncrypterExt, HashKind,
     MerkleStream, PublicCreds, SecretStream, Sign, Signature, Signer, SymKey, SymKeyKind, VarHash,
 };
+use error::BoxInIoErr;
 use trailered::Trailered;
 
 use ::log::error;
@@ -39,138 +42,32 @@ use std::{
     io::{self, Read, Seek, SeekFrom, Write},
     net::SocketAddr,
     ops::{Add, Sub},
-    sync::PoisonError,
     time::{Duration, SystemTime},
 };
 use strum_macros::{Display, EnumDiscriminants, FromRepr};
 
-#[macro_export]
-macro_rules! bterr {
-    ($err:expr) => {
-        $err
-    };
-}
-
 #[derive(Debug)]
-pub enum Error {
+pub enum BlockError {
     MissingWritecap,
-    Io(std::io::Error),
-    Serde(btserde::Error),
-    Crypto(crypto::Error),
     IncorrectSize { expected: usize, actual: usize },
     NoBlockKey,
-    NotOpen(u64),
-    InvalidHandle { handle: u64, inode: u64 },
-    NoHandlesAvailable(u64),
     NoBlockPath,
-    InodeNotFound(u64),
-    Custom(Box<dyn std::fmt::Debug + Send + Sync>),
-}
-
-impl Error {
-    fn custom<E: std::fmt::Debug + Send + Sync + 'static>(err: E) -> Error {
-        Error::Custom(Box::new(err))
-    }
 }
 
-impl Display for Error {
+impl Display for BlockError {
     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
         match self {
-            Error::MissingWritecap => write!(f, "missing writecap"),
-            Error::Io(err) => err.fmt(f),
-            Error::Serde(err) => err.fmt(f),
-            Error::Crypto(err) => err.fmt(f),
-            Error::IncorrectSize { expected, actual } => {
+            BlockError::MissingWritecap => write!(f, "missing writecap"),
+            BlockError::IncorrectSize { expected, actual } => {
                 write!(f, "incorrect size {actual}, expected {expected}")
             }
-            Error::NoBlockKey => write!(f, "no block key is present"),
-            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::NoBlockPath => write!(f, "no block path was specified"),
-            Error::InodeNotFound(inode) => write!(f, "inode {inode} could not be found"),
-            Error::Custom(err) => err.fmt(f),
+            BlockError::NoBlockKey => write!(f, "no block key is present"),
+            BlockError::NoBlockPath => write!(f, "no block path was specified"),
         }
     }
 }
 
-impl std::error::Error for Error {}
-
-impl From<std::io::Error> for Error {
-    fn from(err: std::io::Error) -> Self {
-        Error::Io(err)
-    }
-}
-
-impl From<Error> for std::io::Error {
-    fn from(err: Error) -> Self {
-        io::Error::new(io::ErrorKind::Other, err)
-    }
-}
-
-impl From<btserde::Error> for Error {
-    fn from(err: btserde::Error) -> Self {
-        Error::Serde(err)
-    }
-}
-
-impl From<crypto::Error> for Error {
-    fn from(err: crypto::Error) -> Self {
-        Error::Crypto(err)
-    }
-}
-
-impl From<std::num::TryFromIntError> for Error {
-    fn from(err: std::num::TryFromIntError) -> Self {
-        Error::custom(err)
-    }
-}
-
-impl<T: std::fmt::Debug> From<PoisonError<T>> for Error {
-    fn from(err: PoisonError<T>) -> Self {
-        Error::custom(err.to_string())
-    }
-}
-
-type Result<T> = std::result::Result<T, Error>;
-
-/// TODO: Remove this once the error_chain crate is integrated.
-trait BoxInIoErr<T> {
-    fn box_err(self) -> std::result::Result<T, io::Error>;
-}
-
-impl<T, E: Into<Box<dyn Send + Sync + std::error::Error>>> BoxInIoErr<T>
-    for std::result::Result<T, E>
-{
-    fn box_err(self) -> std::result::Result<T, io::Error> {
-        self.map_err(|err| io::Error::new(io::ErrorKind::Other, err))
-    }
-}
-
-trait ToStringInIoErr<T> {
-    fn err_to_string(self) -> std::result::Result<T, io::Error>;
-}
-
-impl<T, E: ToString> ToStringInIoErr<T> for std::result::Result<T, E> {
-    fn err_to_string(self) -> std::result::Result<T, io::Error> {
-        self.map_err(|err| io::Error::new(io::ErrorKind::Other, err.to_string()))
-    }
-}
-
-/// TODO: Remove this once the error_chain crate is integrated.
-trait StrInIoErr<T> {
-    fn str_err(self) -> std::result::Result<T, io::Error>;
-}
-
-impl<T, E: Display> StrInIoErr<T> for std::result::Result<T, E> {
-    fn str_err(self) -> std::result::Result<T, io::Error> {
-        self.map_err(|err| io::Error::new(io::ErrorKind::Other, err.to_string()))
-    }
-}
+impl std::error::Error for BlockError {}
 
 /// The default sector size to use for new blocks.
 pub const SECTOR_SZ_DEFAULT: usize = 4096;
@@ -218,7 +115,7 @@ pub trait Sectored {
     fn assert_sector_sz(&self, actual: usize) -> Result<()> {
         let expected = self.sector_sz();
         if expected != actual {
-            Err(Error::IncorrectSize { expected, actual })
+            Err(bterr!(BlockError::IncorrectSize { expected, actual }))
         } else {
             Ok(())
         }
@@ -228,7 +125,7 @@ pub trait Sectored {
     fn assert_at_least_sector_sz(&self, actual: usize) -> Result<()> {
         let expected = self.sector_sz();
         if actual < expected {
-            Err(Error::IncorrectSize { expected, actual })
+            Err(bterr!(BlockError::IncorrectSize { expected, actual }))
         } else {
             Ok(())
         }
@@ -544,7 +441,7 @@ impl BlockMetaBody {
         self.secrets = self
             .block_key
             .as_ref()
-            .ok_or(Error::NoBlockKey)?
+            .ok_or(BlockError::NoBlockKey)?
             .ser_encrypt(secrets)?;
         Ok(output)
     }
@@ -560,11 +457,13 @@ impl BlockMetaBody {
     pub fn secrets(&self) -> Result<&BlockMetaSecrets> {
         self.secrets_struct
             .as_ref()
-            .ok_or_else(|| Error::custom("secrets have not been decrypted"))
+            .ok_or_else(|| bterr!("secrets have not been decrypted"))
     }
 
     pub fn block_key(&self) -> Result<&SymKey> {
-        self.block_key.as_ref().ok_or(Error::NoBlockKey)
+        self.block_key
+            .as_ref()
+            .ok_or_else(|| bterr!(BlockError::NoBlockKey))
     }
 
     pub fn block_id(&self) -> Result<&BlockId> {
@@ -653,8 +552,12 @@ impl<T: Read + Seek, C: Creds> BlockStream<T, C> {
                 meta.body.path = block_path;
                 meta.body.use_readcap_for(&creds)?;
                 // We need to use the writecap and signing_key provided by the current credentials.
-                meta.body.writecap =
-                    Some(creds.writecap().ok_or(Error::MissingWritecap)?.to_owned());
+                meta.body.writecap = Some(
+                    creds
+                        .writecap()
+                        .ok_or(BlockError::MissingWritecap)?
+                        .to_owned(),
+                );
                 meta.body.signing_key = creds.public_sign().to_owned();
                 meta.body.decrypt_secrets()?;
                 meta
@@ -663,8 +566,12 @@ impl<T: Read + Seek, C: Creds> BlockStream<T, C> {
                 let mut meta = BlockMeta::new(&creds)?;
                 meta.body.path = block_path;
                 meta.body.add_readcap_for(creds.principal(), &creds)?;
-                meta.body.writecap =
-                    Some(creds.writecap().ok_or(Error::MissingWritecap)?.to_owned());
+                meta.body.writecap = Some(
+                    creds
+                        .writecap()
+                        .ok_or(BlockError::MissingWritecap)?
+                        .to_owned(),
+                );
                 meta
             }
         };
@@ -813,7 +720,7 @@ impl<T, C> BlockOpenOptions<T, C> {
 
 impl<T: Read + Write + Seek + 'static, C: Creds + 'static> BlockOpenOptions<T, C> {
     pub fn open(self) -> Result<Box<dyn Block>> {
-        let block_path = self.block_path.ok_or(Error::NoBlockPath)?;
+        let block_path = self.block_path.ok_or(BlockError::NoBlockPath)?;
         let stream = BlockStream::new(self.inner, self.creds, block_path)?;
         let block_key = stream.meta_body().block_key().map(|e| e.to_owned())?;
         let mut stream = MerkleStream::new(stream)?;
@@ -865,7 +772,7 @@ impl BrotliParams {
 }
 
 impl<T: Write> TryCompose<T, CompressorWriter<T>> for BrotliParams {
-    type Error = Error;
+    type Error = crate::Error;
     fn try_compose(self, inner: T) -> Result<CompressorWriter<T>> {
         Ok(CompressorWriter::new(
             inner,
@@ -877,7 +784,7 @@ impl<T: Write> TryCompose<T, CompressorWriter<T>> for BrotliParams {
 }
 
 impl<T: Read> TryCompose<T, Decompressor<T>> for BrotliParams {
-    type Error = Error;
+    type Error = crate::Error;
     fn try_compose(self, inner: T) -> Result<Decompressor<T>> {
         Ok(Decompressor::new(inner, self.buf_sz))
     }
@@ -1048,10 +955,7 @@ impl Directory {
     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()
-                )));
+                return Err(bterr!("directory already contains entry '{}'", entry.key()));
             }
             btree_map::Entry::Vacant(entry) => entry,
         };
@@ -1061,7 +965,7 @@ impl Directory {
         }));
         match dir_entry {
             DirEntry::File(record) => Ok(record),
-            _ => Err(Error::custom("DirEntry was not the File variant")),
+            _ => Err(bterr!("DirEntry was not the File variant")),
         }
     }
 

+ 19 - 14
crates/btlib/src/sectored_buf.rs

@@ -7,8 +7,8 @@ mod private {
     use btserde::{read_from, write_to};
 
     use crate::{
-        Block, BlockMeta, BoxInIoErr, Decompose, Error, MetaAccess, ReadExt, Result, Sectored,
-        TryCompose,
+        bterr, Block, BlockError, BlockMeta, BoxInIoErr, Decompose, MetaAccess, ReadExt, Result,
+        Sectored, TryCompose,
     };
 
     /// A stream which buffers writes and read such that the inner stream only sees reads and writes
@@ -86,10 +86,10 @@ mod private {
             let read_bytes = if self.buf_start < self.len {
                 let read_bytes = self.inner.fill_buf(&mut self.buf)?;
                 if read_bytes < self.buf.len() {
-                    return Err(Error::IncorrectSize {
+                    return Err(bterr!(BlockError::IncorrectSize {
                         expected: self.buf.len(),
                         actual: read_bytes,
-                    });
+                    }));
                 }
                 read_bytes
             } else {
@@ -112,15 +112,15 @@ mod private {
     }
 
     impl<T: Sectored + Read + Seek> TryCompose<T, SectoredBuf<T>> for SectoredBuf<()> {
-        type Error = Error;
+        type Error = crate::Error;
         fn try_compose(self, inner: T) -> Result<SectoredBuf<T>> {
             let sect_sz = inner.sector_sz();
             if sect_sz < Self::RESERVED {
-                return Err(Error::custom(format!(
+                return Err(bterr!(
                     "a sector size of at least {} is required. Got {}",
                     Self::RESERVED,
                     sect_sz,
-                )));
+                ));
             }
             let mut sectored = SectoredBuf {
                 inner,
@@ -134,15 +134,20 @@ mod private {
             sectored.buf.resize(sect_sz, 0);
             let len_stored = match sectored.fill_internal_buf() {
                 Ok(bytes_read) => bytes_read >= Self::RESERVED,
-                Err(Error::IncorrectSize { actual, expected }) => {
-                    if actual > 0 {
-                        return Err(Error::IncorrectSize { expected, actual });
+                Err(err) => {
+                    let err = err.downcast::<BlockError>()?;
+                    match err {
+                        BlockError::IncorrectSize { actual, expected } => {
+                            if actual > 0 {
+                                return Err(bterr!(BlockError::IncorrectSize { expected, actual }));
+                            }
+                            // When the actual size was 0 that just means the inner stream was
+                            // empty, which is not an error.
+                            false
+                        }
+                        err => return Err(bterr!(err)),
                     }
-                    // When the actual size was 0 that just means the inner stream was empty, which
-                    // is not an error.
-                    false
                 }
-                Err(err) => return Err(err),
             };
             if len_stored {
                 if let Ok(len) = read_from::<u64, _>(&mut sectored.buf.as_slice()) {