From f02521659db8ea543b803b6f095082ac96365a13 Mon Sep 17 00:00:00 2001 From: yuhui Date: Tue, 17 Dec 2024 10:57:38 +0800 Subject: [PATCH] Add SimpleFilesystem --- clients/filesystem-fuse/.cargo/config.toml | 1 - clients/filesystem-fuse/Cargo.toml | 6 +- clients/filesystem-fuse/Makefile | 70 +++ clients/filesystem-fuse/build.gradle.kts | 32 +- clients/filesystem-fuse/src/filesystem.rs | 427 +++++++++++++++++- .../filesystem-fuse/src/fuse_api_handle.rs | 7 +- clients/filesystem-fuse/src/lib.rs | 2 + clients/filesystem-fuse/src/main.rs | 2 + .../src/opened_file_manager.rs | 65 +++ clients/filesystem-fuse/src/utils.rs | 35 ++ 10 files changed, 625 insertions(+), 22 deletions(-) create mode 100644 clients/filesystem-fuse/Makefile create mode 100644 clients/filesystem-fuse/src/opened_file_manager.rs create mode 100644 clients/filesystem-fuse/src/utils.rs diff --git a/clients/filesystem-fuse/.cargo/config.toml b/clients/filesystem-fuse/.cargo/config.toml index 78bc9f7fe48..9d5bb048edc 100644 --- a/clients/filesystem-fuse/.cargo/config.toml +++ b/clients/filesystem-fuse/.cargo/config.toml @@ -16,5 +16,4 @@ # under the License. [build] -target-dir = "build" rustflags = ["-Adead_code", "-Aclippy::redundant-field-names"] diff --git a/clients/filesystem-fuse/Cargo.toml b/clients/filesystem-fuse/Cargo.toml index 2883cecc656..3bcf20f37ef 100644 --- a/clients/filesystem-fuse/Cargo.toml +++ b/clients/filesystem-fuse/Cargo.toml @@ -30,13 +30,15 @@ name = "gvfs-fuse" path = "src/main.rs" [lib] -name="gvfs_fuse" +name = "gvfs_fuse" [dependencies] async-trait = "0.1" bytes = "1.6.0" -futures-util = "0.3.30" +dashmap = "6.1.0" fuse3 = { version = "0.8.1", "features" = ["tokio-runtime", "unprivileged"] } +futures-util = "0.3.30" +libc = "0.2.168" log = "0.4.22" tokio = { version = "1.38.0", features = ["full"] } tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } diff --git a/clients/filesystem-fuse/Makefile b/clients/filesystem-fuse/Makefile new file mode 100644 index 00000000000..2e48a3e1fd8 --- /dev/null +++ b/clients/filesystem-fuse/Makefile @@ -0,0 +1,70 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +.EXPORT_ALL_VARIABLES: + +.PHONY: build +build: + cargo build --all-features --workspace + +fmt: + cargo fmt --all + +cargo-sort: install-cargo-sort + cargo sort -w + +check-fmt: + cargo fmt --all -- --check + +check-clippy: + #cargo clippy --all-targets --all-features --workspace -- -D warnings + cargo clippy --all-targets --all-features --workspace -- + +install-cargo-sort: + cargo install cargo-sort@1.0.9 + +check-cargo-sort: install-cargo-sort + cargo sort -c + +install-cargo-machete: + cargo install cargo-machete + +cargo-machete: install-cargo-machete + cargo machete + +install-taplo-cli: + cargo install taplo-cli@0.9.0 + +fix-toml: install-taplo-cli + taplo fmt + +check-toml: install-taplo-cli + taplo check + +check: check-fmt check-clippy check-cargo-sort check-toml cargo-machete + +doc-test: + cargo test --no-fail-fast --doc --all-features --workspace + +unit-test: doc-test + cargo test --no-fail-fast --lib --all-features --workspace + +test: doc-test + cargo test --no-fail-fast --all-targets --all-features --workspace + +clean: + cargo clean diff --git a/clients/filesystem-fuse/build.gradle.kts b/clients/filesystem-fuse/build.gradle.kts index 08693ddc5bd..201397c19e7 100644 --- a/clients/filesystem-fuse/build.gradle.kts +++ b/clients/filesystem-fuse/build.gradle.kts @@ -32,7 +32,7 @@ val buildRustProject by tasks.registering(Exec::class) { dependsOn(checkRustEnvironment) description = "Compile the Rust project" workingDir = file("$projectDir") - commandLine("bash", "-c", "cargo build --release") + commandLine("bash", "-c", "make build") } val checkRustProject by tasks.registering(Exec::class) { @@ -40,18 +40,7 @@ val checkRustProject by tasks.registering(Exec::class) { description = "Check the Rust project" workingDir = file("$projectDir") - commandLine( - "bash", - "-c", - """ - set -e - echo "Checking the code format" - cargo fmt --all -- --check - - echo "Running clippy" - cargo clippy --all-targets --all-features --workspace -- -D warnings - """.trimIndent() - ) + commandLine( "bash", "-c", "make check") } val testRustProject by tasks.registering(Exec::class) { @@ -59,7 +48,18 @@ val testRustProject by tasks.registering(Exec::class) { description = "Run tests in the Rust project" group = "verification" workingDir = file("$projectDir") - commandLine("bash", "-c", "cargo test --release") + commandLine("bash", "-c", "make test") + + standardOutput = System.out + errorOutput = System.err +} + +val cleanRustProject by tasks.registering(Exec::class) { + dependsOn(checkRustEnvironment) + description = "Run tests in the Rust project" + group = "verification" + workingDir = file("$projectDir") + commandLine("bash", "-c", "make clean") standardOutput = System.out errorOutput = System.err @@ -85,3 +85,7 @@ tasks.named("check") { tasks.named("test") { dependsOn(testRustProject) } + +tasks.named("clean") { + dependsOn(cleanRustProject) +} diff --git a/clients/filesystem-fuse/src/filesystem.rs b/clients/filesystem-fuse/src/filesystem.rs index 6d1d8fa2538..6a2bf80c6dc 100644 --- a/clients/filesystem-fuse/src/filesystem.rs +++ b/clients/filesystem-fuse/src/filesystem.rs @@ -16,9 +16,15 @@ * specific language governing permissions and limitations * under the License. */ +use crate::opened_file_manager::OpenedFileManager; +use crate::utils::{join_file_path, split_file_path}; use async_trait::async_trait; use bytes::Bytes; use fuse3::{Errno, FileType, Timestamp}; +use std::collections::HashMap; +use std::sync::atomic::AtomicU64; +use std::time::SystemTime; +use tokio::sync::RwLock; pub(crate) type Result = std::result::Result; @@ -174,9 +180,6 @@ pub struct FileStat { // file type like regular file or directory and so on pub(crate) kind: FileType, - // file permission - pub(crate) perm: u16, - // file access time pub(crate) atime: Timestamp, @@ -190,6 +193,48 @@ pub struct FileStat { pub(crate) nlink: u32, } +impl FileStat { + pub fn new_file_with_path(path: &str, size: u64) -> Self { + let (parent, name) = split_file_path(path); + Self::new_file(parent, name, size) + } + + pub fn new_dir_with_path(path: &str) -> Self { + let (parent, name) = split_file_path(path); + Self::new_dir(parent, name) + } + + pub fn new_file(parent: &str, name: &str, size: u64) -> Self { + Self::new_file_entry(parent, name, size, FileType::RegularFile) + } + + pub fn new_dir(parent: &str, name: &str) -> Self { + Self::new_file_entry(parent, name, 0, FileType::Directory) + } + + pub fn new_file_entry(parent: &str, name: &str, size: u64, kind: FileType) -> Self { + let atime = Timestamp::from(SystemTime::now()); + Self { + file_id: 0, + parent_file_id: 0, + name: name.into(), + path: join_file_path(parent, name), + size: size, + kind: kind, + atime: atime, + mtime: atime, + ctime: atime, + nlink: 1, + } + } + + pub(crate) fn set_file_id(&mut self, parent_file_id: u64, file_id: u64) { + debug_assert!(file_id != 0 && parent_file_id != 0); + self.parent_file_id = parent_file_id; + self.file_id = file_id; + } +} + /// Opened file for read or write, it is used to read or write the file content. pub(crate) struct OpenedFile { pub(crate) file_stat: FileStat, @@ -201,6 +246,66 @@ pub(crate) struct OpenedFile { pub writer: Option>, } +impl OpenedFile { + pub fn new(file_stat: FileStat) -> Self { + OpenedFile { + file_stat: file_stat, + handle_id: 0, + reader: None, + writer: None, + } + } + + async fn read(&mut self, offset: u64, size: u32) -> Result { + self.file_stat.atime = Timestamp::from(SystemTime::now()); + self.reader.as_mut().unwrap().read(offset, size).await + } + + async fn write(&mut self, offset: u64, data: &[u8]) -> Result { + let end = offset + data.len() as u64; + + if end > self.file_stat.size { + self.file_stat.size = end; + } + self.file_stat.atime = Timestamp::from(SystemTime::now()); + self.file_stat.mtime = self.file_stat.atime; + + self.writer.as_mut().unwrap().write(offset, data).await + } + + async fn close(&mut self) -> Result<()> { + if let Some(mut reader) = self.reader.take() { + reader.close().await?; + } + if let Some(mut writer) = self.writer.take() { + self.flush().await?; + writer.close().await? + } + Ok(()) + } + + async fn flush(&mut self) -> Result<()> { + if let Some(writer) = &mut self.writer { + writer.flush().await?; + } + Ok(()) + } + + fn file_handle(&self) -> FileHandle { + debug_assert!(self.handle_id != 0); + debug_assert!(self.file_stat.file_id != 0); + FileHandle { + file_id: self.file_stat.file_id, + handle_id: self.handle_id, + } + } + + pub(crate) fn set_file_id(&mut self, parent_file_id: u64, file_id: u64) { + debug_assert!(file_id != 0 && parent_file_id != 0); + self.file_stat.set_file_id(parent_file_id, file_id) + } +} + // FileHandle is the file handle for the opened file. pub(crate) struct FileHandle { pub(crate) file_id: u64, @@ -239,3 +344,319 @@ pub trait FileWriter: Sync + Send { Ok(()) } } + +/// SimpleFileSystem is a simple implementation for the file system. +/// it is used to manage the file metadata and file handle. +/// The operations of the file system are implemented by the PathFileSystem. +/// Note: This class is not use in the production code, it is used for the demo and testing +pub struct SimpleFileSystem { + /// file entries + file_entry_manager: RwLock, + /// opened files + opened_file_manager: OpenedFileManager, + /// inode id generator + inode_id_generator: AtomicU64, + + /// real system + fs: T, +} + +impl SimpleFileSystem { + const INITIAL_INODE_ID: u64 = 10000; + const ROOT_DIR_PARENT_FILE_ID: u64 = 0; + const ROOT_DIR_FILE_ID: u64 = 1; + const ROOT_DIR_NAME: &'static str = ""; + + pub(crate) fn new(fs: T) -> Self { + Self { + file_entry_manager: RwLock::new(FileEntryManager::new()), + opened_file_manager: OpenedFileManager::new(), + inode_id_generator: AtomicU64::new(Self::INITIAL_INODE_ID), + fs, + } + } + + fn next_inode_id(&self) -> u64 { + self.inode_id_generator + .fetch_add(1, std::sync::atomic::Ordering::SeqCst) + } + + async fn get_file_node(&self, file_id: u64) -> Result { + self.file_entry_manager + .read() + .await + .get_file_by_id(file_id) + .ok_or(Errno::from(libc::ENOENT)) + } + + async fn get_file_node_by_path(&self, path: &str) -> Option { + self.file_entry_manager.read().await.get_file_by_name(path) + } + + async fn fill_file_id(&self, file_stat: &mut FileStat, parent_file_id: u64) { + let mut file_manager = self.file_entry_manager.write().await; + let file = file_manager.get_file_by_name(&file_stat.path); + match file { + None => { + file_stat.set_file_id(parent_file_id, self.next_inode_id()); + file_manager.insert(file_stat.parent_file_id, file_stat.file_id, &file_stat.path); + } + Some(file) => { + file_stat.set_file_id(file.parent_file_id, file.file_id); + } + } + } + + async fn open_file_internal( + &self, + file_id: u64, + flags: u32, + kind: FileType, + ) -> Result { + let file_node = self.get_file_node(file_id).await?; + + let mut file = { + match kind { + FileType::Directory => { + self.fs + .open_dir(&file_node.file_name, OpenFileFlags(flags)) + .await? + } + FileType::RegularFile => { + self.fs + .open_file(&file_node.file_name, OpenFileFlags(flags)) + .await? + } + _ => return Err(Errno::from(libc::EINVAL)), + } + }; + file.set_file_id(file_node.parent_file_id, file_id); + let file = self.opened_file_manager.put_file(file); + let file = file.lock().await; + Ok(file.file_handle()) + } +} + +#[async_trait] +impl RawFileSystem for SimpleFileSystem { + async fn init(&self) -> Result<()> { + self.file_entry_manager.write().await.insert( + Self::ROOT_DIR_PARENT_FILE_ID, + Self::ROOT_DIR_FILE_ID, + Self::ROOT_DIR_NAME, + ); + self.fs.init().await + } + + async fn get_file_path(&self, file_id: u64) -> String { + let file = self.get_file_node(file_id).await; + file.map(|x| x.file_name).unwrap_or_else(|_| "".to_string()) + } + + async fn valid_file_id(&self, _file_id: u64, fh: u64) -> Result<()> { + let file_id = self + .opened_file_manager + .get_file(fh) + .ok_or(Errno::from(libc::EBADF))? + .lock() + .await + .file_stat + .file_id; + + (file_id == _file_id) + .then_some(()) + .ok_or(Errno::from(libc::EBADF)) + } + + async fn stat(&self, file_id: u64) -> Result { + let file_node = self.get_file_node(file_id).await?; + let mut stat = self.fs.stat(&file_node.file_name).await?; + stat.set_file_id(file_node.parent_file_id, file_node.file_id); + Ok(stat) + } + + async fn lookup(&self, parent_file_id: u64, name: &str) -> Result { + let parent_file_node = self.get_file_node(parent_file_id).await?; + let mut stat = self.fs.lookup(&parent_file_node.file_name, name).await?; + self.fill_file_id(&mut stat, parent_file_id).await; + Ok(stat) + } + + async fn read_dir(&self, file_id: u64) -> Result> { + let file_node = self.get_file_node(file_id).await?; + let mut files = self.fs.read_dir(&file_node.file_name).await?; + for file in files.iter_mut() { + self.fill_file_id(file, file_node.file_id).await; + } + Ok(files) + } + + async fn open_file(&self, file_id: u64, flags: u32) -> Result { + self.open_file_internal(file_id, flags, FileType::RegularFile) + .await + } + + async fn open_dir(&self, file_id: u64, flags: u32) -> Result { + self.open_file_internal(file_id, flags, FileType::Directory) + .await + } + + async fn create_file(&self, parent_file_id: u64, name: &str, flags: u32) -> Result { + let parent_node = self.get_file_node(parent_file_id).await?; + let mut file = self + .fs + .create_file(&parent_node.file_name, name, OpenFileFlags(flags)) + .await?; + + file.set_file_id(parent_file_id, self.next_inode_id()); + { + let mut file_node_manager = self.file_entry_manager.write().await; + file_node_manager.insert( + file.file_stat.parent_file_id, + file.file_stat.file_id, + &file.file_stat.path, + ); + } + let file = self.opened_file_manager.put_file(file); + let file = file.lock().await; + Ok(file.file_handle()) + } + + async fn create_dir(&self, parent_file_id: u64, name: &str) -> Result { + let parent_node = self.get_file_node(parent_file_id).await?; + let mut file = self.fs.create_dir(&parent_node.file_name, name).await?; + + file.set_file_id(parent_file_id, self.next_inode_id()); + { + let mut file_node_manager = self.file_entry_manager.write().await; + file_node_manager.insert(file.parent_file_id, file.file_id, &file.path); + } + Ok(file.file_id) + } + + async fn set_attr(&self, file_id: u64, file_stat: &FileStat) -> Result<()> { + let file_node = self.get_file_node(file_id).await?; + self.fs + .set_attr(&file_node.file_name, file_stat, true) + .await + } + + async fn remove_file(&self, parent_file_id: u64, name: &str) -> Result<()> { + let parent_file_node = self.get_file_node(parent_file_id).await?; + self.fs + .remove_file(&parent_file_node.file_name, name) + .await?; + + { + let mut file_id_manager = self.file_entry_manager.write().await; + file_id_manager.remove(&join_file_path(&parent_file_node.file_name, name)); + } + Ok(()) + } + + async fn remove_dir(&self, parent_file_id: u64, name: &str) -> Result<()> { + let parent_file_node = self.get_file_node(parent_file_id).await?; + self.fs + .remove_dir(&parent_file_node.file_name, name) + .await?; + + { + let mut file_id_manager = self.file_entry_manager.write().await; + file_id_manager.remove(&join_file_path(&parent_file_node.file_name, name)); + } + Ok(()) + } + + async fn close_file(&self, _file_id: u64, fh: u64) -> Result<()> { + let file = self + .opened_file_manager + .remove_file(fh) + .ok_or(Errno::from(libc::EBADF))?; + let mut file = file.lock().await; + file.close().await?; + Ok(()) + } + + async fn read(&self, _file_id: u64, fh: u64, offset: u64, size: u32) -> Result { + let file_stat: FileStat; + let data = { + let opened_file = self + .opened_file_manager + .get_file(fh) + .ok_or(Errno::from(libc::EBADF))?; + let mut opened_file = opened_file.lock().await; + file_stat = opened_file.file_stat.clone(); + opened_file.read(offset, size).await + }; + + self.fs.set_attr(&file_stat.path, &file_stat, false).await?; + + data + } + + async fn write(&self, _file_id: u64, fh: u64, offset: u64, data: &[u8]) -> Result { + let (len, file_stat) = { + let opened_file = self + .opened_file_manager + .get_file(fh) + .ok_or(Errno::from(libc::EBADF))?; + let mut opened_file = opened_file.lock().await; + let len = opened_file.write(offset, data).await; + (len, opened_file.file_stat.clone()) + }; + + self.fs.set_attr(&file_stat.path, &file_stat, false).await?; + + len + } +} + +/// File entry is represent the abstract file. +#[derive(Debug, Clone)] +struct FileEntry { + file_id: u64, + parent_file_id: u64, + file_name: String, +} + +/// FileEntryManager is manage all the file entries in memory. it is used manger the file relationship and name mapping. +struct FileEntryManager { + // file_id_map is a map of file_id to file name. + file_id_map: HashMap, + + // file_name_map is a map of file name to file id. + file_name_map: HashMap, +} + +impl FileEntryManager { + fn new() -> Self { + Self { + file_id_map: HashMap::new(), + file_name_map: HashMap::new(), + } + } + + fn get_file_by_id(&self, file_id: u64) -> Option { + self.file_id_map.get(&file_id).cloned() + } + + fn get_file_by_name(&self, file_name: &str) -> Option { + self.file_name_map.get(file_name).cloned() + } + + fn insert(&mut self, parent_file_id: u64, file_id: u64, file_name: &str) { + let file_node = FileEntry { + file_id, + parent_file_id, + file_name: file_name.to_string(), + }; + self.file_id_map.insert(file_id, file_node.clone()); + self.file_name_map.insert(file_name.to_string(), file_node); + } + + fn remove(&mut self, file_name: &str) { + if let Some(node) = self.file_name_map.remove(file_name) { + self.file_id_map.remove(&node.file_id); + } + } +} diff --git a/clients/filesystem-fuse/src/fuse_api_handle.rs b/clients/filesystem-fuse/src/fuse_api_handle.rs index 8c065df0227..800930f4ed3 100644 --- a/clients/filesystem-fuse/src/fuse_api_handle.rs +++ b/clients/filesystem-fuse/src/fuse_api_handle.rs @@ -401,6 +401,10 @@ impl Filesystem for FuseApiHandle { const fn fstat_to_file_attr(file_st: &FileStat, context: &FileSystemContext) -> FileAttr { debug_assert!(file_st.file_id != 0 && file_st.parent_file_id != 0); + let perm = match file_st.kind { + Directory => context.default_dir_perm, + _ => context.default_file_perm, + }; FileAttr { ino: file_st.file_id, size: file_st.size, @@ -409,7 +413,7 @@ const fn fstat_to_file_attr(file_st: &FileStat, context: &FileSystemContext) -> mtime: file_st.mtime, ctime: file_st.ctime, kind: file_st.kind, - perm: file_st.perm, + perm: perm, nlink: file_st.nlink, uid: context.uid, gid: context.gid, @@ -469,7 +473,6 @@ mod test { path: "".to_string(), size: 10032, kind: FileType::RegularFile, - perm: 0, atime: Timestamp { sec: 10, nsec: 3 }, mtime: Timestamp { sec: 12, nsec: 5 }, ctime: Timestamp { sec: 15, nsec: 7 }, diff --git a/clients/filesystem-fuse/src/lib.rs b/clients/filesystem-fuse/src/lib.rs index 54fb59a5107..b531df82f79 100644 --- a/clients/filesystem-fuse/src/lib.rs +++ b/clients/filesystem-fuse/src/lib.rs @@ -18,3 +18,5 @@ */ mod filesystem; mod fuse_api_handle; +mod opened_file_manager; +mod utils; diff --git a/clients/filesystem-fuse/src/main.rs b/clients/filesystem-fuse/src/main.rs index f6a7e69ec67..24358840df5 100644 --- a/clients/filesystem-fuse/src/main.rs +++ b/clients/filesystem-fuse/src/main.rs @@ -18,6 +18,8 @@ */ mod filesystem; mod fuse_api_handle; +mod opened_file_manager; +mod utils; use log::debug; use log::info; diff --git a/clients/filesystem-fuse/src/opened_file_manager.rs b/clients/filesystem-fuse/src/opened_file_manager.rs new file mode 100644 index 00000000000..664f0a89875 --- /dev/null +++ b/clients/filesystem-fuse/src/opened_file_manager.rs @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +use crate::filesystem::OpenedFile; +use dashmap::DashMap; +use std::sync::atomic::AtomicU64; +use std::sync::Arc; +use tokio::sync::Mutex; + +// OpenedFileManager is a manager all the opened files. and allocate a file handle id for the opened file. +pub(crate) struct OpenedFileManager { + // file_handle_map is a map of file_handle_id to opened file. + file_handle_map: DashMap>>, + + // file_handle_id_generator is used to generate unique file handle IDs. + handle_id_generator: AtomicU64, +} + +impl OpenedFileManager { + pub fn new() -> Self { + Self { + file_handle_map: Default::default(), + handle_id_generator: AtomicU64::new(1), + } + } + + pub(crate) fn next_handle_id(&self) -> u64 { + self.handle_id_generator + .fetch_add(1, std::sync::atomic::Ordering::SeqCst) + } + + pub(crate) fn put_file(&self, mut file: OpenedFile) -> Arc> { + let file_handle_id = self.next_handle_id(); + file.handle_id = file_handle_id; + let file_handle = Arc::new(Mutex::new(file)); + self.file_handle_map + .insert(file_handle_id, file_handle.clone()); + file_handle + } + + pub(crate) fn get_file(&self, handle_id: u64) -> Option>> { + self.file_handle_map + .get(&handle_id) + .map(|x| x.value().clone()) + } + + pub(crate) fn remove_file(&self, handle_id: u64) -> Option>> { + self.file_handle_map.remove(&handle_id).map(|x| x.1) + } +} diff --git a/clients/filesystem-fuse/src/utils.rs b/clients/filesystem-fuse/src/utils.rs new file mode 100644 index 00000000000..6118b672362 --- /dev/null +++ b/clients/filesystem-fuse/src/utils.rs @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +// join the parent and name to a path +pub fn join_file_path(parent: &str, name: &str) -> String { + if parent.is_empty() { + name.to_string() + } else { + format!("{}/{}", parent, name) + } +} + +// split the path to parent and name +pub fn split_file_path(path: &str) -> (&str, &str) { + match path.rfind('/') { + Some(pos) => (&path[..pos], &path[pos + 1..]), + None => ("", path), + } +}