diff --git a/olivefs/src/filesystem.rs b/olivefs/src/filesystem.rs index ee77518..315df2a 100644 --- a/olivefs/src/filesystem.rs +++ b/olivefs/src/filesystem.rs @@ -1,7 +1,7 @@ use fuser::{ - Filesystem, KernelConfig, ReplyAttr, ReplyBmap, ReplyCreate, ReplyData, ReplyDirectory, - ReplyDirectoryPlus, ReplyEmpty, ReplyEntry, ReplyIoctl, ReplyLock, ReplyLseek, ReplyOpen, - ReplyStatfs, ReplyWrite, ReplyXattr, Request, TimeOrNow, + FileAttr, Filesystem, KernelConfig, ReplyAttr, ReplyBmap, ReplyCreate, ReplyData, + ReplyDirectory, ReplyDirectoryPlus, ReplyEmpty, ReplyEntry, ReplyIoctl, ReplyLock, ReplyLseek, + ReplyOpen, ReplyStatfs, ReplyWrite, ReplyXattr, Request, TimeOrNow, }; use libc::{ENOSYS, EPERM, O_APPEND, O_RDONLY, O_RDWR, O_WRONLY}; use log::{debug, error, warn}; @@ -200,6 +200,8 @@ impl Filesystem for OliveFilesystem { rdev: u32, reply: ReplyEntry, ) { + // TODO Support this as a create + release. + // Modern Linuxes will call create() rather than mknod()+open() so this may even be optional. debug!( "[Not Implemented] mknod(parent: {:#x?}, name: {:?}, mode: {}, \ umask: {:#x?}, rdev: {})", @@ -381,7 +383,7 @@ impl Filesystem for OliveFilesystem { Ok(()) }, - "opendir", + "open", ); } @@ -817,7 +819,75 @@ impl Filesystem for OliveFilesystem { flags: {:#x?})", parent, name, mode, umask, flags ); - reply.error(ENOSYS); + + let name = if let Some(name) = name.to_str() { + name + } else { + // If we can't decode the filename, pretend it doesn't exist. + reply.error(ENOENT); + return; + } + .to_owned(); + + let requester = self.requester.clone(); + + // TODO This needs more careful consideration before using to write files + // How does truncation work? + let mut open_mode = OpenMode { + read: false, + write: false, + append: false, + }; + + if test_bits(flags, O_APPEND) { + open_mode.append = true; + } + if test_bits(flags, O_RDWR) { + open_mode.read = true; + open_mode.write = true; + } else if test_bits(flags, O_WRONLY) { + open_mode.write = true; + } else if test_bits(flags, O_RDONLY) { + open_mode.read = true; + }; + + self.spawn_with_error_handler( + async move { + let parent_vnode = VnodeId( + parent + .try_into() + .context("Converting u64 inode to u32 VnodeId.")?, + ); + + match requester.create(parent_vnode, open_mode, name).await? { + DataResponse::Success((file_handle, 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.created( + &Duration::from_secs(5), + &file_attr, + 0, + file_handle as u64, + FOPEN_DIRECT_IO, + ); + } + DataResponse::Error { code, message } => { + warn!( + "create(parent: {:#x?}, flags: {:#x?}) failed: {:?}", + parent_vnode, flags, message + ); + reply.error(code as c_int); + } + } + + Ok(()) + }, + "create", + ); } /// Test for a POSIX file lock. diff --git a/olivefs/src/requester.rs b/olivefs/src/requester.rs index ac24606..086d115 100644 --- a/olivefs/src/requester.rs +++ b/olivefs/src/requester.rs @@ -196,6 +196,21 @@ impl Requester { .await } + pub async fn create( + &self, + dir_vnode: VnodeId, + mode: OpenMode, + name: String, + ) -> anyhow::Result> { + self.internal + .command(&DataCommand::CreateFile { + dir_vnode, + mode, + 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 3c4931d..193a4bb 100644 --- a/olivefs_common/src/messages.rs +++ b/olivefs_common/src/messages.rs @@ -54,6 +54,11 @@ pub enum DataCommand { dir_vnode: VnodeId, name: String, }, + CreateFile { + dir_vnode: VnodeId, + mode: OpenMode, + name: String, + }, OpenFile { vnode: VnodeId, mode: OpenMode, diff --git a/olivefsd/src/server/connections.rs b/olivefsd/src/server/connections.rs index 0e0edb4..58fb332 100644 --- a/olivefsd/src/server/connections.rs +++ b/olivefsd/src/server/connections.rs @@ -62,6 +62,14 @@ pub async fn handle_command_stream( ) .await?; } + DataCommand::CreateFile { + dir_vnode, + mode, + name, + } => { + send_bare_message(&mut tx, &file_access.create(dir_vnode, mode, name).await?) + .await?; + } } } diff --git a/olivefsd/src/server/file_access.rs b/olivefsd/src/server/file_access.rs index 4cb928b..af2e797 100644 --- a/olivefsd/src/server/file_access.rs +++ b/olivefsd/src/server/file_access.rs @@ -297,6 +297,71 @@ impl FileAccess { Ok(DataResponse::Success(())) } + pub async fn create( + &self, + dir_vnode: VnodeId, + mode: OpenMode, + name: String, + ) -> anyhow::Result> { + match self.resolve_vnode(dir_vnode).await { + Ok(dir_path) => { + let lookup_path = dir_path.join(name); + // TODO check the security of this: make sure you can't escape the root + match lookup_path.absolutize_virtually(&self.client_info.root) { + Ok(the_path) => { + // We'll allocate the Vnode after we know the target doesn't already exist. Fill in 0 for now. + let mut open_options = OpenOptions::new(); + + // Only create new files + open_options.create_new(true); + + if mode.read { + open_options.read(true); + } + if mode.write { + open_options.write(true); + } + if mode.append { + open_options.append(true); + } + // TODO truncate? + + match open_options.open(&the_path).await { + Ok(file) => { + // TODO offset & append files: how does that work? + let handle = Arc::new(RwLock::new(FileHandle { file, offset: 0 })); + + let file_handle: u32 = self + .client_state + .file_handles + .write() + .await + .insert(handle) + .try_into() + .unwrap(); + + let vnode = self.allocate_vnode(the_path.to_path_buf()).await?; + let metadata = self.read_metadata(&the_path, vnode).await?; + + Ok(DataResponse::Success((file_handle, metadata))) + } + Err(error) => Ok(io_error_to_response( + error, + EFAULT, + &format!("create {:?}", the_path), + )), + } + } + Err(error) => Ok(DataResponse::Error { + code: ENOENT, + message: format!("Can't make absolute. {:?} ({:?})", error, lookup_path), + }), + } + } + Err(response) => Ok(response), + } + } + pub async fn lookup( &self, dir_vnode: VnodeId,