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(&current, &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);

Reply via email to