This is an automated email from the ASF dual-hosted git repository.
fanng pushed a commit to branch branch-gvfs-fuse-dev
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/branch-gvfs-fuse-dev by this
push:
new b238873df [#5734] feat (gvfs-fuse): Gvfs-fuse basic FUSE-level
implementation and code structure layout (#5835)
b238873df is described below
commit b238873df79246b2d4721738f22d25940eaca6c7
Author: Yuhui <[email protected]>
AuthorDate: Mon Dec 16 11:56:59 2024 +0800
[#5734] feat (gvfs-fuse): Gvfs-fuse basic FUSE-level implementation and
code structure layout (#5835)
### What changes were proposed in this pull request?
1. Implement basic FUSE interfaces.
2. Implement filesystem trait and relation structures.
### Why are the changes needed?
Fix: #5734
### Does this PR introduce _any_ user-facing change?
No
### How was this patch tested?
No
---
.github/workflows/gvfs-fuse-build-test.yml | 89 ++++
clients/filesystem-fuse/.cargo/config.toml | 2 +-
clients/filesystem-fuse/Cargo.toml | 10 +-
.../{.cargo/config.toml => rust-toolchain.toml} | 7 +-
clients/filesystem-fuse/src/filesystem.rs | 241 ++++++++++
clients/filesystem-fuse/src/fuse_api_handle.rs | 507 +++++++++++++++++++++
clients/filesystem-fuse/src/{main.rs => lib.rs} | 14 +-
clients/filesystem-fuse/src/main.rs | 4 +-
8 files changed, 855 insertions(+), 19 deletions(-)
diff --git a/.github/workflows/gvfs-fuse-build-test.yml
b/.github/workflows/gvfs-fuse-build-test.yml
new file mode 100644
index 000000000..4af01d82d
--- /dev/null
+++ b/.github/workflows/gvfs-fuse-build-test.yml
@@ -0,0 +1,89 @@
+name: Build gvfs-fuse and testing
+
+# Controls when the workflow will run
+on:
+ push:
+ branches: [ "main", "branch-*" ]
+ pull_request:
+ branches: [ "main", "branch-*" ]
+ workflow_dispatch:
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number ||
github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ changes:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: dorny/paths-filter@v2
+ id: filter
+ with:
+ filters: |
+ source_changes:
+ - .github/**
+ - api/**
+ - bin/**
+ - catalogs/hadoop/**
+ - clients/filesystem-fuse/**
+ - common/**
+ - conf/**
+ - core/**
+ - dev/**
+ - gradle/**
+ - meta/**
+ - scripts/**
+ - server/**
+ - server-common/**
+ - build.gradle.kts
+ - gradle.properties
+ - gradlew
+ - setting.gradle.kts
+ outputs:
+ source_changes: ${{ steps.filter.outputs.source_changes }}
+
+ # Build for AMD64 architecture
+ Gvfs-Build:
+ needs: changes
+ if: needs.changes.outputs.source_changes == 'true'
+ runs-on: ubuntu-latest
+ timeout-minutes: 60
+ strategy:
+ matrix:
+ architecture: [linux/amd64]
+ java-version: [ 17 ]
+ env:
+ PLATFORM: ${{ matrix.architecture }}
+ steps:
+ - uses: actions/checkout@v3
+
+ - uses: actions/setup-java@v4
+ with:
+ java-version: ${{ matrix.java-version }}
+ distribution: 'temurin'
+ cache: 'gradle'
+
+ - name: Set up QEMU
+ uses: docker/setup-qemu-action@v2
+
+ - name: Check required command
+ run: |
+ dev/ci/check_commands.sh
+
+ - name: Build and test Gravitino
+ run: |
+ ./gradlew :clients:filesystem-fuse:build -PenableFuse=true
+
+ - name: Free up disk space
+ run: |
+ dev/ci/util_free_space.sh
+
+ - name: Upload tests reports
+ uses: actions/upload-artifact@v3
+ if: ${{ (failure() && steps.integrationTest.outcome == 'failure') ||
contains(github.event.pull_request.labels.*.name, 'upload log') }}
+ with:
+ name: Gvfs-fuse integrate-test-reports-${{ matrix.java-version }}
+ path: |
+ clients/filesystem-fuse/build/test/log/*.log
+
diff --git a/clients/filesystem-fuse/.cargo/config.toml
b/clients/filesystem-fuse/.cargo/config.toml
index 37751e880..78bc9f7fe 100644
--- a/clients/filesystem-fuse/.cargo/config.toml
+++ b/clients/filesystem-fuse/.cargo/config.toml
@@ -17,4 +17,4 @@
[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 1b186d61c..2883cecc6 100644
--- a/clients/filesystem-fuse/Cargo.toml
+++ b/clients/filesystem-fuse/Cargo.toml
@@ -29,9 +29,15 @@ repository = "https://github.com/apache/gravitino"
name = "gvfs-fuse"
path = "src/main.rs"
+[lib]
+name="gvfs_fuse"
+
[dependencies]
+async-trait = "0.1"
+bytes = "1.6.0"
futures-util = "0.3.30"
-libc = "0.2.164"
+fuse3 = { version = "0.8.1", "features" = ["tokio-runtime", "unprivileged"] }
log = "0.4.22"
tokio = { version = "1.38.0", features = ["full"] }
-tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
\ No newline at end of file
+tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
+
diff --git a/clients/filesystem-fuse/.cargo/config.toml
b/clients/filesystem-fuse/rust-toolchain.toml
similarity index 88%
copy from clients/filesystem-fuse/.cargo/config.toml
copy to clients/filesystem-fuse/rust-toolchain.toml
index 37751e880..a7cf73787 100644
--- a/clients/filesystem-fuse/.cargo/config.toml
+++ b/clients/filesystem-fuse/rust-toolchain.toml
@@ -15,6 +15,7 @@
# specific language governing permissions and limitations
# under the License.
-[build]
-target-dir = "build"
-
+[toolchain]
+channel = "1.82.0"
+components = ["rustfmt", "clippy", "rust-src"]
+profile = "default"
diff --git a/clients/filesystem-fuse/src/filesystem.rs
b/clients/filesystem-fuse/src/filesystem.rs
new file mode 100644
index 000000000..6d1d8fa25
--- /dev/null
+++ b/clients/filesystem-fuse/src/filesystem.rs
@@ -0,0 +1,241 @@
+/*
+ * 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 async_trait::async_trait;
+use bytes::Bytes;
+use fuse3::{Errno, FileType, Timestamp};
+
+pub(crate) type Result<T> = std::result::Result<T, Errno>;
+
+/// RawFileSystem interface for the file system implementation. it use by
FuseApiHandle,
+/// it ues the file id to operate the file system apis
+/// the `file_id` and `parent_file_id` it is the unique identifier for the
file system,
+/// it is used to identify the file or directory
+/// the `handle_id` it is the file handle, it is used to identify the opened
file,
+/// it is used to read or write the file content
+/// the `file id` and `handle_id` need to mapping the `ino`/`inode` and `fh`
in the fuse3
+#[async_trait]
+pub(crate) trait RawFileSystem: Send + Sync {
+ /// Init the file system
+ async fn init(&self) -> Result<()>;
+
+ /// Get the file path by file id, if the file id is valid, return the file
path
+ async fn get_file_path(&self, file_id: u64) -> String;
+
+ /// Validate the file id and file handle, if file id and file handle is
valid and it associated, return Ok
+ async fn valid_file_id(&self, file_id: u64, fh: u64) -> Result<()>;
+
+ /// Get the file stat by file id. if the file id is valid, return the file
stat
+ async fn stat(&self, file_id: u64) -> Result<FileStat>;
+
+ /// Lookup the file by parent file id and file name, if the file is exist,
return the file stat
+ async fn lookup(&self, parent_file_id: u64, name: &str) ->
Result<FileStat>;
+
+ /// Read the directory by file id, if the file id is a valid directory,
return the file stat list
+ async fn read_dir(&self, dir_file_id: u64) -> Result<Vec<FileStat>>;
+
+ /// Open the file by file id and flags, if the file id is a valid file,
return the file handle
+ async fn open_file(&self, file_id: u64, flags: u32) -> Result<FileHandle>;
+
+ /// Open the directory by file id and flags, if successful, return the
file handle
+ async fn open_dir(&self, file_id: u64, flags: u32) -> Result<FileHandle>;
+
+ /// Create the file by parent file id and file name and flags, if
successful, return the file handle
+ async fn create_file(&self, parent_file_id: u64, name: &str, flags: u32)
-> Result<FileHandle>;
+
+ /// Create the directory by parent file id and file name, if successful,
return the file id
+ async fn create_dir(&self, parent_file_id: u64, name: &str) -> Result<u64>;
+
+ /// Set the file attribute by file id and file stat
+ async fn set_attr(&self, file_id: u64, file_stat: &FileStat) -> Result<()>;
+
+ /// Remove the file by parent file id and file name
+ async fn remove_file(&self, parent_file_id: u64, name: &str) -> Result<()>;
+
+ /// Remove the directory by parent file id and file name
+ async fn remove_dir(&self, parent_file_id: u64, name: &str) -> Result<()>;
+
+ /// Close the file by file id and file handle, if successful
+ async fn close_file(&self, file_id: u64, fh: u64) -> Result<()>;
+
+ /// Read the file content by file id, file handle, offset and size, if
successful, return the read result
+ async fn read(&self, file_id: u64, fh: u64, offset: u64, size: u32) ->
Result<Bytes>;
+
+ /// Write the file content by file id, file handle, offset and data, if
successful, return the written size
+ async fn write(&self, file_id: u64, fh: u64, offset: u64, data: &[u8]) ->
Result<u32>;
+}
+
+/// PathFileSystem is the interface for the file system implementation, it use
to interact with other file system
+/// it is used file path to operate the file system
+#[async_trait]
+pub(crate) trait PathFileSystem: Send + Sync {
+ /// Init the file system
+ async fn init(&self) -> Result<()>;
+
+ /// Get the file stat by file path, if the file is exist, return the file
stat
+ async fn stat(&self, name: &str) -> Result<FileStat>;
+
+ /// Get the file stat by parent file path and file name, if the file is
exist, return the file stat
+ async fn lookup(&self, parent: &str, name: &str) -> Result<FileStat>;
+
+ /// Read the directory by file path, if the file is a valid directory,
return the file stat list
+ async fn read_dir(&self, name: &str) -> Result<Vec<FileStat>>;
+
+ /// Open the file by file path and flags, if the file is exist, return the
opened file
+ async fn open_file(&self, name: &str, flags: OpenFileFlags) ->
Result<OpenedFile>;
+
+ /// Open the directory by file path and flags, if the file is exist,
return the opened file
+ async fn open_dir(&self, name: &str, flags: OpenFileFlags) ->
Result<OpenedFile>;
+
+ /// Create the file by parent file path and file name and flags, if
successful, return the opened file
+ async fn create_file(
+ &self,
+ parent: &str,
+ name: &str,
+ flags: OpenFileFlags,
+ ) -> Result<OpenedFile>;
+
+ /// Create the directory by parent file path and file name, if successful,
return the file stat
+ async fn create_dir(&self, parent: &str, name: &str) -> Result<FileStat>;
+
+ /// Set the file attribute by file path and file stat
+ async fn set_attr(&self, name: &str, file_stat: &FileStat, flush: bool) ->
Result<()>;
+
+ /// Remove the file by parent file path and file name
+ async fn remove_file(&self, parent: &str, name: &str) -> Result<()>;
+
+ /// Remove the directory by parent file path and file name
+ async fn remove_dir(&self, parent: &str, name: &str) -> Result<()>;
+}
+
+// FileSystemContext is the system environment for the fuse file system.
+pub(crate) struct FileSystemContext {
+ // system user id
+ pub(crate) uid: u32,
+
+ // system group id
+ pub(crate) gid: u32,
+
+ // default file permission
+ pub(crate) default_file_perm: u16,
+
+ // default idr permission
+ pub(crate) default_dir_perm: u16,
+
+ // io block size
+ pub(crate) block_size: u32,
+}
+
+impl FileSystemContext {
+ pub(crate) fn new(uid: u32, gid: u32) -> Self {
+ FileSystemContext {
+ uid,
+ gid,
+ default_file_perm: 0o644,
+ default_dir_perm: 0o755,
+ block_size: 4 * 1024,
+ }
+ }
+}
+
+// FileStat is the file metadata of the file
+#[derive(Clone, Debug)]
+pub struct FileStat {
+ // file id for the file system.
+ pub(crate) file_id: u64,
+
+ // parent file id
+ pub(crate) parent_file_id: u64,
+
+ // file name
+ pub(crate) name: String,
+
+ // file path of the fuse file system root
+ pub(crate) path: String,
+
+ // file size
+ pub(crate) size: u64,
+
+ // 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,
+
+ // file modify time
+ pub(crate) mtime: Timestamp,
+
+ // file create time
+ pub(crate) ctime: Timestamp,
+
+ // file link count
+ pub(crate) nlink: u32,
+}
+
+/// 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,
+
+ pub(crate) handle_id: u64,
+
+ pub reader: Option<Box<dyn FileReader>>,
+
+ pub writer: Option<Box<dyn FileWriter>>,
+}
+
+// FileHandle is the file handle for the opened file.
+pub(crate) struct FileHandle {
+ pub(crate) file_id: u64,
+
+ pub(crate) handle_id: u64,
+}
+
+// OpenFileFlags is the open file flags for the file system.
+pub struct OpenFileFlags(u32);
+
+/// File reader interface for read file content
+#[async_trait]
+pub(crate) trait FileReader: Sync + Send {
+ /// read the file content by offset and size, if successful, return the
read result
+ async fn read(&mut self, offset: u64, size: u32) -> Result<Bytes>;
+
+ /// close the file
+ async fn close(&mut self) -> Result<()> {
+ Ok(())
+ }
+}
+
+/// File writer interface for write file content
+#[async_trait]
+pub trait FileWriter: Sync + Send {
+ /// write the file content by offset and data, if successful, return the
written size
+ async fn write(&mut self, offset: u64, data: &[u8]) -> Result<u32>;
+
+ /// close the file
+ async fn close(&mut self) -> Result<()> {
+ Ok(())
+ }
+
+ /// flush the file
+ async fn flush(&mut self) -> Result<()> {
+ Ok(())
+ }
+}
diff --git a/clients/filesystem-fuse/src/fuse_api_handle.rs
b/clients/filesystem-fuse/src/fuse_api_handle.rs
new file mode 100644
index 000000000..8c065df02
--- /dev/null
+++ b/clients/filesystem-fuse/src/fuse_api_handle.rs
@@ -0,0 +1,507 @@
+/*
+ * 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::{FileStat, FileSystemContext, RawFileSystem};
+use fuse3::path::prelude::{ReplyData, ReplyOpen, ReplyStatFs, ReplyWrite};
+use fuse3::path::Request;
+use fuse3::raw::prelude::{
+ FileAttr, ReplyAttr, ReplyCreated, ReplyDirectory, ReplyDirectoryPlus,
ReplyEntry, ReplyInit,
+};
+use fuse3::raw::reply::{DirectoryEntry, DirectoryEntryPlus};
+use fuse3::raw::Filesystem;
+use fuse3::FileType::{Directory, RegularFile};
+use fuse3::{Errno, FileType, Inode, SetAttr, Timestamp};
+use futures_util::stream;
+use futures_util::stream::BoxStream;
+use futures_util::StreamExt;
+use std::ffi::{OsStr, OsString};
+use std::num::NonZeroU32;
+use std::time::{Duration, SystemTime};
+
+pub(crate) struct FuseApiHandle<T: RawFileSystem> {
+ fs: T,
+ default_ttl: Duration,
+ fs_context: FileSystemContext,
+}
+
+impl<T: RawFileSystem> FuseApiHandle<T> {
+ const DEFAULT_ATTR_TTL: Duration = Duration::from_secs(1);
+ const DEFAULT_MAX_WRITE_SIZE: u32 = 16 * 1024;
+
+ pub fn new(fs: T, context: FileSystemContext) -> Self {
+ Self {
+ fs: fs,
+ default_ttl: Self::DEFAULT_ATTR_TTL,
+ fs_context: context,
+ }
+ }
+
+ pub async fn get_file_path(&self, file_id: u64) -> String {
+ self.fs.get_file_path(file_id).await
+ }
+
+ async fn get_modified_file_stat(
+ &self,
+ file_id: u64,
+ size: Option<u64>,
+ atime: Option<Timestamp>,
+ mtime: Option<Timestamp>,
+ ) -> Result<FileStat, Errno> {
+ let mut file_stat = self.fs.stat(file_id).await?;
+
+ if let Some(size) = size {
+ file_stat.size = size;
+ };
+
+ if let Some(atime) = atime {
+ file_stat.atime = atime;
+ };
+
+ if let Some(mtime) = mtime {
+ file_stat.mtime = mtime;
+ };
+
+ Ok(file_stat)
+ }
+}
+
+impl<T: RawFileSystem> Filesystem for FuseApiHandle<T> {
+ async fn init(&self, _req: Request) -> fuse3::Result<ReplyInit> {
+ self.fs.init().await?;
+ Ok(ReplyInit {
+ max_write: NonZeroU32::new(Self::DEFAULT_MAX_WRITE_SIZE).unwrap(),
+ })
+ }
+
+ async fn destroy(&self, _req: Request) {
+ //TODO need to call the destroy method of the local_fs
+ }
+
+ async fn lookup(
+ &self,
+ _req: Request,
+ parent: Inode,
+ name: &OsStr,
+ ) -> fuse3::Result<ReplyEntry> {
+ let name = name.to_string_lossy();
+ let file_stat = self.fs.lookup(parent, &name).await?;
+ Ok(ReplyEntry {
+ ttl: self.default_ttl,
+ attr: fstat_to_file_attr(&file_stat, &self.fs_context),
+ generation: 0,
+ })
+ }
+
+ async fn getattr(
+ &self,
+ _req: Request,
+ inode: Inode,
+ fh: Option<u64>,
+ _flags: u32,
+ ) -> fuse3::Result<ReplyAttr> {
+ // check the fh is associated with the file_id
+ if let Some(fh) = fh {
+ self.fs.valid_file_id(inode, fh).await?;
+ }
+
+ let file_stat = self.fs.stat(inode).await?;
+ Ok(ReplyAttr {
+ ttl: self.default_ttl,
+ attr: fstat_to_file_attr(&file_stat, &self.fs_context),
+ })
+ }
+
+ async fn setattr(
+ &self,
+ _req: Request,
+ inode: Inode,
+ fh: Option<u64>,
+ set_attr: SetAttr,
+ ) -> fuse3::Result<ReplyAttr> {
+ // check the fh is associated with the file_id
+ if let Some(fh) = fh {
+ self.fs.valid_file_id(inode, fh).await?;
+ }
+
+ let new_file_stat = self
+ .get_modified_file_stat(inode, set_attr.size, set_attr.atime,
set_attr.mtime)
+ .await?;
+ let attr = fstat_to_file_attr(&new_file_stat, &self.fs_context);
+ self.fs.set_attr(inode, &new_file_stat).await?;
+ Ok(ReplyAttr {
+ ttl: self.default_ttl,
+ attr: attr,
+ })
+ }
+
+ async fn mkdir(
+ &self,
+ _req: Request,
+ parent: Inode,
+ name: &OsStr,
+ _mode: u32,
+ _umask: u32,
+ ) -> fuse3::Result<ReplyEntry> {
+ let name = name.to_string_lossy();
+ let handle_id = self.fs.create_dir(parent, &name).await?;
+ Ok(ReplyEntry {
+ ttl: self.default_ttl,
+ attr: dummy_file_attr(
+ handle_id,
+ Directory,
+ Timestamp::from(SystemTime::now()),
+ &self.fs_context,
+ ),
+ generation: 0,
+ })
+ }
+
+ async fn unlink(&self, _req: Request, parent: Inode, name: &OsStr) ->
fuse3::Result<()> {
+ let name = name.to_string_lossy();
+ self.fs.remove_file(parent, &name).await?;
+ Ok(())
+ }
+
+ async fn rmdir(&self, _req: Request, parent: Inode, name: &OsStr) ->
fuse3::Result<()> {
+ let name = name.to_string_lossy();
+ self.fs.remove_dir(parent, &name).await?;
+ Ok(())
+ }
+
+ async fn open(&self, _req: Request, inode: Inode, flags: u32) ->
fuse3::Result<ReplyOpen> {
+ let file_handle = self.fs.open_file(inode, flags).await?;
+ Ok(ReplyOpen {
+ fh: file_handle.handle_id,
+ flags: flags,
+ })
+ }
+
+ async fn read(
+ &self,
+ _req: Request,
+ inode: Inode,
+ fh: u64,
+ offset: u64,
+ size: u32,
+ ) -> fuse3::Result<ReplyData> {
+ let data = self.fs.read(inode, fh, offset, size).await?;
+ Ok(ReplyData { data: data })
+ }
+
+ async fn write(
+ &self,
+ _req: Request,
+ inode: Inode,
+ fh: u64,
+ offset: u64,
+ data: &[u8],
+ _write_flags: u32,
+ _flags: u32,
+ ) -> fuse3::Result<ReplyWrite> {
+ let written = self.fs.write(inode, fh, offset, data).await?;
+ Ok(ReplyWrite { written: written })
+ }
+
+ async fn statfs(&self, _req: Request, _inode: Inode) ->
fuse3::Result<ReplyStatFs> {
+ //TODO: Implement statfs for the filesystem
+ Ok(ReplyStatFs {
+ blocks: 1000000,
+ bfree: 1000000,
+ bavail: 1000000,
+ files: 1000000,
+ ffree: 1000000,
+ bsize: 4096,
+ namelen: 255,
+ frsize: 4096,
+ })
+ }
+
+ async fn release(
+ &self,
+ _eq: Request,
+ inode: Inode,
+ fh: u64,
+ _flags: u32,
+ _lock_owner: u64,
+ _flush: bool,
+ ) -> fuse3::Result<()> {
+ self.fs.close_file(inode, fh).await
+ }
+
+ async fn opendir(&self, _req: Request, inode: Inode, flags: u32) ->
fuse3::Result<ReplyOpen> {
+ let file_handle = self.fs.open_dir(inode, flags).await?;
+ Ok(ReplyOpen {
+ fh: file_handle.handle_id,
+ flags: flags,
+ })
+ }
+
+ type DirEntryStream<'a>
+ = BoxStream<'a, fuse3::Result<DirectoryEntry>>
+ where
+ T: 'a;
+
+ #[allow(clippy::needless_lifetimes)]
+ async fn readdir<'a>(
+ &'a self,
+ _req: Request,
+ parent: Inode,
+ _fh: u64,
+ offset: i64,
+ ) -> fuse3::Result<ReplyDirectory<Self::DirEntryStream<'a>>> {
+ let current = self.fs.stat(parent).await?;
+ let files = self.fs.read_dir(parent).await?;
+ let entries_stream =
+ stream::iter(files.into_iter().enumerate().map(|(index,
file_stat)| {
+ Ok(DirectoryEntry {
+ inode: file_stat.file_id,
+ name: file_stat.name.clone().into(),
+ kind: file_stat.kind,
+ offset: (index + 3) as i64,
+ })
+ }));
+
+ let relative_paths = stream::iter([
+ Ok(DirectoryEntry {
+ inode: current.file_id,
+ name: ".".into(),
+ kind: Directory,
+ offset: 1,
+ }),
+ Ok(DirectoryEntry {
+ inode: current.parent_file_id,
+ name: "..".into(),
+ kind: Directory,
+ offset: 2,
+ }),
+ ]);
+
+ //TODO Need to improve the read dir operation
+ let combined_stream = relative_paths.chain(entries_stream);
+ Ok(ReplyDirectory {
+ entries: combined_stream.skip(offset as usize).boxed(),
+ })
+ }
+
+ async fn releasedir(
+ &self,
+ _req: Request,
+ inode: Inode,
+ fh: u64,
+ _flags: u32,
+ ) -> fuse3::Result<()> {
+ self.fs.close_file(inode, fh).await
+ }
+
+ async fn create(
+ &self,
+ _req: Request,
+ parent: Inode,
+ name: &OsStr,
+ _mode: u32,
+ flags: u32,
+ ) -> fuse3::Result<ReplyCreated> {
+ let name = name.to_string_lossy();
+ let file_handle = self.fs.create_file(parent, &name, flags).await?;
+ Ok(ReplyCreated {
+ ttl: self.default_ttl,
+ attr: dummy_file_attr(
+ file_handle.file_id,
+ RegularFile,
+ Timestamp::from(SystemTime::now()),
+ &self.fs_context,
+ ),
+ generation: 0,
+ fh: file_handle.handle_id,
+ flags: flags,
+ })
+ }
+
+ type DirEntryPlusStream<'a>
+ = BoxStream<'a, fuse3::Result<DirectoryEntryPlus>>
+ where
+ T: 'a;
+
+ #[allow(clippy::needless_lifetimes)]
+ async fn readdirplus<'a>(
+ &'a self,
+ _req: Request,
+ parent: Inode,
+ _fh: u64,
+ offset: u64,
+ _lock_owner: u64,
+ ) -> fuse3::Result<ReplyDirectoryPlus<Self::DirEntryPlusStream<'a>>> {
+ let current = self.fs.stat(parent).await?;
+ let files = self.fs.read_dir(parent).await?;
+ let entries_stream =
+ stream::iter(files.into_iter().enumerate().map(|(index,
file_stat)| {
+ Ok(DirectoryEntryPlus {
+ inode: file_stat.file_id,
+ name: file_stat.name.clone().into(),
+ kind: file_stat.kind,
+ offset: (index + 3) as i64,
+ attr: fstat_to_file_attr(&file_stat, &self.fs_context),
+ generation: 0,
+ entry_ttl: self.default_ttl,
+ attr_ttl: self.default_ttl,
+ })
+ }));
+
+ let relative_paths = stream::iter([
+ Ok(DirectoryEntryPlus {
+ inode: current.file_id,
+ name: OsString::from("."),
+ kind: Directory,
+ offset: 1,
+ attr: fstat_to_file_attr(¤t, &self.fs_context),
+ generation: 0,
+ entry_ttl: self.default_ttl,
+ attr_ttl: self.default_ttl,
+ }),
+ Ok(DirectoryEntryPlus {
+ inode: current.parent_file_id,
+ name: OsString::from(".."),
+ kind: Directory,
+ offset: 2,
+ attr: dummy_file_attr(
+ current.parent_file_id,
+ Directory,
+ Timestamp::from(SystemTime::now()),
+ &self.fs_context,
+ ),
+ generation: 0,
+ entry_ttl: self.default_ttl,
+ attr_ttl: self.default_ttl,
+ }),
+ ]);
+
+ //TODO Need to improve the read dir operation
+ let combined_stream = relative_paths.chain(entries_stream);
+ Ok(ReplyDirectoryPlus {
+ entries: combined_stream.skip(offset as usize).boxed(),
+ })
+ }
+}
+
+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);
+ FileAttr {
+ ino: file_st.file_id,
+ size: file_st.size,
+ blocks: (file_st.size + context.block_size as u64 - 1) /
context.block_size as u64,
+ atime: file_st.atime,
+ mtime: file_st.mtime,
+ ctime: file_st.ctime,
+ kind: file_st.kind,
+ perm: file_st.perm,
+ nlink: file_st.nlink,
+ uid: context.uid,
+ gid: context.gid,
+ rdev: 0,
+ blksize: context.block_size,
+ #[cfg(target_os = "macos")]
+ crtime: file_st.ctime,
+ #[cfg(target_os = "macos")]
+ flags: 0,
+ }
+}
+
+const fn dummy_file_attr(
+ file_id: u64,
+ kind: FileType,
+ now: Timestamp,
+ context: &FileSystemContext,
+) -> FileAttr {
+ debug_assert!(file_id != 0);
+ let mode = match kind {
+ Directory => context.default_dir_perm,
+ _ => context.default_file_perm,
+ };
+ FileAttr {
+ ino: file_id,
+ size: 0,
+ blocks: 1,
+ atime: now,
+ mtime: now,
+ ctime: now,
+ kind,
+ perm: mode,
+ nlink: 0,
+ uid: context.uid,
+ gid: context.gid,
+ rdev: 0,
+ blksize: context.block_size,
+ #[cfg(target_os = "macos")]
+ crtime: now,
+ #[cfg(target_os = "macos")]
+ flags: 0,
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::filesystem::{FileStat, FileSystemContext};
+ use crate::fuse_api_handle::fstat_to_file_attr;
+ use fuse3::{FileType, Timestamp};
+
+ #[test]
+ fn test_fstat_to_file_attr() {
+ let file_stat = FileStat {
+ file_id: 1,
+ parent_file_id: 3,
+ name: "test".to_string(),
+ 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 },
+ nlink: 0,
+ };
+
+ let context = FileSystemContext {
+ uid: 1,
+ gid: 2,
+ default_file_perm: 0o644,
+ default_dir_perm: 0o755,
+ block_size: 4 * 1024,
+ };
+
+ let file_attr = fstat_to_file_attr(&file_stat, &context);
+
+ assert_eq!(file_attr.ino, 1);
+ assert_eq!(file_attr.size, 10032);
+ assert_eq!(file_attr.blocks, 3);
+ assert_eq!(file_attr.atime, Timestamp { sec: 10, nsec: 3 });
+ assert_eq!(file_attr.mtime, Timestamp { sec: 12, nsec: 5 });
+ assert_eq!(file_attr.ctime, Timestamp { sec: 15, nsec: 7 });
+ assert_eq!(file_attr.kind, FileType::RegularFile);
+ assert_eq!(file_attr.perm, 0);
+ assert_eq!(file_attr.nlink, 0);
+ assert_eq!(file_attr.uid, 1);
+ assert_eq!(file_attr.gid, 2);
+ assert_eq!(file_attr.rdev, 0);
+ assert_eq!(file_attr.blksize, 4 * 1024);
+ #[cfg(target_os = "macos")]
+ assert_eq!(file_attr.crtime, Timestamp { sec: 15, nsec: 7 });
+ #[cfg(target_os = "macos")]
+ assert_eq!(file_attr.flags, 0);
+ }
+}
diff --git a/clients/filesystem-fuse/src/main.rs
b/clients/filesystem-fuse/src/lib.rs
similarity index 76%
copy from clients/filesystem-fuse/src/main.rs
copy to clients/filesystem-fuse/src/lib.rs
index 48b6ab551..54fb59a51 100644
--- a/clients/filesystem-fuse/src/main.rs
+++ b/clients/filesystem-fuse/src/lib.rs
@@ -16,15 +16,5 @@
* specific language governing permissions and limitations
* under the License.
*/
-
-use log::debug;
-use log::info;
-use std::process::exit;
-
-#[tokio::main]
-async fn main() {
- tracing_subscriber::fmt().with_env_filter("debug").init();
- info!("Starting filesystem...");
- debug!("Shutdown filesystem...");
- exit(0);
-}
+mod filesystem;
+mod fuse_api_handle;
diff --git a/clients/filesystem-fuse/src/main.rs
b/clients/filesystem-fuse/src/main.rs
index 48b6ab551..f6a7e69ec 100644
--- a/clients/filesystem-fuse/src/main.rs
+++ b/clients/filesystem-fuse/src/main.rs
@@ -16,6 +16,8 @@
* specific language governing permissions and limitations
* under the License.
*/
+mod filesystem;
+mod fuse_api_handle;
use log::debug;
use log::info;
@@ -23,7 +25,7 @@ use std::process::exit;
#[tokio::main]
async fn main() {
- tracing_subscriber::fmt().with_env_filter("debug").init();
+ tracing_subscriber::fmt().init();
info!("Starting filesystem...");
debug!("Shutdown filesystem...");
exit(0);