From fecdaaecdf4b94b5aeee7b8f8529bd43a0580d1c Mon Sep 17 00:00:00 2001 From: Olivier 'reivilibre Date: Mon, 7 Feb 2022 23:14:42 +0000 Subject: [PATCH] Add support for mkdir --- olivefs/src/filesystem.rs | 43 +++++++++++++++++++++++++++--- olivefs/src/requester.rs | 10 +++++++ olivefs_common/src/messages.rs | 4 +++ olivefsd/src/server/connections.rs | 10 +++++++ olivefsd/src/server/file_access.rs | 40 ++++++++++++++++++++------- 5 files changed, 94 insertions(+), 13 deletions(-) diff --git a/olivefs/src/filesystem.rs b/olivefs/src/filesystem.rs index 3ec7df2..3e2c4c4 100644 --- a/olivefs/src/filesystem.rs +++ b/olivefs/src/filesystem.rs @@ -259,11 +259,46 @@ impl Filesystem for OliveFilesystem { umask: u32, reply: ReplyEntry, ) { - debug!( - "[Not Implemented] mkdir(parent: {:#x?}, name: {:?}, mode: {}, umask: {:#x?})", - parent, name, mode, umask + let name = if let Some(name) = name.to_str() { + name + } else { + // If we can't decode the filename, pretend it doesn't exist. + error!("mkdir: 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.")?, + ); + + match requester.mkdir(parent_vnode, name).await? { + DataResponse::Success(metadata) => { + // Use the direct I/O flag so that we can be in control of when EOF happens + // rather than letting the kernel assume the getattr size is valid + // and caching up to that point. + // We might wind up wanting to do our own buffering... + let file_attr: FileAttr = metadata.into(); + // TODO generation should be generated client-side + reply.entry(&Duration::from_secs(5), &file_attr, 42); + } + DataResponse::Error { code, message } => { + warn!("mkdir(parent: {:#x?}) failed: {:?}", parent_vnode, message); + reply.error(code as c_int); + } + } + + Ok(()) + }, + "mkdir", ); - reply.error(ENOSYS); } /// Remove a file. diff --git a/olivefs/src/requester.rs b/olivefs/src/requester.rs index dbb53cd..f6cc257 100644 --- a/olivefs/src/requester.rs +++ b/olivefs/src/requester.rs @@ -212,6 +212,16 @@ impl Requester { .await } + pub async fn mkdir( + &self, + dir_vnode: VnodeId, + name: String, + ) -> anyhow::Result> { + self.internal + .command(&DataCommand::MakeDirectory { dir_vnode, 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 410728f..fe50bfd 100644 --- a/olivefs_common/src/messages.rs +++ b/olivefs_common/src/messages.rs @@ -91,6 +91,10 @@ pub enum DataCommand { StatFs { vnode: VnodeId, }, + MakeDirectory { + dir_vnode: VnodeId, + name: String, + }, } #[derive(Serialize, Deserialize, Debug, Clone)] diff --git a/olivefsd/src/server/connections.rs b/olivefsd/src/server/connections.rs index c6d73e2..ae7209b 100644 --- a/olivefsd/src/server/connections.rs +++ b/olivefsd/src/server/connections.rs @@ -136,6 +136,16 @@ pub async fn handle_command_stream( ) .await?; } + DataCommand::MakeDirectory { dir_vnode, name } => { + send_bare_message( + &mut tx, + &file_access + .mkdir(dir_vnode, 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 c2928ed..eaf7060 100644 --- a/olivefsd/src/server/file_access.rs +++ b/olivefsd/src/server/file_access.rs @@ -257,15 +257,17 @@ impl FileAccess { pub async fn getattr(&self, vnode: VnodeId) -> anyhow::Result> { let inode_map = self.client_state.inode_map.read().await; if let Some(inode_info) = inode_map.get(vnode.0 as usize) { - let result = self - .read_metadata(&inode_info.real_path, vnode) - .await; - trace!("getattr. Inode {:?} Path: {:?} = {:?}", vnode.0, inode_info.real_path, result); - Ok(result - .map_or_else( - |err| io_error_to_response(err, ENOENT, "getattr"), - |fm| DataResponse::Success(fm), - )) + let result = self.read_metadata(&inode_info.real_path, vnode).await; + trace!( + "getattr. Inode {:?} Path: {:?} = {:?}", + vnode.0, + inode_info.real_path, + result + ); + Ok(result.map_or_else( + |err| io_error_to_response(err, ENOENT, "getattr"), + |fm| DataResponse::Success(fm), + )) } else { Ok(DataResponse::Error { code: error_codes::EFAULT, @@ -416,6 +418,7 @@ impl FileAccess { name: String, ) -> anyhow::Result> { // TODO use the better version of this then handle I/O errors in the callsite + // TODO THIS DOES NOT RESOLVE SYMLINKS THAT IT COULD DO SAFELY match self.resolve_vnode(dir_vnode).await { Ok(dir_path) => { let lookup_path = dir_path.join(name); @@ -740,4 +743,23 @@ impl FileAccess { statfs_wrapper(&path).map(DataResponse::Success) } + + /// 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 mkdir( + &self, + dir_vnode: VnodeId, + name: String, + ) -> anyhow::Result> { + let parent_dir_resolved = self + .resolve_vnode_including_follow_symlinks_safely_best_effort(dir_vnode) + .await?; + let target_path = parent_dir_resolved.join(name); + tokio::fs::create_dir(&target_path).await?; + let vnode = self.allocate_vnode(target_path.clone()).await?; + + let metadata = self.read_metadata(&target_path, vnode).await?; + Ok(DataResponse::Success(metadata)) + } }