|
@@ -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);
|
|
|
+ }
|
|
|
}
|