Răsfoiți Sursa

Implemented `Blocktree::rename`.

Matthew Carr 2 ani în urmă
părinte
comite
d55d736bf2
2 a modificat fișierele cu 317 adăugiri și 14 ștergeri
  1. 16 1
      crates/btfuse/src/main.rs
  2. 301 13
      crates/btlib/src/blocktree.rs

+ 16 - 1
crates/btfuse/src/main.rs

@@ -180,7 +180,7 @@ mod test {
     use std::{
         ffi::{OsStr, OsString},
         fs::{
-            create_dir, hard_link, metadata, read, read_dir, remove_dir, remove_file,
+            create_dir, hard_link, metadata, read, read_dir, remove_dir, remove_file, rename,
             set_permissions, write, Permissions, ReadDir,
         },
         os::unix::fs::PermissionsExt,
@@ -499,4 +499,19 @@ mod test {
         let os_err = err.raw_os_error().expect("raw_os_error was empty");
         assert_eq!(os_err, libc::EACCES);
     }
+
+    #[test]
+    fn rename_file() {
+        const FILE_NAME: &str = "parabola.txt";
+        const EXPECTED: &[u8] = b"We are eternal all this pain is an illusion";
+        let mut case = TestCase::new();
+        let src_path = case.mnt_path().join(FILE_NAME);
+        let dst_path = case.mnt_path().join("parabola_lyrics.txt");
+
+        write(&src_path, EXPECTED).unwrap();
+        rename(&src_path, &dst_path).unwrap();
+
+        let actual = read(&dst_path).unwrap();
+        assert_eq!(EXPECTED, actual)
+    }
 }

+ 301 - 13
crates/btlib/src/blocktree.rs

@@ -35,6 +35,8 @@ use crate::{
 pub use private::{Blocktree, ModeAuthorizer, SpecInodes};
 
 mod private {
+    use std::collections::BTreeMap;
+
     use super::*;
 
     type Inode = u64;
@@ -1472,6 +1474,105 @@ mod private {
             .io_err()
         }
 
+        fn rename(
+            &self,
+            ctx: &Context,
+            src_dir: Self::Inode,
+            src_name: &CStr,
+            dst_dir: Self::Inode,
+            dst_name: &CStr,
+            flags: u32,
+        ) -> io::Result<()> {
+            debug!("Blocktree::rename, src_dir {src_dir}, src_name {:?}, dst_dir {dst_dir}, dst_name {:?}, flags {flags}", src_name, dst_name);
+
+            fn not_found_err(name: &str) -> crate::Error {
+                bterr!("file named '{name}' was not found").context(io::ErrorKind::NotFound)
+            }
+
+            fn modify_entries(
+                src_entries: Option<&mut BTreeMap<String, DirEntry>>,
+                dst_entries: &mut BTreeMap<String, DirEntry>,
+                entry: DirEntry,
+                src_name: &str,
+                dst_name: String,
+                no_replace: bool,
+                exchange: bool,
+            ) -> Result<()> {
+                if let Some(prev_entry) = dst_entries.insert(dst_name, entry) {
+                    if no_replace {
+                        return Err(bterr!("destination already exists")
+                            .context(io::ErrorKind::AlreadyExists));
+                    }
+                    if exchange {
+                        let entries = src_entries.unwrap_or(dst_entries);
+                        entries.insert(src_name.to_owned(), prev_entry);
+                    }
+                } else if exchange {
+                    return Err(
+                        bterr!("exchange was specified but destination doesn't exist")
+                            .context(io::ErrorKind::NotFound),
+                    );
+                }
+                Ok(())
+            }
+
+            let src_name = src_name.to_str().box_err()?;
+            let dst_name = dst_name.to_str().box_err()?.to_owned();
+            let no_replace = flags & libc::RENAME_NOREPLACE != 0;
+            let exchange = flags & libc::RENAME_EXCHANGE != 0;
+            if no_replace && exchange {
+                return Err(io::Error::new(
+                    io::ErrorKind::InvalidInput,
+                    "Both RENAME_EXCHANGE and RENAME_NOREPLACE were specified, but are incompatible"
+                ));
+            }
+            if src_dir == dst_dir {
+                self.borrow_block_mut(src_dir, |src_dir| {
+                    self.authorizer.write_allowed(ctx, src_dir.meta())?;
+                    let mut dir = src_dir.read_dir()?;
+                    let entry = dir
+                        .entries
+                        .remove(src_name)
+                        .ok_or_else(|| not_found_err(src_name))?;
+                    modify_entries(
+                        None,
+                        &mut dir.entries,
+                        entry,
+                        src_name,
+                        dst_name,
+                        no_replace,
+                        exchange,
+                    )?;
+                    src_dir.write_dir(&dir)
+                })?;
+            } else {
+                self.borrow_block_mut(src_dir, |src_dir| {
+                    self.authorizer.write_allowed(ctx, src_dir.meta())?;
+                    let mut dir_src = src_dir.read_dir()?;
+                    let entry = dir_src
+                        .entries
+                        .remove(src_name)
+                        .ok_or_else(|| not_found_err(src_name))?;
+                    self.borrow_block_mut(dst_dir, |dst_dir| {
+                        self.authorizer.write_allowed(ctx, dst_dir.meta())?;
+                        let mut dir_dst = dst_dir.read_dir()?;
+                        modify_entries(
+                            Some(&mut dir_src.entries),
+                            &mut dir_dst.entries,
+                            entry,
+                            src_name,
+                            dst_name,
+                            no_replace,
+                            exchange,
+                        )?;
+                        dst_dir.write_dir(&dir_dst)
+                    })?;
+                    src_dir.write_dir(&dir_src)
+                })?;
+            }
+            Ok(())
+        }
+
         //////////////////////////////////
         // METHODS WHICH ARE NOT SUPPORTED
         //////////////////////////////////
@@ -1632,19 +1733,6 @@ mod private {
             Self::not_supported()
         }
 
-        fn rename(
-            &self,
-            _ctx: &Context,
-            _olddir: Self::Inode,
-            _oldname: &CStr,
-            _newdir: Self::Inode,
-            _newname: &CStr,
-            _flags: u32,
-        ) -> io::Result<()> {
-            debug!("Blocktree::rename called");
-            Self::not_supported()
-        }
-
         fn setlk(
             &self,
             _ctx: &Context,
@@ -2340,4 +2428,204 @@ mod tests {
             handle.join().unwrap();
         }
     }
+
+    #[test]
+    fn rename_in_same_directory() {
+        let case = BtTestCase::new_empty();
+        let bt = &case.bt;
+        let ctx = case.context();
+        let root = SpecInodes::RootDir.into();
+        let src_name = CString::new("src").unwrap();
+        let dst_name = CString::new("dst").unwrap();
+
+        let (entry, ..) = bt
+            .create(&ctx, root, &src_name, CreateIn::default())
+            .unwrap();
+        let inode = entry.inode;
+        bt.rename(&ctx, root, &src_name, root, &dst_name, 0)
+            .unwrap();
+
+        let entry = bt.lookup(&ctx, root, &dst_name).unwrap();
+        assert_eq!(inode, entry.inode);
+        let result = bt.lookup(&ctx, root, &src_name);
+        assert!(result.is_err());
+    }
+
+    #[test]
+    fn rename_to_different_directory() {
+        let case = BtTestCase::new_empty();
+        let bt = &case.bt;
+        let ctx = case.context();
+        let root = SpecInodes::RootDir.into();
+        let dir_name = CString::new("dir").unwrap();
+        let file_name = CString::new("file").unwrap();
+
+        let entry = bt.mkdir(&ctx, root, &dir_name, 0o755, 0).unwrap();
+        let dir = entry.inode;
+        let (entry, ..) = bt
+            .create(&ctx, root, &file_name, CreateIn::default())
+            .unwrap();
+        let file = entry.inode;
+        bt.rename(&ctx, root, &file_name, dir, &file_name, 0)
+            .unwrap();
+
+        let entry = bt.lookup(&ctx, dir, &file_name).unwrap();
+        assert_eq!(file, entry.inode);
+        let result = bt.lookup(&ctx, root, &file_name);
+        assert!(result.is_err());
+    }
+
+    #[test]
+    fn rename_no_replace_same_directory() {
+        let case = BtTestCase::new_empty();
+        let bt = &case.bt;
+        let ctx = case.context();
+        let root = SpecInodes::RootDir.into();
+        let oldname = CString::new("old").unwrap();
+        let newname = CString::new("new").unwrap();
+
+        bt.create(&ctx, root, &oldname, CreateIn::default())
+            .unwrap();
+        bt.create(&ctx, root, &newname, CreateIn::default())
+            .unwrap();
+
+        let result = bt.rename(&ctx, root, &oldname, root, &newname, libc::RENAME_NOREPLACE);
+        let matched = if let Err(err) = result {
+            err.kind() == io::ErrorKind::AlreadyExists
+        } else {
+            false
+        };
+        assert!(matched);
+    }
+
+    #[test]
+    fn rename_no_replace_different_directory() {
+        let case = BtTestCase::new_empty();
+        let bt = &case.bt;
+        let ctx = case.context();
+        let root = SpecInodes::RootDir.into();
+        let dir_name = CString::new("dir").unwrap();
+        let file_name = CString::new("file").unwrap();
+
+        let entry = bt.mkdir(&ctx, root, &dir_name, 0o755, 0).unwrap();
+        let dir = entry.inode;
+        bt.create(&ctx, root, &file_name, CreateIn::default())
+            .unwrap();
+        bt.create(&ctx, dir, &file_name, CreateIn::default())
+            .unwrap();
+
+        let result = bt.rename(
+            &ctx,
+            root,
+            &file_name,
+            dir,
+            &file_name,
+            libc::RENAME_NOREPLACE,
+        );
+        let matched = if let Err(err) = result {
+            err.kind() == io::ErrorKind::AlreadyExists
+        } else {
+            false
+        };
+        assert!(matched);
+    }
+
+    #[test]
+    fn rename_exchange_same_directory() {
+        let case = BtTestCase::new_empty();
+        let bt = &case.bt;
+        let ctx = case.context();
+        let root = SpecInodes::RootDir.into();
+        let name_one = CString::new("one").unwrap();
+        let name_two = CString::new("two").unwrap();
+        let flags = libc::O_RDWR as u32;
+        let create_in = CreateIn {
+            mode: 0o644,
+            umask: 0,
+            flags,
+            fuse_flags: 0,
+        };
+
+        let (entry_one, ..) = bt.create(&ctx, root, &name_one, create_in.clone()).unwrap();
+        let (entry_two, ..) = bt.create(&ctx, root, &name_two, create_in).unwrap();
+        bt.rename(
+            &ctx,
+            root,
+            &name_one,
+            root,
+            &name_two,
+            libc::RENAME_EXCHANGE,
+        )
+        .unwrap();
+
+        let actual_one = bt.lookup(&ctx, root, &name_one).unwrap();
+        assert_eq!(entry_two.inode, actual_one.inode);
+        let actual_two = bt.lookup(&ctx, root, &name_two).unwrap();
+        assert_eq!(entry_one.inode, actual_two.inode);
+    }
+
+    #[test]
+    fn rename_exchange_different_directories() {
+        let case = BtTestCase::new_empty();
+        let bt = &case.bt;
+        let ctx = case.context();
+        let root = SpecInodes::RootDir.into();
+        let dir_name = CString::new("dir").unwrap();
+        let name_one = CString::new("one").unwrap();
+        let name_two = CString::new("two").unwrap();
+        let flags = libc::O_RDWR as u32;
+        let create_in = CreateIn {
+            mode: 0o644,
+            umask: 0,
+            flags,
+            fuse_flags: 0,
+        };
+
+        let (entry_one, ..) = bt.create(&ctx, root, &name_one, create_in.clone()).unwrap();
+        let dir_entry = bt.mkdir(&ctx, root, &dir_name, 0o755, 0).unwrap();
+        let dir = dir_entry.inode;
+        let (entry_two, ..) = bt.create(&ctx, dir, &name_two, create_in).unwrap();
+        bt.rename(&ctx, root, &name_one, dir, &name_two, libc::RENAME_EXCHANGE)
+            .unwrap();
+
+        let actual_one = bt.lookup(&ctx, root, &name_one).unwrap();
+        assert_eq!(entry_two.inode, actual_one.inode);
+        let actual_two = bt.lookup(&ctx, dir, &name_two).unwrap();
+        assert_eq!(entry_one.inode, actual_two.inode);
+    }
+
+    #[test]
+    fn rename_exchange_and_no_replace_is_err() {
+        let case = BtTestCase::new_empty();
+        let bt = &case.bt;
+        let ctx = case.context();
+        let root = SpecInodes::RootDir.into();
+        let name_one = CString::new("one").unwrap();
+        let name_two = CString::new("two").unwrap();
+        let flags = libc::O_RDWR as u32;
+        let create_in = CreateIn {
+            mode: 0o644,
+            umask: 0,
+            flags,
+            fuse_flags: 0,
+        };
+
+        bt.create(&ctx, root, &name_one, create_in.clone()).unwrap();
+        bt.create(&ctx, root, &name_two, create_in).unwrap();
+        let result = bt.rename(
+            &ctx,
+            root,
+            &name_one,
+            root,
+            &name_two,
+            libc::RENAME_EXCHANGE | libc::RENAME_NOREPLACE,
+        );
+
+        let matched = if let Err(err) = result {
+            err.kind() == io::ErrorKind::InvalidInput
+        } else {
+            false
+        };
+        assert!(matched);
+    }
 }