Răsfoiți Sursa

Modfied locking in the Blocktree struct to allow for more
concurrent access.

Matthew Carr 2 ani în urmă
părinte
comite
c7165de3f1
2 a modificat fișierele cu 278 adăugiri și 114 ștergeri
  1. 262 112
      crates/btlib/src/blocktree.rs
  2. 16 2
      crates/btlib/src/lib.rs

+ 262 - 112
crates/btlib/src/blocktree.rs

@@ -8,7 +8,7 @@ mod private {
             Context, DirEntry as FuseDirEntry, Entry, FileSystem, FsOptions, OpenOptions,
         },
     };
-    use log::{debug, error};
+    use log::{debug, error, warn};
     use serde::{Deserialize, Serialize};
     use std::{
         collections::hash_map::{self, HashMap},
@@ -17,15 +17,14 @@ mod private {
         io::{self, SeekFrom, Write},
         path::{Path, PathBuf},
         sync::{
-            atomic::{AtomicU64, Ordering},
-            RwLock,
+            atomic::{AtomicU64, Ordering}, RwLock,
         },
         time::Duration,
     };
 
     use crate::{
         crypto::Creds, Block, BlockMeta, BlockOpenOptions, BlockPath, BoxInIoErr, DirEntry,
-        Directory, Epoch, Error, Result,
+        Directory, Epoch, Error, Result, ToStringInIoErr,
     };
 
     type Inode = u64;
@@ -178,12 +177,78 @@ mod private {
         }
     }
 
+    enum HandleValue {
+        File {
+            block: RwLock<Box<dyn Block>>,
+        },
+        Directory {
+            dir: Directory,
+        }
+    }
+
+    impl HandleValue {
+        fn new_file(block: Box<dyn Block>) -> HandleValue {
+            HandleValue::File {
+                block: RwLock::new(block),
+            }
+        }
+
+        fn new_dir(dir: Directory) -> HandleValue {
+            HandleValue::Directory { dir }
+        }
+
+        fn not_file_err<T>() -> io::Result<T> {
+            Err(io::Error::new(io::ErrorKind::Other, "handle is not for a file"))
+        }
+
+        fn block_mut(&mut self) -> io::Result<&mut Box<dyn Block>> {
+            match self {
+                Self::File { block, .. } => block
+                    .get_mut()
+                    .map_err(|err| io::Error::new(io::ErrorKind::Other, err.to_string())),
+                _ => Self::not_file_err(),
+            }
+        }
+
+        fn access_block<T, F: FnOnce(&Box<dyn Block>) -> io::Result<T>>(
+            &self,
+            cb: F,
+        ) -> io::Result<T> {
+            match self {
+                Self::File { block, .. } => {
+                    let guard = block.read().err_to_string()?;
+                    cb(&guard)
+                }
+                _ => Self::not_file_err(),
+            }
+        }
+
+        fn access_block_mut<T, F: FnOnce(&mut Box<dyn Block>) -> io::Result<T>>(
+            &self,
+            cb: F,
+        ) -> io::Result<T> {
+            match self {
+                Self::File { block, .. } => {
+                    let mut guard = block.write().err_to_string()?;
+                    cb(&mut guard)
+                }
+                _ => Self::not_file_err(),
+            }
+        }
+
+        fn directory(&self) -> io::Result<&Directory> {
+            match self {
+                Self::Directory { dir, .. } => Ok(dir),
+                _ => Err(io::Error::new(io::ErrorKind::Other, "handle is not for a directory"))
+            }
+        }
+    }
+
     struct InodeTableValue {
-        blocks: HashMap<Handle, Box<dyn Block>>,
-        dirs: HashMap<Handle, Directory>,
+        handle_values: HashMap<Handle, HandleValue>,
         next_handle: Handle,
-        lookup_count: AtomicU64,
         unclaimed_handles: Vec<Handle>,
+        lookup_count: u64,
     }
 
     impl InodeTableValue {
@@ -193,22 +258,36 @@ mod private {
 
         fn new(block: Box<dyn Block>) -> InodeTableValue {
             const FIRST_HANDLE: Handle = 1;
-            let mut blocks = HashMap::with_capacity(1);
-            blocks.insert(FIRST_HANDLE, block);
+            let mut handles = HashMap::with_capacity(1);
+            handles.insert(FIRST_HANDLE, HandleValue::new_file(block));
             Self {
-                blocks,
-                dirs: HashMap::new(),
+                handle_values: handles,
                 next_handle: FIRST_HANDLE + 1,
-                lookup_count: AtomicU64::new(1),
+                lookup_count: 1,
                 unclaimed_handles: vec![FIRST_HANDLE],
             }
         }
 
-        fn block(&mut self, handle: Handle) -> io::Result<&mut Box<dyn Block>> {
-            Ok(self
-                .blocks
+        fn invalid_handle_err(handle: Handle) -> Error {
+            Error::custom(format!("invalid handle {handle}"))
+        }
+
+        fn block_mut(&mut self, handle: Handle) -> io::Result<&mut Box<dyn Block>> {
+            self.handle_values
                 .get_mut(&handle)
-                .ok_or_else(|| Error::custom(format!("invalid handle {handle}")))?)
+                .ok_or_else(|| Self::invalid_handle_err(handle))?
+                .block_mut()
+        }
+
+        fn access_block_mut<T, F: FnOnce(&mut Box<dyn Block>) -> io::Result<T>>(
+            &self,
+            handle: Handle,
+            cb: F,
+        ) -> io::Result<T> {
+            self.handle_values
+                .get(&handle)
+                .ok_or_else(|| Self::invalid_handle_err(handle))?
+                .access_block_mut(cb)
         }
 
         fn try_borrow_block<T, F: FnOnce(&mut Box<dyn Block>) -> io::Result<T>>(
@@ -219,28 +298,32 @@ mod private {
                 .unclaimed_handles
                 .last()
                 .ok_or_else(|| Error::custom("no handles available"))?;
-            let block = self.blocks.get_mut(handle).ok_or_else(|| {
-                let err = io::Error::new(
-                    io::ErrorKind::Other,
+            let handle_value = self.handle_values.get_mut(handle).ok_or_else(|| {
+                let err = Error::custom(
                     "logic error: can't find block associated with handle",
                 );
                 error!("{err}");
                 err
             })?;
+            let block = handle_value.block_mut()?;
             cb(block)
         }
 
         fn insert(&mut self, block: Box<dyn Block>) {
             let handle = self.next_handle;
             self.next_handle += 1;
-            self.blocks.insert(handle, block);
+            self.handle_values
+                .insert(handle, HandleValue::new_file(block));
             self.unclaimed_handles.push(handle);
         }
 
-        fn insert_then_get_mut(&mut self, block: Box<dyn Block>) -> &mut Box<dyn Block> {
+        fn insert_then_get_mut(
+            &mut self,
+            block: Box<dyn Block>,
+        ) -> &mut Box<dyn Block> {
             self.insert(block);
             let handle = self.unclaimed_handles.last().unwrap();
-            self.blocks.get_mut(handle).unwrap()
+            self.block_mut(*handle).unwrap()
         }
 
         fn take_handle(&mut self) -> Option<Handle> {
@@ -251,21 +334,25 @@ mod private {
             self.unclaimed_handles.push(handle);
             while self.unclaimed_handles.len() > Self::UNCLAIMED_HANDLE_LIMIT {
                 let handle = self.unclaimed_handles.pop().unwrap();
-                self.blocks.remove(&handle);
+                self.handle_values.remove(&handle);
             }
         }
 
-        fn incr_lookup_count(&self) -> u64 {
-            self.lookup_count.fetch_add(1, Ordering::SeqCst)
+        fn incr_lookup_count(&mut self) -> u64 {
+            let prev = self.lookup_count;
+            self.lookup_count += 1;
+            prev
         }
 
-        fn decr_lookup_count(&self, count: u64) -> u64 {
-            self.lookup_count.fetch_sub(count, Ordering::SeqCst)
+        fn decr_lookup_count(&mut self, count: u64) -> u64 {
+            let prev = self.lookup_count;
+            self.lookup_count -= count;
+            prev
         }
     }
 
-    type InodeTable = HashMap<Inode, InodeTableValue>;
-    type InodeTableEntry<'a> = hash_map::Entry<'a, Inode, InodeTableValue>;
+    type InodeTable = HashMap<Inode, RwLock<InodeTableValue>>;
+    type InodeTableEntry<'a> = hash_map::Entry<'a, Inode, RwLock<InodeTableValue>>;
 
     /// Structure for metadata about a blocktree.
     #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
@@ -379,8 +466,8 @@ mod private {
             authorizer: A,
         ) -> Result<Blocktree<C, A>> {
             let mut inodes = HashMap::with_capacity(1);
-            inodes.insert(SpecInodes::Sb.into(), InodeTableValue::new(sb_block));
-            inodes.insert(SpecInodes::RootDir.into(), InodeTableValue::new(root_block));
+            inodes.insert(SpecInodes::Sb.into(), RwLock::new(InodeTableValue::new(sb_block)));
+            inodes.insert(SpecInodes::RootDir.into(), RwLock::new(InodeTableValue::new(root_block)));
             Ok(Blocktree {
                 path: btdir,
                 inodes: RwLock::new(inodes),
@@ -462,7 +549,7 @@ mod private {
             let mut inodes = self
                 .inodes
                 .write()
-                .map_err(|err| Error::custom(err.to_string()))?;
+                .err_to_string()?;
             let entry = inodes.entry(inode);
             cb(entry)
         }
@@ -475,9 +562,9 @@ mod private {
             let inodes = self
                 .inodes
                 .read()
-                .map_err(|err| Error::custom(err.to_string()))?;
-            let value = inodes.get(&inode).ok_or_else(|| Error::NotOpen(inode))?;
-            cb(value)
+                .err_to_string()?;
+            let value = inodes.get(&inode).ok_or_else(|| Error::NotOpen(inode))?.read().err_to_string()?;
+            cb(&value)
         }
 
         fn access_value_mut<T, F: FnOnce(&mut InodeTableValue) -> io::Result<T>>(
@@ -485,24 +572,21 @@ mod private {
             inode: Inode,
             cb: F,
         ) -> io::Result<T> {
-            self.access_entry(inode, |mut entry| match &mut entry {
-                InodeTableEntry::Vacant(_) => Err(Error::NotOpen(inode).into()),
-                InodeTableEntry::Occupied(entry) => cb(entry.get_mut()),
-            })
+            let inodes = self
+                .inodes
+                .read()
+                .err_to_string()?;
+            let mut value = inodes.get(&inode).ok_or_else(|| Error::NotOpen(inode))?.write().err_to_string()?;
+            cb(&mut value)
         }
 
-        fn access_block<T, F: FnOnce(&mut Box<dyn Block>) -> io::Result<T>>(
+        fn access_block_mut<T, F: FnOnce(&mut Box<dyn Block>) -> io::Result<T>>(
             &self,
             inode: Inode,
             handle: Handle,
             cb: F,
         ) -> io::Result<T> {
-            self.access_value_mut(inode, |value| {
-                cb(value
-                    .blocks
-                    .get_mut(&handle)
-                    .ok_or(Error::InvalidHandle { handle, inode })?)
-            })
+            self.access_value(inode, |value| value.access_block_mut(handle, cb))
         }
 
         fn access_meta<T, F: FnOnce(&BlockMeta) -> io::Result<T>>(
@@ -511,14 +595,14 @@ mod private {
             cb: F,
         ) -> io::Result<T> {
             self.access_value(inode, |value| {
-                let block = value
-                    .blocks
+                let handle_value = value
+                    .handle_values
                     .values()
                     .next()
                     .ok_or_else(|| 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.
-                cb(block.meta())
+                handle_value.access_block(|block| cb(block.meta()))
             })
         }
 
@@ -529,14 +613,14 @@ mod private {
         ) -> io::Result<T> {
             self.access_value_mut(inode, |value| {
                 let block = match value.unclaimed_handles.last() {
-                    Some(handle) => value.blocks.get_mut(handle).unwrap(),
+                    Some(handle) => value.handle_values.get_mut(handle).unwrap().block_mut()?,
                     None => {
-                        let block = value
-                            .blocks
+                        let block_path = value
+                            .handle_values
                             .values()
                             .next()
-                            .ok_or_else(|| Error::NotOpen(inode))?;
-                        let block_path = block.meta_body().path.clone();
+                            .ok_or_else(|| 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)?;
                         value.insert_then_get_mut(block)
@@ -555,7 +639,7 @@ mod private {
                 let handle = value
                     .take_handle()
                     .ok_or_else(|| Error::NoHandlesAvailable(inode))?;
-                let block = value.block(handle)?;
+                let block = value.block_mut(handle)?;
                 let result = cb(handle, block);
                 if result.is_err() {
                     value.give_handle(handle);
@@ -566,7 +650,7 @@ mod private {
 
         fn give_handle(&self, inode: Inode, handle: Handle) -> io::Result<()> {
             self.access_value_mut(inode, |value| {
-                let block = value.block(handle)?;
+                let block = value.block_mut(handle)?;
                 // Be kind, rewind.
                 block.seek(SeekFrom::Start(0))?;
                 value.give_handle(handle);
@@ -586,11 +670,11 @@ mod private {
                         Self::open_block(&self.path, inode, self.creds.clone(), block_path)?;
                     let mut value = InodeTableValue::new(block);
                     let result = cb(&mut value);
-                    entry.insert(value);
+                    entry.insert(RwLock::new(value));
                     result
                 }
                 InodeTableEntry::Occupied(mut entry) => {
-                    let value = entry.get_mut();
+                    let value = entry.get_mut().get_mut().err_to_string()?;
                     if value.unclaimed_handles.is_empty() {
                         let block =
                             Self::open_block(&self.path, inode, self.creds.clone(), block_path)?;
@@ -614,23 +698,20 @@ mod private {
         }
 
         fn inode_forget(&self, inode: Inode, count: u64) -> io::Result<()> {
+            let mut inodes = self.inodes.write().err_to_string()?;
             let prev = {
-                let inodes = self
-                    .inodes
-                    .read()
-                    .map_err(|err| Error::custom(err.to_string()))?;
-                let value = match inodes.get(&inode) {
-                    Some(value) => value,
-                    None => return Ok(()),
+                let inode_lock = match inodes.get_mut(&inode) {
+                    Some(inode_lock) => inode_lock,
+                    None => {
+                        warn!("an attempt was made to forget non-existent inode {inode}");
+                        return Ok(())
+                    }
                 };
+                let mut value = inode_lock.write().err_to_string()?;
                 value.decr_lookup_count(count)
             };
             if 1 == prev {
-                let mut inodes = self
-                    .inodes
-                    .write()
-                    .map_err(|err| Error::custom(err.to_string()))?;
-                inodes.remove(&inode);
+                inodes.remove(&inode); 
             }
             Ok(())
         }
@@ -730,7 +811,9 @@ mod private {
             ctx: &Context,
             inode: Self::Inode,
             flags: u32,
-            fuse_flags: u32,
+            // This is the second field of the `fuse_open_in` struct, which is currently unused
+            // by the kernel. (https://man7.org/linux/man-pages/man4/fuse.4.html)
+            _fuse_flags: u32,
         ) -> io::Result<(Option<Self::Handle>, OpenOptions)> {
             debug!("Blocktree::open called on inode {inode}");
             let flags: i32 = flags.try_into().box_err()?;
@@ -740,17 +823,12 @@ mod private {
             if flags & libc::O_CLOEXEC != 0 {
                 return Self::unsupported_flag("O_CLOEXEC");
             }
+            if flags & libc::O_DIRECTORY != 0 {
+                return Self::unsupported_flag("O_DIRECTORY");
+            }
             let handle = self.take_block_if_ok(inode, |handle, block| {
-                let mode = block
-                    .mut_meta_body()
-                    .access_secrets(|secrets| Ok(secrets.mode))?;
-                let file_type = FileType::from_value(mode)?;
-                if (flags & libc::O_DIRECTORY) != 0 && FileType::Dir != file_type {
-                    return Err(io::Error::from_raw_os_error(libc::ENOTDIR));
-                }
                 let ctx = AuthzContext::new(ctx, block.meta());
-                let read_mask = libc::O_RDONLY | libc::O_RDWR;
-                if read_mask & flags != 0 {
+                if flags == libc::O_RDONLY || (flags & libc::O_RDWR) != 0 {
                     self.authorizer.can_read(&ctx)?;
                 }
                 let write_mask = libc::O_WRONLY | libc::O_RDWR;
@@ -774,7 +852,7 @@ mod private {
         ) -> io::Result<()> {
             debug!("Blocktree::release called on inode {inode}");
             if flush {
-                self.access_block(inode, handle, |block| block.flush())?;
+                self.access_block_mut(inode, handle, |block| block.flush())?;
             };
             self.give_handle(inode, handle)
         }
@@ -796,7 +874,7 @@ mod private {
                 })?;
                 let handle = value.next_handle;
                 value.next_handle += 1;
-                value.dirs.insert(handle, dir);
+                value.handle_values.insert(handle, HandleValue::new_dir(dir));
                 Ok(handle)
             })?;
             Ok((Some(handle), OpenOptions::empty()))
@@ -811,7 +889,7 @@ mod private {
         ) -> io::Result<()> {
             debug!("Blocktree::releasedir called for inode {inode}");
             self.access_value_mut(inode, |value| {
-                value.dirs.remove(&handle);
+                value.handle_values.remove(&handle);
                 Ok(())
             })
         }
@@ -851,7 +929,7 @@ mod private {
             let (handle, attr) =
                 self.open_then_take_handle(inode, block_path, |handle, value| {
                     value.incr_lookup_count();
-                    let block = value.block(handle)?;
+                    let block = value.block_mut(handle)?;
                     Ok(block.mut_meta_body().access_secrets(|secrets| {
                         secrets.inode = inode;
                         secrets.mode = args.mode;
@@ -887,12 +965,17 @@ mod private {
             offset: u64,
             lock_owner: Option<u64>,
             delayed_write: bool,
+            // `flags` and `fuse_flags` are the arguments that were passed to `open` when this
+            // handle was returned.
             flags: u32,
-            fuse_flags: u32,
+            _fuse_flags: u32,
         ) -> io::Result<usize> {
             debug!("Blocktree::write called called on inode {inode}");
+            if flags as libc::c_int == libc::O_RDONLY {
+                return Err(io::Error::new(io::ErrorKind::PermissionDenied, "file is readonly"));
+            }
             let mut size: usize = size.try_into().box_err()?;
-            self.access_block(inode, handle, |block| {
+            self.access_block_mut(inode, handle, |block| {
                 let mut buf = [0u8; crate::SECTOR_SZ_DEFAULT];
                 let mut written = 0;
                 while size > 0 {
@@ -934,7 +1017,7 @@ mod private {
             lock_owner: u64,
         ) -> io::Result<()> {
             debug!("Blocktree::flush called for inode {inode}");
-            self.access_block(inode, handle, |block| block.flush())
+            self.access_block_mut(inode, handle, |block| block.flush())
         }
 
         fn read(
@@ -950,7 +1033,7 @@ mod private {
         ) -> io::Result<usize> {
             debug!("Blocktree::read called on inode {inode}");
             let mut size: usize = size.try_into().box_err()?;
-            self.access_block(inode, handle, |block| {
+            self.access_block_mut(inode, handle, |block| {
                 let mut buf = [0u8; crate::SECTOR_SZ_DEFAULT];
                 let mut read = 0;
                 while size > 0 {
@@ -996,9 +1079,13 @@ mod private {
             ) -> io::Result<usize>,
         ) -> io::Result<()> {
             debug!("Blocktree::readdir called on inode {inode}");
+            let mut size: usize = size.try_into().box_err()?;
             self.access_value(inode, |value| {
-                let dir = value.dirs.get(&handle)
-                    .ok_or(Error::InvalidHandle { handle, inode })?;
+                let dir = value
+                    .handle_values
+                    .get(&handle)
+                    .ok_or(Error::InvalidHandle { handle, inode })?
+                    .directory()?;
                 let mut index: u64 = 0;
                 for (name, entry) in dir.entries() {
                     index += 1;
@@ -1015,7 +1102,10 @@ mod private {
                         type_: entry.kind() as u32,
                         name: name.as_bytes(),
                     };
-                    add_entry(dir_entry)?;
+                    size -= add_entry(dir_entry)?;
+                    if size <= 0 {
+                        break;
+                    }
                 }
                 Ok(())
             })
@@ -1050,7 +1140,7 @@ mod private {
         ) -> io::Result<u64> {
             debug!("Blocktree::lseek called for inode {inode}");
             let seek_from = SeekFrom::whence_offset(whence, offset)?;
-            self.access_block(inode, handle, |block| block.seek(seek_from))
+            self.access_block_mut(inode, handle, |block| block.seek(seek_from))
         }
 
         //////////////////////////////////
@@ -1334,10 +1424,13 @@ mod private {
 
 #[cfg(test)]
 mod tests {
-    use std::ffi::CString;
     use fuse_backend_rs::{
         abi::fuse_abi::CreateIn,
-        api::filesystem::{Context, FileSystem, FsOptions}
+        api::filesystem::{Context, FileSystem, FsOptions},
+    };
+    use std::{
+        io,
+        ffi::CString
     };
     use tempdir::TempDir;
 
@@ -1535,7 +1628,9 @@ mod tests {
         }
 
         fn context(&self) -> Context {
-            let (stat, ..) = self.bt.getattr(&Default::default(), SpecInodes::RootDir.into(), None)
+            let (stat, ..) = self
+                .bt
+                .getattr(&Default::default(), SpecInodes::RootDir.into(), None)
                 .expect("getattr failed");
             Context {
                 uid: stat.st_uid,
@@ -1552,13 +1647,19 @@ mod tests {
         let bt = &case.bt;
         let ctx = case.context();
         let name = CString::new("README.md").unwrap();
+        let flags = libc::O_RDWR as u32;
 
         let (entry, handle, ..) = bt
             .create(
                 &ctx,
                 SpecInodes::RootDir.into(),
                 name.as_c_str(),
-                CreateIn { mode: libc::S_IFREG | 0o644, umask: 0, flags: 0, fuse_flags: 0 },
+                CreateIn {
+                    mode: libc::S_IFREG | 0o644,
+                    umask: 0,
+                    flags,
+                    fuse_flags: 0,
+                },
             )
             .expect("failed to create file");
         let inode = entry.inode;
@@ -1576,27 +1677,17 @@ mod tests {
                 0,
                 None,
                 false,
-                0,
+                flags,
                 0,
             )
             .expect("write failed");
         assert_eq!(LEN, written);
 
-        bt.lseek(&ctx, inode, handle, 0, 0)
-            .expect("lseek failed");
+        bt.lseek(&ctx, inode, handle, 0, 0).expect("lseek failed");
 
         let mut actual = BtCursor::new([0u8; LEN]);
         let read = bt
-            .read(
-                &ctx,
-                inode,
-                handle,
-                &mut actual,
-                LEN as u32,
-                0,
-                None,
-                0,
-            )
+            .read(&ctx, inode, handle, &mut actual, LEN as u32, 0, None, flags)
             .expect("failed to read");
         assert_eq!(LEN, read);
 
@@ -1636,6 +1727,7 @@ mod tests {
         let case = BtTestCase::new_empty();
         let bt = &case.bt;
         let ctx = case.context();
+        let flags = libc::O_RDWR as u32;
 
         {
             let (entry, handle, ..) = bt
@@ -1643,7 +1735,12 @@ mod tests {
                     &ctx,
                     SpecInodes::RootDir.into(),
                     name.as_c_str(),
-                    CreateIn { mode: libc::S_IFREG | 0o644, umask: 0, flags: 0, fuse_flags: 0 },
+                    CreateIn {
+                        mode: libc::S_IFREG | 0o644,
+                        umask: 0,
+                        flags,
+                        fuse_flags: 0,
+                    },
                 )
                 .expect("failed to create file");
             let inode = entry.inode;
@@ -1662,7 +1759,7 @@ mod tests {
                     0,
                     None,
                     false,
-                    0,
+                    flags,
                     0,
                 )
                 .expect("write failed");
@@ -1695,10 +1792,63 @@ mod tests {
                 EXPECTED.len() as u32,
                 0,
                 None,
-                0,
+                flags,
             )
             .expect("read failed");
 
         assert_eq!(EXPECTED, actual.into_inner().as_slice())
     }
+
+    /// Tests that an error is returned by the `Blocktree::write` method if the file was opened
+    /// read-only.
+    #[test]
+    fn open_read_only_write_is_error() {
+        let name = CString::new("books.ods").unwrap();
+        let case = BtTestCase::new_empty();
+        let bt = &case.bt;
+        let ctx = case.context();
+
+        let (entry, handle, ..) = bt
+            .create(
+                &ctx,
+                SpecInodes::RootDir.into(),
+                name.as_c_str(),
+                CreateIn {
+                    mode: libc::S_IFREG | 0o644,
+                    umask: 0,
+                    flags: 0,
+                    fuse_flags: 0,
+                },
+            )
+            .expect("failed to create file");
+        let inode = entry.inode;
+        let handle = handle.unwrap();
+
+        bt.release(&ctx, inode, 0, handle, false, false, None)
+            .expect("release failed");
+
+        let flags = libc::O_RDONLY as u32;
+        let (handle, ..) = bt
+            .open(&ctx, inode, flags, 0)
+            .expect("open failed");
+        let handle = handle.unwrap();
+
+        const LEN: usize = 32;
+        let mut reader = BtCursor::new([1u8; LEN]);
+        let result = bt.write(
+            &ctx,
+            inode,
+            handle,
+            &mut reader,
+            LEN.try_into().unwrap(),
+            0,
+            None,
+            false,
+            flags,
+            0,
+        );
+
+        let err = result.err().unwrap();
+        assert_eq!(io::ErrorKind::PermissionDenied, err.kind());
+    }
 }

+ 16 - 2
crates/btlib/src/lib.rs

@@ -137,12 +137,24 @@ trait BoxInIoErr<T> {
     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> {
+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>;
@@ -933,7 +945,9 @@ impl Directory {
     }
 
     pub fn entries(&self) -> impl Iterator<Item = (&str, &DirEntry)> {
-        self.entries.iter().map(|(name, entry)| (name.as_str(), entry))
+        self.entries
+            .iter()
+            .map(|(name, entry)| (name.as_str(), entry))
     }
 }