Răsfoiți Sursa

Implemented mkdir and rmdir in Blocktree.

Matthew Carr 2 ani în urmă
părinte
comite
c0c2f8885f
3 a modificat fișierele cu 266 adăugiri și 98 ștergeri
  1. 85 4
      crates/btfuse/src/main.rs
  2. 152 94
      crates/btlib/src/blocktree.rs
  3. 29 0
      crates/btlib/src/lib.rs

+ 85 - 4
crates/btfuse/src/main.rs

@@ -180,8 +180,8 @@ mod test {
     use std::{
         ffi::{OsStr, OsString},
         fs::{
-            hard_link, metadata, read, read_dir, remove_file, set_permissions, write, Permissions,
-            ReadDir,
+            create_dir, hard_link, metadata, read, read_dir, remove_dir, remove_file,
+            set_permissions, write, Permissions, ReadDir,
         },
         os::unix::fs::PermissionsExt,
         sync::mpsc::{channel, Receiver},
@@ -283,6 +283,10 @@ mod test {
         }
 
         fn unmount_and_wait(&mut self) {
+            // If `handle` has already been taken that means `wait` has already been called. If
+            // this thread called `wait` and subsequently became unblocked, we know that the FUSE
+            // thread halted, which only happens when the file system is unmounted. Hence
+            // we don't have to unmount it, nor wait for the FUSE thread.
             if self.handle.is_none() {
                 return;
             }
@@ -307,7 +311,6 @@ mod test {
         env_logger::Builder::from_default_env().btformat().init();
         let mut case = TestCase::new();
         case.wait();
-
     }
 
     /// Tests if the file system can be mount then unmounted successfully.
@@ -399,7 +402,7 @@ mod test {
     }
 
     #[test]
-    fn set_permissions_bits() {
+    fn set_mode_bits() {
         const EXPECTED: u32 = libc::S_IFREG | 0o777;
         let mut case = TestCase::new();
         let file_path = case.mnt_path().join("bagobits");
@@ -419,4 +422,82 @@ mod test {
             .mode();
         assert_eq!(EXPECTED, actual);
     }
+
+    #[test]
+    fn create_directory() {
+        const EXPECTED: &str = "etc";
+        let mut case = TestCase::new();
+        let mnt_path = case.mnt_path();
+        let dir_path = mnt_path.join(EXPECTED);
+
+        create_dir(&dir_path).expect("create_dir failed");
+
+        let actual = file_names(read_dir(mnt_path).expect("read_dir failed"));
+        assert!(actual.eq([EXPECTED]));
+    }
+
+    #[test]
+    fn create_file_under_new_directory() {
+        const DIR_NAME: &str = "etc";
+        const FILE_NAME: &str = "file";
+        let mut case = TestCase::new();
+        let mnt_path = case.mnt_path();
+        let dir_path = mnt_path.join(DIR_NAME);
+        let file_path = dir_path.join(FILE_NAME);
+
+        create_dir(&dir_path).expect("create_dir failed");
+        write(&file_path, []).expect("write failed");
+
+        let actual = file_names(read_dir(dir_path).expect("read_dir failed"));
+        assert!(actual.eq([FILE_NAME]));
+    }
+
+    #[test]
+    fn create_then_remove_directory() {
+        const DIR_NAME: &str = "etc";
+        let mut case = TestCase::new();
+        let mnt_path = case.mnt_path();
+        let dir_path = mnt_path.join(DIR_NAME);
+
+        create_dir(&dir_path).expect("create_dir failed");
+        remove_dir(&dir_path).expect("remove_dir failed");
+
+        let actual = file_names(read_dir(&mnt_path).expect("read_dir failed"));
+        const EMPTY: [&str; 0] = [""; 0];
+        assert!(actual.eq(EMPTY));
+    }
+
+    #[test]
+    fn read_only_dir_cant_create_subdir() {
+        const DIR_NAME: &str = "etc";
+        let mut case = TestCase::new();
+        let dir_path = case.mnt_path().join(DIR_NAME);
+        create_dir(&dir_path).expect("create_dir failed");
+        set_permissions(&dir_path, Permissions::from_mode(libc::S_IFDIR | 0o444))
+            .expect("set_permissions failed");
+
+        let result = create_dir(dir_path.join("sub"));
+
+        let err = result.err().expect("create_dir returned `Ok`");
+        let os_err = err.raw_os_error().expect("raw_os_error was empty");
+        assert_eq!(os_err, libc::EACCES);
+    }
+
+    #[test]
+    fn read_only_dir_cant_remove_subdir() {
+        const DIR_NAME: &str = "etc";
+        let mut case = TestCase::new();
+        let dir_path = case.mnt_path().join(DIR_NAME);
+        let sub_path = dir_path.join("sub");
+        create_dir(&dir_path).expect("create_dir failed");
+        create_dir(&sub_path).expect("create_dir failed");
+        set_permissions(&dir_path, Permissions::from_mode(libc::S_IFDIR | 0o444))
+            .expect("set_permissions failed");
+
+        let result = remove_dir(&sub_path);
+
+        let err = result.err().expect("remove_dir returned `Ok`");
+        let os_err = err.raw_os_error().expect("raw_os_error was empty");
+        assert_eq!(os_err, libc::EACCES);
+    }
 }

+ 152 - 94
crates/btlib/src/blocktree.rs

@@ -5,7 +5,8 @@ mod private {
     use fuse_backend_rs::{
         abi::fuse_abi::{stat64, statvfs64, CreateIn},
         api::filesystem::{
-            Context, DirEntry as FuseDirEntry, Entry, FileSystem, FsOptions, OpenOptions, SetattrValid,
+            Context, DirEntry as FuseDirEntry, Entry, FileSystem, FsOptions, OpenOptions,
+            SetattrValid,
         },
     };
     use log::{debug, error, warn};
@@ -151,6 +152,22 @@ mod private {
         fn can_exec<'a>(&self, ctx: &AuthzContext<'a>) -> io::Result<()>;
     }
 
+    trait AuthorizerExt: Authorizer {
+        fn read_allowed(&self, ctx: &Context, meta: &BlockMeta) -> io::Result<()> {
+            Authorizer::can_read(self, &AuthzContext::new(ctx, meta))
+        }
+
+        fn write_allowed(&self, ctx: &Context, meta: &BlockMeta) -> io::Result<()> {
+            Authorizer::can_write(self, &AuthzContext::new(ctx, meta))
+        }
+
+        fn exec_allowed(&self, ctx: &Context, meta: &BlockMeta) -> io::Result<()> {
+            Authorizer::can_exec(self, &AuthzContext::new(ctx, meta))
+        }
+    }
+
+    impl<T: Authorizer> AuthorizerExt for T {}
+
     /// A particularly simple authorizer that just looks at the mode bits in the block metadata
     /// to make authorization decisions.
     pub struct ModeAuthorizer {}
@@ -163,10 +180,17 @@ mod private {
                 Err(io::Error::new(io::ErrorKind::PermissionDenied, denied_msg))
             }
         }
+
+        fn user_is_root(ctx: &AuthzContext<'_>) -> bool {
+            ctx.uid == 0
+        }
     }
 
     impl Authorizer for ModeAuthorizer {
         fn can_read<'a>(&self, ctx: &AuthzContext<'a>) -> io::Result<()> {
+            if Self::user_is_root(ctx) {
+                return Ok(());
+            }
             let secrets = ctx.meta.body.secrets()?;
             let mask = (libc::S_IRUSR * (secrets.uid == ctx.uid) as u32)
                 | (libc::S_IRGRP * (secrets.gid == ctx.gid) as u32)
@@ -175,6 +199,9 @@ mod private {
         }
 
         fn can_write<'a>(&self, ctx: &AuthzContext<'a>) -> io::Result<()> {
+            if Self::user_is_root(ctx) {
+                return Ok(());
+            }
             let secrets = ctx.meta.body.secrets()?;
             let mask = (libc::S_IWUSR * (secrets.uid == ctx.uid) as u32)
                 | (libc::S_IWGRP * (secrets.gid == ctx.gid) as u32)
@@ -183,6 +210,9 @@ mod private {
         }
 
         fn can_exec<'a>(&self, ctx: &AuthzContext<'a>) -> io::Result<()> {
+            if Self::user_is_root(ctx) {
+                return Ok(());
+            }
             let secrets = ctx.meta.body.secrets()?;
             let mask = (libc::S_IXUSR * (secrets.uid == ctx.uid) as u32)
                 | (libc::S_IXGRP * (secrets.gid == ctx.gid) as u32)
@@ -672,16 +702,6 @@ mod private {
             })
         }
 
-        fn give_handle(&self, inode: Inode, handle: Handle) -> io::Result<()> {
-            self.access_value_mut(inode, |value| {
-                let block = value.block_mut(handle)?;
-                // Be kind, rewind.
-                block.seek(SeekFrom::Start(0))?;
-                value.give_handle(handle);
-                Ok(())
-            })
-        }
-
         fn open_value<T, F: FnOnce(&mut InodeTableValue) -> io::Result<T>>(
             &self,
             inode: Inode,
@@ -781,6 +801,17 @@ mod private {
                 format!("unsupported flag: {flag}"),
             ))
         }
+
+        fn fuse_entry(&self, inode: Inode, stat: stat64) -> Entry {
+            Entry {
+                generation: self.generation,
+                inode,
+                attr: stat,
+                attr_flags: 0,
+                attr_timeout: self.attr_timeout(),
+                entry_timeout: self.entry_timeout(),
+            }
+        }
     }
 
     unsafe impl<C: Sync, A: Sync> Sync for Blocktree<C, A> {}
@@ -802,10 +833,8 @@ mod private {
             debug!("Blocktree::lookup called on parent {parent}");
             let name = name.to_str().box_err()?;
             let (dir, block_path) = self.borrow_block(parent, |block| {
-                self.authorizer
-                    .can_exec(&AuthzContext::new(ctx, block.meta()))?;
-                block.seek(SeekFrom::Start(0))?;
-                let dir: Directory = read_from(block)?;
+                self.authorizer.exec_allowed(ctx, block.meta())?;
+                let dir = block.read_dir()?;
                 let path = block.meta_body().path.to_owned();
                 Ok((dir, path))
             })?;
@@ -821,14 +850,7 @@ mod private {
                 value.incr_lookup_count();
                 Ok(stat)
             })?;
-            Ok(Entry {
-                inode,
-                generation: self.generation,
-                attr: stat,
-                attr_flags: 0,
-                attr_timeout: self.attr_timeout(),
-                entry_timeout: self.entry_timeout(),
-            })
+            Ok(self.fuse_entry(inode, stat))
         }
 
         fn open(
@@ -876,10 +898,17 @@ mod private {
             _lock_owner: Option<u64>,
         ) -> io::Result<()> {
             debug!("Blocktree::release called on inode {inode}");
-            if flush {
-                self.access_block_mut(inode, handle, |block| block.flush())?;
-            };
-            self.give_handle(inode, handle)
+            self.access_value_mut(inode, |value| {
+                value.access_block_mut(handle, |block| {
+                    if flush {
+                        block.flush()?;
+                    }
+                    // Be kind, rewind.
+                    block.seek(SeekFrom::Start(0))
+                })?;
+                value.give_handle(handle);
+                Ok(())
+            })
         }
 
         fn opendir(
@@ -891,8 +920,7 @@ mod private {
             debug!("Blocktree::opendir called on inode {inode}");
             let handle = self.access_value_mut(inode, |value| {
                 value.borrow_block(|block| {
-                    let ctx = AuthzContext::new(ctx, block.meta());
-                    self.authorizer.can_exec(&ctx)?;
+                    self.authorizer.exec_allowed(ctx, block.meta())?;
                     Ok(())
                 })?;
                 let handle = value.take_handle()?;
@@ -935,20 +963,17 @@ mod private {
 
             // Add a directory entry to the parent for the new inode.
             let mut block_path = self.borrow_block(parent, |block| {
-                let ctx = AuthzContext::new(ctx, block.meta());
-                self.authorizer.can_write(&ctx)?;
+                self.authorizer.write_allowed(ctx, block.meta())?;
 
-                block.seek(SeekFrom::Start(0))?;
-                let mut dir: Directory = read_from(block).box_err()?;
+                let mut dir = block.read_dir()?;
                 dir.add_file(name.clone(), inode)?;
-                block.seek(SeekFrom::Start(0))?;
-                write_to(&dir, block).box_err()?;
-                block.flush()?;
+                block.write_dir(&dir)?;
+
                 Ok(block.meta_body().path.clone())
             })?;
             block_path.push_component(name);
 
-            let (handle, attr) =
+            let (handle, stat) =
                 self.open_then_take_handle(inode, block_path, |handle, value| {
                     let block = value.block_mut(handle)?;
                     Ok(block.mut_meta_body().access_secrets(|secrets| {
@@ -961,19 +986,15 @@ mod private {
                         secrets.ctime = now;
                         secrets.mtime = now;
                         secrets.nlink = 1;
-                        Ok((handle, secrets.attr()))
+                        Ok((handle, secrets.stat()))
                     })?)
                 })?;
 
-            let entry = Entry {
-                inode,
-                generation: self.generation,
-                attr: attr.into(),
-                attr_flags: 0,
-                attr_timeout: self.attr_timeout(),
-                entry_timeout: self.entry_timeout(),
-            };
-            Ok((entry, Some(handle), OpenOptions::empty()))
+            Ok((
+                self.fuse_entry(inode, stat),
+                Some(handle),
+                OpenOptions::empty(),
+            ))
         }
 
         fn write(
@@ -1147,8 +1168,7 @@ mod private {
             _handle: Option<Self::Handle>,
         ) -> io::Result<(stat64, Duration)> {
             debug!("Blocktree::getattr called for inode {inode}");
-            let mut stat = self.access_meta(inode, |meta| Ok(meta.body.secrets()?.stat()))?;
-            stat.st_ino = inode;
+            let stat = self.access_meta(inode, |meta| Ok(meta.body.secrets()?.stat()))?;
             Ok((stat, self.attr_timeout()))
         }
 
@@ -1176,11 +1196,9 @@ mod private {
             debug!("Blocktree::unlink called on parent {parent}");
             let name = name.to_str().box_err()?;
             let (block_path, inode) = self.borrow_block(parent, |block| {
-                let ctx = AuthzContext::new(ctx, block.meta());
-                self.authorizer.can_write(&ctx)?;
+                self.authorizer.write_allowed(ctx, block.meta())?;
 
-                block.seek(SeekFrom::Start(0))?;
-                let mut dir: Directory = read_from(block)?;
+                let mut dir = block.read_dir()?;
                 let entry = match dir.entries.remove(name) {
                     None => return Err(io::Error::from_raw_os_error(libc::ENOENT)),
                     Some(entry) => entry,
@@ -1191,8 +1209,7 @@ mod private {
                         "no inode associated with the given name",
                     )
                 })?;
-                block.seek(SeekFrom::Start(0))?;
-                write_to(&dir, block)?;
+                block.write_dir(&dir)?;
 
                 let mut block_path = block.meta_body().path.clone();
                 block_path.push_component(name.to_owned());
@@ -1222,44 +1239,34 @@ mod private {
             debug!("Blocktree::link called for inode {inode}");
             let newname = newname.to_str().box_err()?;
             self.borrow_block(newparent, |block| {
-                let ctx = AuthzContext::new(ctx, block.meta());
-                self.authorizer.can_write(&ctx)?;
+                self.authorizer.write_allowed(ctx, block.meta())?;
 
-                block.seek(SeekFrom::Start(0))?;
-                let mut dir: Directory = read_from(block)?;
+                let mut dir = block.read_dir()?;
                 if dir.entries.contains_key(newname) {
                     return Err(io::Error::from_raw_os_error(libc::EEXIST));
                 }
 
-                let (file_type, attr) = self.access_value_mut(inode, |value| {
-                    let (file_type, attr) = value.borrow_block(|block| {
+                let (file_type, stat) = self.access_value_mut(inode, |value| {
+                    let (file_type, stat) = value.borrow_block(|block| {
                         let meta = block.mut_meta_body();
-                        let (mode, attr) = meta.access_secrets(|secrets| {
+                        let (mode, stat) = meta.access_secrets(|secrets| {
                             secrets.nlink += 1;
-                            Ok((secrets.mode, secrets.attr()))
+                            Ok((secrets.mode, secrets.stat()))
                         })?;
                         let file_type = FileType::from_value(mode)?;
                         block.flush_meta()?;
-                        Ok((file_type, attr))
+                        Ok((file_type, stat))
                     })?;
                     value.incr_lookup_count();
-                    Ok((file_type, attr))
+                    Ok((file_type, stat))
                 })?;
                 let entry = match file_type {
                     FileType::Reg => DirEntry::File(BlockRecord::new(inode)),
                     FileType::Dir => DirEntry::Directory(BlockRecord::new(inode)),
                 };
                 dir.entries.insert(newname.to_owned(), entry);
-                block.seek(SeekFrom::Start(0))?;
-                write_to(&dir, block)?;
-                Ok(Entry {
-                    inode,
-                    generation: self.generation,
-                    attr: attr.into(),
-                    attr_flags: 0,
-                    attr_timeout: self.attr_timeout(),
-                    entry_timeout: self.entry_timeout(),
-                })
+                block.write_dir(&dir)?;
+                Ok(self.fuse_entry(inode, stat))
             })
         }
 
@@ -1273,8 +1280,7 @@ mod private {
         ) -> io::Result<(stat64, Duration)> {
             debug!("Blocktree::setattr called for inode {inode}");
             let stat = self.borrow_block(inode, |block| {
-                let ctx = AuthzContext::new(ctx, block.meta());
-                self.authorizer.can_write(&ctx)?;
+                self.authorizer.write_allowed(ctx, block.meta())?;
 
                 let stat = block.mut_meta_body().access_secrets(|secrets| {
                     if valid.intersects(SetattrValid::MODE) {
@@ -1321,6 +1327,65 @@ mod private {
             Ok((stat, self.attr_timeout()))
         }
 
+        fn mkdir(
+            &self,
+            ctx: &Context,
+            parent: Self::Inode,
+            name: &CStr,
+            mode: u32,
+            umask: u32,
+        ) -> io::Result<Entry> {
+            debug!("Blocktree::mkdir called");
+            let name = name.to_str().box_err()?.to_owned();
+            let (inode, mut block_path) = self.borrow_block(parent, |block| {
+                self.authorizer.write_allowed(ctx, block.meta())?;
+
+                let mut dir = block.read_dir()?;
+                if dir.entries.contains_key(&name) {
+                    return Err(io::Error::from_raw_os_error(libc::EEXIST));
+                }
+
+                let inode = self.next_inode()?;
+                dir.add_file(name.clone(), inode)?;
+                block.write_dir(&dir)?;
+
+                Ok((inode, block.meta_body().path.clone()))
+            })?;
+            block_path.push_component(name);
+            let stat = self.open_value(inode, block_path, |value| {
+                let stat = value.borrow_block(|block| {
+                    let stat = block.mut_meta_body().access_secrets(|secrets| {
+                        secrets.mode = libc::S_IFDIR | mode & !umask;
+                        secrets.uid = ctx.uid;
+                        secrets.gid = ctx.gid;
+                        Ok(secrets.stat())
+                    })?;
+                    block.write_dir(&Directory::new())?;
+                    block.flush_meta()?;
+                    Ok(stat)
+                })?;
+                value.incr_lookup_count();
+                Ok(stat)
+            })?;
+            Ok(self.fuse_entry(inode, stat))
+        }
+
+        fn rmdir(&self, ctx: &Context, parent: Self::Inode, name: &CStr) -> io::Result<()> {
+            debug!("Blocktree::rmdir called on parent {parent}");
+            let name = name.to_str().box_err()?.to_owned();
+            self.borrow_block(parent, |block| {
+                self.authorizer.write_allowed(ctx, block.meta())?;
+
+                let mut dir = block.read_dir()?;
+                if dir.entries.remove(&name).is_none() {
+                    return Err(io::Error::from_raw_os_error(libc::ENOENT));
+                }
+
+                block.write_dir(&dir)?;
+                Ok(())
+            })
+        }
+
         //////////////////////////////////
         // METHODS WHICH ARE NOT SUPPORTED
         //////////////////////////////////
@@ -1429,18 +1494,6 @@ mod private {
             Self::not_supported()
         }
 
-        fn mkdir(
-            &self,
-            _ctx: &Context,
-            _parent: Self::Inode,
-            _name: &CStr,
-            _mode: u32,
-            _umask: u32,
-        ) -> io::Result<Entry> {
-            debug!("Blocktree::mkdir called");
-            Self::not_supported()
-        }
-
         fn mknod(
             &self,
             _ctx: &Context,
@@ -1511,11 +1564,6 @@ mod private {
             Self::not_supported()
         }
 
-        fn rmdir(&self, _ctx: &Context, parent: Self::Inode, _name: &CStr) -> io::Result<()> {
-            debug!("Blocktree::rmdir called on parent {parent}");
-            Self::not_supported()
-        }
-
         fn setlk(
             &self,
             _ctx: &Context,
@@ -1745,6 +1793,16 @@ mod tests {
             let result = ModeAuthorizer {}.can_read(&case.context());
             assert!(result.is_ok())
         }
+
+        #[test]
+        fn root_always_allowed() {
+            let case = TestCase::new(0, 0, 0);
+            let ctx = case.context();
+            let authorizer = ModeAuthorizer {};
+            assert!(authorizer.can_read(&ctx).is_ok());
+            assert!(authorizer.can_write(&ctx).is_ok());
+            assert!(authorizer.can_exec(&ctx).is_ok());
+        }
     }
 
     struct BtTestCase {

+ 29 - 0
crates/btlib/src/lib.rs

@@ -18,6 +18,7 @@ extern crate static_assertions;
 #[cfg(test)]
 extern crate lazy_static;
 
+use btserde::{read_from, write_to};
 use crypto::{
     AsymKeyPub, Ciphertext, Creds, Decrypter, DecrypterExt, Encrypter, EncrypterExt, HashKind,
     MerkleStream, PublicCreds, SecretStream, Sign, Signature, Signer, SymKey, SymKeyKind, VarHash,
@@ -167,10 +168,38 @@ impl<T, E: Display> StrInIoErr<T> for std::result::Result<T, E> {
 /// The default sector size to use for new blocks.
 const SECTOR_SZ_DEFAULT: usize = 4096;
 
+/// ### THE BLOCK TRAIT
+///
 /// Trait for types which provide read and write access to blocks.
 pub trait Block: Read + Write + Seek + MetaAccess {
     /// Flushes metadata to persistent storage.
     fn flush_meta(&mut self) -> Result<()>;
+
+    fn read_dir(&mut self) -> Result<Directory> {
+        self.seek(SeekFrom::Start(0))?;
+        // &mut &mut Self has to be passed because rustc complains that &mut Self is not sized,
+        // for some reason (implicit dereference?). You can't pass in `&mut self` because `self`
+        // is not declared as mutable. Hence this hack.
+        let mut selfie = self;
+        let dir: Directory = read_from(&mut selfie)?;
+        Ok(dir)
+    }
+
+    fn write_dir(&mut self, dir: &Directory) -> Result<()> {
+        self.seek(SeekFrom::Start(0))?;
+        let mut selfie = self;
+        write_to(dir, &mut selfie)?;
+        selfie.flush()?;
+        Ok(())
+    }
+}
+
+impl<T: MetaAccess> MetaAccess for &mut T {}
+
+impl<T: Block> Block for &mut T {
+    fn flush_meta(&mut self) -> Result<()> {
+        (*self).flush_meta()
+    }
 }
 
 // A trait for streams which only allow reads and writes in fixed sized units called sectors.