diff --git a/olivefs/src/filesystem.rs b/olivefs/src/filesystem.rs index 71acf88..20e47e6 100644 --- a/olivefs/src/filesystem.rs +++ b/olivefs/src/filesystem.rs @@ -390,7 +390,7 @@ impl Filesystem for OliveFilesystem { reply.error(ENOENT); return; } - .to_owned(); + .to_owned(); let link = if let Some(link) = link.to_str() { link @@ -400,7 +400,7 @@ impl Filesystem for OliveFilesystem { reply.error(ENOENT); return; } - .to_owned(); + .to_owned(); let requester = self.requester.clone(); @@ -419,7 +419,10 @@ impl Filesystem for OliveFilesystem { reply.entry(&Duration::from_secs(5), &file_attr, 42); } DataResponse::Error { code, message } => { - warn!("symlink(parent: {:#x?}) failed: {:?}", parent_vnode, message); + warn!( + "symlink(parent: {:#x?}) failed: {:?}", + parent_vnode, message + ); reply.error(code as c_int); } } @@ -441,12 +444,62 @@ impl Filesystem for OliveFilesystem { flags: u32, reply: ReplyEmpty, ) { - debug!( - "[Not Implemented] rename(parent: {:#x?}, name: {:?}, newparent: {:#x?}, \ - newname: {:?}, flags: {})", - parent, name, newparent, newname, flags, + let name = if let Some(name) = name.to_str() { + name + } else { + // If we can't decode the filename, pretend it doesn't exist. + error!("rename: Old Filename not convertible."); + reply.error(ENOENT); + return; + } + .to_owned(); + + let newname = if let Some(newname) = newname.to_str() { + newname + } else { + // If we can't decode the filename, pretend it doesn't exist. + error!("rename: New Filename not convertible."); + reply.error(ENOENT); + return; + } + .to_owned(); + + let requester = self.requester.clone(); + + self.spawn_with_error_handler( + async move { + let parent_vnode = VnodeId( + parent + .try_into() + .context("Converting u64 inode to u32 VnodeId.")?, + ); + + let newparent_vnode = VnodeId( + newparent + .try_into() + .context("Converting u64 inode to u32 VnodeId.")?, + ); + + match requester + .rename(parent_vnode, name, newparent_vnode, newname) + .await? + { + DataResponse::Success(()) => { + reply.ok(); + } + DataResponse::Error { code, message } => { + warn!( + "rename(parent: {:#x?}, newparent: {:#?}) failed: {:?}", + parent_vnode, newparent_vnode, message + ); + reply.error(code as c_int); + } + } + + Ok(()) + }, + "rename", ); - reply.error(ENOSYS); } /// Create a hard link. diff --git a/olivefs/src/requester.rs b/olivefs/src/requester.rs index 922ed93..abc364b 100644 --- a/olivefs/src/requester.rs +++ b/olivefs/src/requester.rs @@ -226,10 +226,14 @@ impl Requester { &self, dir_vnode: VnodeId, name: String, - link: String + link: String, ) -> anyhow::Result> { self.internal - .command(&DataCommand::MakeSymlink { dir_vnode, name, link }) + .command(&DataCommand::MakeSymlink { + dir_vnode, + name, + link, + }) .await } @@ -253,6 +257,23 @@ impl Requester { .await } + pub async fn rename( + &self, + old_dir_vnode: VnodeId, + old_name: String, + new_dir_vnode: VnodeId, + new_name: String, + ) -> anyhow::Result> { + self.internal + .command(&DataCommand::Rename { + old_dir_vnode, + old_name, + new_dir_vnode, + new_name, + }) + .await + } + pub async fn open(&self, vnode: VnodeId, mode: OpenMode) -> anyhow::Result> { self.internal .command(&DataCommand::OpenFile { vnode, mode }) diff --git a/olivefs_common/src/messages.rs b/olivefs_common/src/messages.rs index dc270c5..0cd9520 100644 --- a/olivefs_common/src/messages.rs +++ b/olivefs_common/src/messages.rs @@ -108,6 +108,12 @@ pub enum DataCommand { name: String, link: String, }, + Rename { + old_dir_vnode: VnodeId, + old_name: String, + new_dir_vnode: VnodeId, + new_name: String, + }, } #[derive(Serialize, Deserialize, Debug, Clone)] diff --git a/olivefsd/src/server/connections.rs b/olivefsd/src/server/connections.rs index 44c16a1..31ffaf9 100644 --- a/olivefsd/src/server/connections.rs +++ b/olivefsd/src/server/connections.rs @@ -166,7 +166,11 @@ pub async fn handle_command_stream( ) .await?; } - DataCommand::MakeSymlink { dir_vnode, name, link } => { + DataCommand::MakeSymlink { + dir_vnode, + name, + link, + } => { send_bare_message( &mut tx, &file_access @@ -174,7 +178,22 @@ pub async fn handle_command_stream( .await .unwrap_or_else(error_to_response), ) - .await?; + .await?; + } + DataCommand::Rename { + old_dir_vnode, + old_name, + new_dir_vnode, + new_name, + } => { + send_bare_message( + &mut tx, + &file_access + .rename(old_dir_vnode, old_name, new_dir_vnode, new_name) + .await + .unwrap_or_else(error_to_response), + ) + .await?; } } } diff --git a/olivefsd/src/server/file_access.rs b/olivefsd/src/server/file_access.rs index 1675e8f..88ef3f0 100644 --- a/olivefsd/src/server/file_access.rs +++ b/olivefsd/src/server/file_access.rs @@ -815,4 +815,28 @@ impl FileAccess { let metadata = self.read_metadata(&target_path, vnode).await?; Ok(DataResponse::Success(metadata)) } + + /// Symlink safety: + /// Makes an attempt to safely resolve symlinks, but there is a possibility of a TOCTOU race. + /// TODO Revisit later for safety. + pub async fn rename( + &self, + old_dir_vnode: VnodeId, + old_name: String, + new_dir_vnode: VnodeId, + new_name: String, + ) -> anyhow::Result> { + let old_parent_dir_resolved = self + .resolve_vnode_including_follow_symlinks_safely_best_effort(old_dir_vnode) + .await?; + let source_path = old_parent_dir_resolved.join(old_name); + + let new_parent_dir_resolved = self + .resolve_vnode_including_follow_symlinks_safely_best_effort(new_dir_vnode) + .await?; + let target_path = new_parent_dir_resolved.join(new_name); + + tokio::fs::rename(source_path, target_path).await?; + Ok(DataResponse::Success(())) + } }