Browse Source

feat: impl fuse for ext4

liujingx 11 months ago
parent
commit
806a8275bf

+ 2 - 0
.gitignore

@@ -4,6 +4,8 @@
 /ext4_test/ext4.img
 
 /ext4_fuse/target
+/ext4_fuse/ext4.img
+/ext4_fuse/mnt
 
 /.idea
 /.vscode

+ 350 - 0
ext4_fuse/Cargo.lock

@@ -0,0 +1,350 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "bitflags"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "colored"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8"
+dependencies = [
+ "lazy_static",
+ "windows-sys",
+]
+
+[[package]]
+name = "deranged"
+version = "0.3.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
+dependencies = [
+ "powerfmt",
+]
+
+[[package]]
+name = "ext4_fuse"
+version = "0.1.0"
+dependencies = [
+ "ext4_rs",
+ "fuser",
+ "log",
+ "simple_logger",
+]
+
+[[package]]
+name = "ext4_rs"
+version = "0.1.0"
+dependencies = [
+ "bitflags",
+ "byteorder",
+ "log",
+]
+
+[[package]]
+name = "fuser"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21370f84640642c8ea36dfb2a6bfc4c55941f476fcf431f6fef25a5ddcf0169b"
+dependencies = [
+ "libc",
+ "log",
+ "memchr",
+ "page_size",
+ "pkg-config",
+ "smallvec",
+ "zerocopy",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.155"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
+
+[[package]]
+name = "log"
+version = "0.4.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
+
+[[package]]
+name = "memchr"
+version = "2.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
+
+[[package]]
+name = "num-conv"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
+
+[[package]]
+name = "num_threads"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "page_size"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b7663cbd190cfd818d08efa8497f6cd383076688c49a391ef7c0d03cd12b561"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "pkg-config"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
+
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.84"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.203"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.203"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "simple_logger"
+version = "4.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e7e46c8c90251d47d08b28b8a419ffb4aede0f87c2eea95e17d1d5bacbf3ef1"
+dependencies = [
+ "colored",
+ "log",
+ "time",
+ "windows-sys",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
+
+[[package]]
+name = "syn"
+version = "2.0.66"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "time"
+version = "0.3.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
+dependencies = [
+ "deranged",
+ "itoa",
+ "libc",
+ "num-conv",
+ "num_threads",
+ "powerfmt",
+ "serde",
+ "time-core",
+ "time-macros",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
+
+[[package]]
+name = "time-macros"
+version = "0.2.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf"
+dependencies = [
+ "num-conv",
+ "time-core",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "zerocopy"
+version = "0.6.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "854e949ac82d619ee9a14c66a1b674ac730422372ccb759ce0c39cabcf2bf8e6"
+dependencies = [
+ "byteorder",
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.6.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "125139de3f6b9d625c39e2efdd73d41bdac468ccd556556440e322be0e1bbd91"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]

+ 10 - 0
ext4_fuse/Cargo.toml

@@ -0,0 +1,10 @@
+[package]
+name = "ext4_fuse"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+fuser = "0.13"
+ext4_rs = { path = ".." }
+simple_logger = "4.3"
+log = "0.4"

+ 35 - 0
ext4_fuse/src/block_file.rs

@@ -0,0 +1,35 @@
+use ext4_rs::{Block, BlockDevice, BLOCK_SIZE};
+use std::fs::{File, OpenOptions};
+use std::io::{Read, Seek, SeekFrom, Write};
+
+#[derive(Debug)]
+pub struct BlockFile(File);
+
+impl BlockFile {
+    pub fn new(path: &str) -> Self {
+        let file = OpenOptions::new()
+            .read(true)
+            .write(true)
+            .open(path)
+            .unwrap();
+        Self(file)
+    }
+}
+
+impl BlockDevice for BlockFile {
+    fn read_block(&self, block_id: u64) -> Block {
+        let mut file = &self.0;
+        let mut buffer = [0u8; BLOCK_SIZE];
+        // warn!("read_block {}", block_id);
+        let _r = file.seek(SeekFrom::Start(block_id * BLOCK_SIZE as u64));
+        let _r = file.read_exact(&mut buffer);
+        Block::new(block_id, buffer)
+    }
+
+    fn write_block(&self, block: &Block) {
+        let mut file = &self.0;
+        // warn!("write_block {}", block.block_id);
+        let _r = file.seek(SeekFrom::Start(block.block_id * BLOCK_SIZE as u64));
+        let _r = file.write_all(&block.data);
+    }
+}

+ 93 - 0
ext4_fuse/src/common.rs

@@ -0,0 +1,93 @@
+use ext4_rs::{
+    DirEntry, FileType as Ext4FileType, InodeRef, OpenFlags, INODE_BLOCK_SIZE,
+};
+use fuser::{FileAttr, FileType};
+use std::time::{Duration, SystemTime};
+
+/// A wrapper of ext4_rs::InodeRef
+pub struct FuseInode(pub InodeRef);
+
+impl FuseInode {
+    pub fn get_attr(&self) -> FileAttr {
+        FileAttr {
+            ino: self.0.id as u64,
+            size: self.0.inode.size(),
+            blocks: self.0.inode.blocks_count(),
+            atime: get_time(self.0.inode.atime as u64),
+            mtime: get_time(self.0.inode.mtime as u64),
+            ctime: get_time(self.0.inode.ctime as u64),
+            crtime: SystemTime::UNIX_EPOCH,
+            kind: translate_ftype(self.0.inode.file_type()),
+            perm: self.0.inode.mode().perm_bits(),
+            nlink: self.0.inode.links_cnt() as u32,
+            uid: self.0.inode.uid as u32,
+            gid: self.0.inode.gid as u32,
+            rdev: 0,
+            blksize: INODE_BLOCK_SIZE as u32,
+            flags: 0,
+        }
+    }
+}
+
+/// File handler for fuse filesystem
+pub struct FileHandler {
+    pub id: u64,
+    pub inode: u32,
+    pub offset: usize,
+    pub flags: OpenFlags,
+}
+
+impl FileHandler {
+    pub fn new(id: u64, inode: u32, flags: OpenFlags) -> Self {
+        Self {
+            id,
+            inode,
+            offset: 0,
+            flags,
+        }
+    }
+}
+
+/// Directory handler for fuse filesystem
+pub struct DirHandler {
+    pub id: u64,
+    pub entries: Vec<DirEntry>,
+    pub cur: usize,
+}
+
+impl DirHandler {
+    pub fn new(id: u64, entries: Vec<DirEntry>) -> Self {
+        Self {
+            id,
+            cur: 0,
+            entries,
+        }
+    }
+
+    pub fn next_entry(&mut self) -> Option<DirEntry> {
+        let entry = if self.cur < self.entries.len() {
+            Some(self.entries[self.cur].clone())
+        } else {
+            None
+        };
+        self.cur += 1;
+        entry
+    }
+}
+
+pub fn translate_ftype(file_type: Ext4FileType) -> FileType {
+    match file_type {
+        Ext4FileType::RegularFile => FileType::RegularFile,
+        Ext4FileType::Directory => FileType::Directory,
+        Ext4FileType::SymLink => FileType::Symlink,
+        Ext4FileType::CharacterDev => FileType::CharDevice,
+        Ext4FileType::BlockDev => FileType::BlockDevice,
+        Ext4FileType::Fifo => FileType::NamedPipe,
+        Ext4FileType::Socket => FileType::Socket,
+        Ext4FileType::Unknown => FileType::RegularFile,
+    }
+}
+
+fn get_time(time: u64) -> SystemTime {
+    SystemTime::UNIX_EPOCH + Duration::from_secs(time)
+}

+ 278 - 0
ext4_fuse/src/fuse_fs.rs

@@ -0,0 +1,278 @@
+//! To make `Ext4FuseFs` behave like `RefFS`, these FUSE interfaces
+//! need to be implemented.
+//!
+//! init destroy lookup forget getattr setattr readlink mknod mkdir
+//! unlink rmdir symlink rename link open read write flush release
+//! fsync opendir readdir releasedir fsyncdir statfs setxattr getxattr
+//! listxattr removexattr access create getlk ioctl
+//!
+//! Rust crate `fuser` doesn't have the detailed explantion of these interfaces.
+//! See `fuse_lowlevel_ops` in C FUSE library for details.
+//! https://libfuse.github.io/doxygen/structfuse__lowlevel__ops.html
+
+use super::common::{translate_ftype, DirHandler, FileHandler, FuseInode};
+use ext4_rs::{DirEntry, ErrCode, Ext4, InodeMode, OpenFlags};
+use fuser::{
+    Filesystem, ReplyAttr, ReplyCreate, ReplyData, ReplyEmpty, ReplyEntry, ReplyOpen, ReplyWrite,
+    Request,
+};
+use std::ffi::OsStr;
+use std::time::Duration;
+
+type FId = u64;
+
+pub struct Ext4FuseFs {
+    fs: Ext4,
+    files: Vec<FileHandler>,
+    next_fid: FId,
+    dirs: Vec<DirHandler>,
+    next_did: FId,
+}
+
+impl Ext4FuseFs {
+    pub fn new(fs: Ext4) -> Self {
+        Self {
+            fs,
+            files: Vec::new(),
+            next_fid: 0,
+            dirs: Vec::new(),
+            next_did: 0,
+        }
+    }
+
+    /// Add a file handler to file list
+    pub fn add_file(&mut self, inode: u32, flags: OpenFlags) -> FId {
+        self.files
+            .push(FileHandler::new(self.next_did, inode, flags));
+        self.next_fid += 1;
+        self.next_fid - 1
+    }
+
+    pub fn release_file(&mut self, fh: FId) {
+        self.files.retain(|f| f.id != fh);
+    }
+
+    /// Add a directory handler to directory list
+    pub fn add_dir(&mut self, entries: Vec<DirEntry>) -> FId {
+        self.dirs.push(DirHandler::new(self.next_did, entries));
+        self.next_did += 1;
+        self.next_did - 1
+    }
+
+    pub fn release_dir(&mut self, did: FId) {
+        self.dirs.retain(|d| d.id != did);
+    }
+}
+
+impl Filesystem for Ext4FuseFs {
+    fn lookup(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEntry) {
+        match self.fs.lookup(parent as u32, name.to_str().unwrap()) {
+            Ok(inode_id) => {
+                let inode = self.fs.inode(inode_id);
+                reply.entry(&get_ttl(), &FuseInode(inode).get_attr(), 0)
+            }
+            Err(e) => reply.error(e.code() as i32),
+        }
+    }
+
+    fn getattr(&mut self, _req: &Request<'_>, ino: u64, reply: ReplyAttr) {
+        let inode = self.fs.inode(ino as u32);
+        reply.attr(&get_ttl(), &FuseInode(inode).get_attr())
+    }
+
+    fn create(
+        &mut self,
+        _req: &Request<'_>,
+        parent: u64,
+        name: &OsStr,
+        mode: u32,
+        _umask: u32,
+        flags: i32,
+        reply: ReplyCreate,
+    ) {
+        match self.fs.create(
+            parent as u32,
+            name.to_str().unwrap(),
+            InodeMode::from_bits_truncate(mode as u16),
+        ) {
+            Ok(ino) => {
+                let fid = self.add_file(ino, OpenFlags::from_bits_truncate(flags as u32));
+                let inode = self.fs.inode(ino);
+                reply.created(&get_ttl(), &FuseInode(inode).get_attr(), 0, fid, 0);
+            }
+            Err(e) => reply.error(e.code() as i32),
+        }
+    }
+
+    fn open(&mut self, _req: &Request<'_>, ino: u64, flags: i32, reply: ReplyOpen) {
+        let inode = self.fs.inode(ino as u32);
+        if !inode.inode.is_file() {
+            return reply.error(ErrCode::EISDIR as i32);
+        }
+        let fid = self.add_file(ino as u32, OpenFlags::from_bits_truncate(flags as u32));
+        reply.opened(fid, 0);
+    }
+
+    fn read(
+        &mut self,
+        _req: &Request<'_>,
+        ino: u64,
+        _fh: u64,
+        offset: i64,
+        size: u32,
+        _flags: i32,
+        _lock_owner: Option<u64>,
+        reply: ReplyData,
+    ) {
+        // let fh = match self.files.iter_mut().find(|f| f.id == fh) {
+        //     Some(f) => f,
+        //     None => return reply.error(ErrCode::ENOENT as i32),
+        // };
+        let mut data = vec![0; size as usize];
+        match self.fs.read(ino as u32, offset as usize, &mut data) {
+            Ok(sz) => reply.data(&data[..sz]),
+            Err(e) => reply.error(e.code() as i32),
+        }
+    }
+
+    fn write(
+        &mut self,
+        _req: &Request<'_>,
+        ino: u64,
+        _fh: u64,
+        offset: i64,
+        data: &[u8],
+        _write_flags: u32,
+        _flags: i32,
+        _lock_owner: Option<u64>,
+        reply: ReplyWrite,
+    ) {
+        match self.fs.write(ino as u32, offset as usize, data) {
+            Ok(sz) => reply.written(sz as u32),
+            Err(e) => reply.error(e.code() as i32),
+        }
+    }
+
+    fn release(
+        &mut self,
+        _req: &Request<'_>,
+        _ino: u64,
+        fh: u64,
+        _flags: i32,
+        _lock_owner: Option<u64>,
+        _flush: bool,
+        reply: ReplyEmpty,
+    ) {
+        self.release_file(fh);
+        reply.ok();
+    }
+
+    fn link(
+        &mut self,
+        _req: &Request<'_>,
+        ino: u64,
+        newparent: u64,
+        newname: &OsStr,
+        reply: ReplyEntry,
+    ) {
+        match self
+            .fs
+            .link(ino as u32, newparent as u32, newname.to_str().unwrap())
+        {
+            Ok(inode) => reply.entry(&get_ttl(), &FuseInode(inode).get_attr(), 0),
+            Err(e) => reply.error(e.code() as i32),
+        }
+    }
+
+    fn unlink(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEmpty) {
+        match self.fs.unlink(parent as u32, name.to_str().unwrap()) {
+            Ok(_) => reply.ok(),
+            Err(e) => reply.error(e.code() as i32),
+        }
+    }
+
+    fn mkdir(
+        &mut self,
+        _req: &Request<'_>,
+        parent: u64,
+        name: &OsStr,
+        mode: u32,
+        _umask: u32,
+        reply: ReplyEntry,
+    ) {
+        match self.fs.mkdir(
+            parent as u32,
+            name.to_str().unwrap(),
+            InodeMode::from_bits_truncate(mode as u16),
+        ) {
+            Ok(inode) => reply.entry(&get_ttl(), &FuseInode(inode).get_attr(), 0),
+            Err(e) => reply.error(e.code() as i32),
+        }
+    }
+
+    fn opendir(&mut self, _req: &Request<'_>, ino: u64, _flags: i32, reply: ReplyOpen) {
+        match self.fs.list(ino as u32) {
+            Ok(entries) => {
+                let fh = self.add_dir(entries);
+                reply.opened(fh, 0);
+            }
+            Err(e) => reply.error(e.code() as i32),
+        }
+    }
+
+    fn readdir(
+        &mut self,
+        _req: &Request<'_>,
+        ino: u64,
+        fh: u64,
+        _offset: i64,
+        mut reply: fuser::ReplyDirectory,
+    ) {
+        let dir = self.dirs.iter_mut().find(|d| d.id == fh);
+        match dir {
+            Some(dir) => {
+                loop {
+                    let entry = dir.next_entry();
+                    if entry.is_none() {
+                        break;
+                    }
+                    let entry = entry.unwrap();
+                    let inode = self.fs.inode(entry.inode());
+                    if reply.add(
+                        ino,
+                        dir.cur as i64,
+                        translate_ftype(inode.inode.file_type()),
+                        entry.name().unwrap(),
+                    ) {
+                        break;
+                    }
+                }
+                reply.ok();
+            }
+            None => reply.error(-1),
+        }
+    }
+
+    fn releasedir(
+        &mut self,
+        _req: &Request<'_>,
+        _ino: u64,
+        fh: u64,
+        _flags: i32,
+        reply: ReplyEmpty,
+    ) {
+        self.release_dir(fh);
+        reply.ok();
+    }
+
+    fn rmdir(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEmpty) {
+        match self.fs.rmdir(parent as u32, name.to_str().unwrap()) {
+            Ok(()) => reply.ok(),
+            Err(e) => reply.error(e.code() as i32),
+        }
+    }
+}
+
+fn get_ttl() -> Duration {
+    Duration::from_secs(1)
+}

+ 60 - 0
ext4_fuse/src/main.rs

@@ -0,0 +1,60 @@
+mod block_file;
+mod common;
+mod fuse_fs;
+
+use block_file::BlockFile;
+use ext4_rs::Ext4;
+use fuse_fs::Ext4FuseFs;
+use fuser::MountOption;
+use log::{error, info};
+use simple_logger::SimpleLogger;
+use std::sync::Arc;
+
+fn make_ext4(path: &str) {
+    let _ = std::process::Command::new("rm")
+        .args(["-rf", path])
+        .status();
+    let _ = std::process::Command::new("dd")
+        .args([
+            "if=/dev/zero",
+            &format!("of={}", path),
+            "bs=1M",
+            "count=512",
+        ])
+        .status();
+    let _ = std::process::Command::new("mkfs.ext4")
+        .args([path])
+        .output();
+}
+
+fn main() {
+    SimpleLogger::new().init().unwrap();
+    log::set_max_level(log::LevelFilter::Debug);
+
+    // Get block file
+    let bf = match option_env!("BF") {
+        Some(bf) => bf,
+        _ => panic!("No block file specified!"),
+    };
+    info!("Use block file \"{}\"", bf);
+    make_ext4(bf);
+
+    // Get mountpoint
+    let mp = match option_env!("MP") {
+        Some(mp) => mp,
+        _ => panic!("No mount point specified!"),
+    };
+    info!("Use mountpoint \"{}\"", mp);
+
+    // Initialize block device and filesystem
+    let block_file = Arc::new(BlockFile::new(bf));
+    let fs = Ext4FuseFs::new(Ext4::load(block_file).expect("Load Ext4 filesystem failed"));
+
+    // Mount fs and enter session loop
+    let options = Vec::<MountOption>::new();
+    info!("Mount ext4fs to \"{}\"", mp);
+    let res = fuser::mount2(fs, &mp, &options);
+    if let Err(e) = res {
+        error!("Error occured: {:?}", e);
+    }
+}

+ 35 - 0
ext4_test/src/block_file.rs

@@ -0,0 +1,35 @@
+use ext4_rs::{Block, BlockDevice, BLOCK_SIZE};
+use std::fs::{File, OpenOptions};
+use std::io::{Read, Seek, SeekFrom, Write};
+
+#[derive(Debug)]
+pub struct BlockFile(File);
+
+impl BlockFile {
+    pub fn new(path: &str) -> Self {
+        let file = OpenOptions::new()
+            .read(true)
+            .write(true)
+            .open(path)
+            .unwrap();
+        Self(file)
+    }
+}
+
+impl BlockDevice for BlockFile {
+    fn read_block(&self, block_id: u64) -> Block {
+        let mut file = &self.0;
+        let mut buffer = [0u8; BLOCK_SIZE];
+        // warn!("read_block {}", block_id);
+        let _r = file.seek(SeekFrom::Start(block_id * BLOCK_SIZE as u64));
+        let _r = file.read_exact(&mut buffer);
+        Block::new(block_id, buffer)
+    }
+
+    fn write_block(&self, block: &Block) {
+        let mut file = &self.0;
+        // warn!("write_block {}", block.block_id);
+        let _r = file.seek(SeekFrom::Start(block.block_id * BLOCK_SIZE as u64));
+        let _r = file.write_all(&block.data);
+    }
+}

+ 74 - 75
ext4_test/src/main.rs

@@ -1,44 +1,11 @@
-use ext4_rs::{Block, BlockDevice, Ext4, BLOCK_SIZE};
+use ext4_rs::{Ext4, InodeMode, OpenFlags, EXT4_ROOT_INO};
 use simple_logger::SimpleLogger;
-use std::fs::{File, OpenOptions};
-use std::io::{Read, Seek, SeekFrom, Write};
 use std::sync::Arc;
+use block_file::BlockFile;
 
-#[derive(Debug)]
-pub struct BlockFile(File);
+mod block_file;
 
-impl BlockFile {
-    pub fn new(path: &str) -> Self {
-        let file = OpenOptions::new()
-            .read(true)
-            .write(true)
-            .open(path)
-            .unwrap();
-        Self(file)
-    }
-}
-
-impl BlockDevice for BlockFile {
-    fn read_block(&self, block_id: u64) -> Block {
-        let mut file = &self.0;
-        let mut buffer = [0u8; BLOCK_SIZE];
-        // warn!("read_block {}", block_id);
-        let _r = file.seek(SeekFrom::Start(block_id * BLOCK_SIZE as u64));
-        let _r = file.read_exact(&mut buffer);
-        Block::new(block_id, buffer)
-    }
-
-    fn write_block(&self, block: &Block) {
-        let mut file = &self.0;
-        // warn!("write_block {}", block.block_id);
-        let _r = file.seek(SeekFrom::Start(block.block_id * BLOCK_SIZE as u64));
-        let _r = file.write_all(&block.data);
-    }
-}
-
-fn logger_init() {
-    SimpleLogger::new().init().unwrap();
-}
+const ROOT_INO: u32 = EXT4_ROOT_INO;
 
 fn make_ext4() {
     let _ = std::process::Command::new("rm")
@@ -59,68 +26,101 @@ fn open_ext4() -> Ext4 {
 }
 
 fn mkdir_test(ext4: &mut Ext4) {
-    ext4.make_dir("d1").expect("mkdir failed");
-    ext4.make_dir("d1/d2").expect("mkdir failed");
-    ext4.make_dir("d1/d2/d3").expect("mkdir failed");
-    ext4.make_dir("d1/d2/d3/d4").expect("mkdir failed");
-    ext4.make_dir("d2").expect("mkdir failed");
-    ext4.make_dir("d2/d3").expect("mkdir failed");
-    ext4.make_dir("d2/d3/d4").expect("mkdir failed");
-    ext4.make_dir("d3").expect("mkdir failed");
+    let dir_mode: InodeMode = InodeMode::DIRECTORY | InodeMode::ALL_RWX;
+    ext4.generic_create(ROOT_INO, "d1", dir_mode)
+        .expect("mkdir failed");
+    ext4.generic_create(ROOT_INO, "d1/d2", dir_mode)
+        .expect("mkdir failed");
+    ext4.generic_create(ROOT_INO, "d1/d2/d3", dir_mode)
+        .expect("mkdir failed");
+    ext4.generic_create(ROOT_INO, "d1/d2/d3/d4", dir_mode)
+        .expect("mkdir failed");
+    ext4.generic_create(ROOT_INO, "d2", dir_mode)
+        .expect("mkdir failed");
+    ext4.generic_create(ROOT_INO, "d2/d3", dir_mode)
+        .expect("mkdir failed");
+    ext4.generic_create(ROOT_INO, "d2/d3/d4", dir_mode)
+        .expect("mkdir failed");
+    ext4.generic_create(ROOT_INO, "d3", dir_mode)
+        .expect("mkdir failed");
 }
 
-fn open_test(ext4: &mut Ext4) {
-    ext4.open_file("d1/d2/d3/d4/f1", "w+").expect("open failed");
-    ext4.open_file("d1/d2/d3/d4/f1", "r").expect("open failed");
-    ext4.open_file("d1/d2/d3/d4/f5", "a").expect("open failed");
-    ext4.open_file("d2/f4", "w+").expect("open failed");
-    ext4.open_file("f1", "w+").expect("open failed");
+fn create_test(ext4: &mut Ext4) {
+    let file_mode: InodeMode = InodeMode::FILE | InodeMode::ALL_RWX;
+    ext4.generic_create(ROOT_INO, "d1/d2/d3/d4/f1", file_mode)
+        .expect("open failed");
+    ext4.generic_create(ROOT_INO, "d3/f0", file_mode)
+        .expect("open failed");
+    ext4.generic_create(ROOT_INO, "d3/f1", file_mode)
+        .expect("open failed");
+    ext4.generic_create(ROOT_INO, "f1", file_mode)
+        .expect("open failed");
 }
 
 fn read_write_test(ext4: &mut Ext4) {
     let wbuffer = "hello world".as_bytes();
-    let mut wfile = ext4.open_file("d3/f0", "w+").expect("open failed");
-    ext4.write_file(&mut wfile, wbuffer).expect("write failed");
+    let wfile = ext4
+        .generic_open(ROOT_INO, "d3/f0", OpenFlags::O_WRONLY)
+        .expect("open failed");
+    ext4.write(wfile.inode, 0, wbuffer).expect("write failed");
 
     let mut rbuffer = vec![0u8; wbuffer.len()];
-    let mut rfile = ext4.open_file("d3/f0", "r").expect("open failed");
-    ext4.read_file(&mut rfile, &mut rbuffer)
-        .expect("read failed");
+    let rfile = ext4
+        .generic_open(ROOT_INO, "d3/f0", OpenFlags::O_RDONLY)
+        .expect("open failed");
+    ext4.read(wfile.inode, 0, &mut rbuffer).expect("read failed");
 
     assert_eq!(wbuffer, rbuffer);
 }
 
 fn large_read_write_test(ext4: &mut Ext4) {
     let wbuffer = vec![99u8; 1024 * 1024 * 16];
-    let mut wfile = ext4.open_file("d3/f1", "w+").expect("open failed");
-    ext4.write_file(&mut wfile, &wbuffer).expect("write failed");
-
-    let mut rfile = ext4.open_file("d3/f1", "r").expect("open failed");
+    let wfile = ext4
+        .generic_open(ROOT_INO, "d3/f1", OpenFlags::O_WRONLY)
+        .expect("open failed");
+    ext4.write(wfile.inode, 0, &wbuffer).expect("write failed");
+
+    let rfile = ext4
+        .generic_open(ROOT_INO, "d3/f1", OpenFlags::O_RDONLY)
+        .expect("open failed");
     let mut rbuffer = vec![0u8; wbuffer.len()];
-    ext4.read_file(&mut rfile, &mut rbuffer)
-        .expect("read failed");
+    ext4.read(rfile.inode, 0,&mut rbuffer).expect("read failed");
 
     assert_eq!(wbuffer, rbuffer);
 }
 
 fn remove_file_test(ext4: &mut Ext4) {
-    ext4.remove_file("d3/f0").expect("remove file failed");
-    ext4.open_file("d3/f0", "r").expect_err("open failed");
-    ext4.remove_file("d3/f1").expect("remove file failed");
-    ext4.open_file("d3/f1", "r").expect_err("open failed");
-    ext4.remove_file("f1").expect("remove file failed");
-    ext4.open_file("f1", "r").expect_err("open failed");
-    ext4.remove_file("d1/not_exist")
+    ext4.generic_remove(ROOT_INO, "d3/f0")
+        .expect("remove file failed");
+    ext4.generic_lookup(ROOT_INO, "d3/f0")
+        .expect_err("file not removed");
+    ext4.generic_remove(ROOT_INO, "d3/f1")
+        .expect("remove file failed");
+    ext4.generic_lookup(ROOT_INO, "d3/f1")
+        .expect_err("file not removed");
+    ext4.generic_remove(ROOT_INO, "f1")
+        .expect("remove file failed");
+    ext4.generic_lookup(ROOT_INO, "f1")
+        .expect_err("file not removed");
+    ext4.generic_remove(ROOT_INO, "d1/not_exist")
         .expect_err("remove file failed");
 }
 
 fn remove_dir_test(ext4: &mut Ext4) {
-    ext4.remove_dir("d2").expect("remove dir failed");
-    ext4.open_file("d2/f4", "r").expect_err("open failed");
+    ext4.generic_remove(ROOT_INO, "d2")
+        .expect_err("remove unempty dir");
+    ext4.generic_create(ROOT_INO, "dtmp", InodeMode::DIRECTORY | InodeMode::ALL_RWX)
+        .expect("mkdir failed");
+    ext4.generic_lookup(ROOT_INO, "dtmp")
+        .expect("dir not created");
+    ext4.generic_remove(ROOT_INO, "dtmp")
+        .expect("remove file failed");
+    ext4.generic_lookup(ROOT_INO, "dtmp")
+        .expect_err("dir not removed");
 }
 
 fn main() {
-    logger_init();
+    SimpleLogger::new().init().unwrap();
     log::set_max_level(log::LevelFilter::Off);
     make_ext4();
     println!("ext4.img created");
@@ -128,15 +128,14 @@ fn main() {
     println!("ext4 opened");
     mkdir_test(&mut ext4);
     println!("mkdir test done");
-    open_test(&mut ext4);
-    println!("open test done");
+    create_test(&mut ext4);
+    println!("create test done");
     read_write_test(&mut ext4);
     println!("read write test done");
     large_read_write_test(&mut ext4);
     println!("large read write test done");
     remove_file_test(&mut ext4);
     println!("remove file test done");
-    log::set_max_level(log::LevelFilter::Debug);
     remove_dir_test(&mut ext4);
     println!("remove dir test done");
 }