ソースを参照

feat: fuse tool enable load and save image

liujingx 10 ヶ月 前
コミット
9c0d538828
4 ファイル変更99 行追加25 行削除
  1. 25 2
      ext4_fuse/src/block_dev.rs
  2. 1 1
      ext4_fuse/src/common.rs
  3. 11 8
      ext4_fuse/src/fuse_fs.rs
  4. 62 14
      ext4_fuse/src/main.rs

+ 25 - 2
ext4_fuse/src/block_dev.rs

@@ -1,6 +1,6 @@
-use ext4_rs::{Block, BlockDevice, BLOCK_SIZE};
+use another_ext4::{Block, BlockDevice, BLOCK_SIZE};
 use std::fs::OpenOptions;
-use std::io::Read;
+use std::io::{Read, Write};
 use std::sync::Mutex;
 
 /// A block device supporting state save and restore
@@ -25,6 +25,29 @@ impl BlockMem {
         }
         Self(Mutex::new(blocks))
     }
+    /// Load a disk image from a file
+    pub fn load(path: &str) -> Self {
+        let mut file = OpenOptions::new().read(true).open(path).unwrap();
+        let mut blocks = Vec::new();
+        let mut buf = [0; BLOCK_SIZE];
+        while file.read(&mut buf).unwrap() > 0 {
+            blocks.push(buf);
+        }
+        Self(Mutex::new(blocks))
+    }
+    /// Save the disk image to a file
+    pub fn save(&self, path: &str) {
+        let mut file = OpenOptions::new()
+            .write(true)
+            .create(true)
+            .truncate(true)
+            .open(path)
+            .unwrap();
+        let blocks = self.0.lock().unwrap();
+        for block in blocks.iter() {
+            file.write_all(block).unwrap();
+        }
+    }
     /// Make an ext4 filesystem on the block device
     pub fn mkfs(&self) {
         let path = "tmp.img";

+ 1 - 1
ext4_fuse/src/common.rs

@@ -1,4 +1,4 @@
-use ext4_rs::{FileAttr as Ext4FileAttr, FileType as Ext4FileType, INODE_BLOCK_SIZE};
+use another_ext4::{FileAttr as Ext4FileAttr, FileType as Ext4FileType, INODE_BLOCK_SIZE};
 use fuser::{FileAttr, FileType, TimeOrNow};
 use std::time::{Duration, SystemTime, UNIX_EPOCH};
 

+ 11 - 8
ext4_fuse/src/fuse_fs.rs

@@ -17,13 +17,13 @@
 
 use super::common::{sys_time2second, time_or_now2second, translate_attr, translate_ftype};
 use crate::block_dev::StateBlockDevice;
-use ext4_rs::{ErrCode, Ext4, Ext4Error, FileType as Ext4FileType, InodeMode};
+use another_ext4::{ErrCode, Ext4, Ext4Error, FileType as Ext4FileType, InodeMode};
 use fuser::{
     FileAttr, FileType, Filesystem, ReplyAttr, ReplyCreate, ReplyData, ReplyDirectory, ReplyEmpty,
     ReplyEntry, ReplyOpen, ReplyWrite, Request,
 };
 use std::collections::HashMap;
-use std::ffi::{c_int, OsStr};
+use std::ffi::OsStr;
 use std::sync::Arc;
 use std::time::Duration;
 
@@ -47,9 +47,16 @@ impl<T: 'static> StateExt4FuseFs<T> {
     const CHECKPOINT_IOC: u32 = 1;
     const RESTORE_IOC: u32 = 2;
 
-    pub fn new(block_dev: Arc<dyn StateBlockDevice<T>>) -> Self {
+    /// Create a file system on a block device
+    /// 
+    /// `init` - If true, initialize the filesystem
+    pub fn new(block_dev: Arc<dyn StateBlockDevice<T>>, init: bool) -> Self {
+        let mut fs = Ext4::load(block_dev.clone()).expect("Failed to load ext4 filesystem");
+        if init {
+            fs.init().expect("Failed to init ext4 filesystem");
+        }
         Self {
-            fs: Ext4::load(block_dev.clone()).unwrap(),
+            fs,
             block_dev,
             states: HashMap::new(),
             next_fid: 0,
@@ -86,10 +93,6 @@ impl<T: 'static> StateExt4FuseFs<T> {
 }
 
 impl<T: 'static> Filesystem for StateExt4FuseFs<T> {
-    fn init(&mut self, _req: &Request<'_>, _config: &mut fuser::KernelConfig) -> Result<(), c_int> {
-        self.fs.init().map_err(|e| e.code() as i32)
-    }
-
     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) => reply.entry(&get_ttl(), &self.get_attr(inode_id).unwrap(), 0),

+ 62 - 14
ext4_fuse/src/main.rs

@@ -8,38 +8,86 @@ use block_dev::BlockMem;
 use clap::Parser;
 use fuse_fs::StateExt4FuseFs;
 use fuser::MountOption;
-use log::{error, info};
+use log::LevelFilter;
 use simple_logger::SimpleLogger;
-use std::sync::Arc;
+use std::sync::{Arc, OnceLock};
 
 #[derive(Parser, Debug)]
+#[command(about = "Another ext4 FUSE Tool")]
 struct Args {
     /// Fs mount point
     #[arg(short, long)]
     mountpoint: String,
-
-    /// Fs block count
-    #[arg(short, long, default_value_t = 4096)]
+    /// Load initial image
+    #[arg(short, long)]
+    image: Option<String>,
+    /// Fs total block number, ignored when [image] is set
+    #[arg(short, long, default_value_t = 8192)]
     block: u64,
+    /// Save image on exit
+    #[arg(short, long)]
+    output: Option<String>,
+    /// Log level
+    #[arg(short, long, default_value_t = String::from("info"))]
+    log: String,
 }
 
+fn parse_log_level(level_str: &str) -> LevelFilter {
+    match level_str.to_lowercase().as_str() {
+        "off" => LevelFilter::Off,
+        "error" => LevelFilter::Error,
+        "warn" => LevelFilter::Warn,
+        "info" => LevelFilter::Info,
+        "debug" => LevelFilter::Debug,
+        "trace" => LevelFilter::Trace,
+        _ => LevelFilter::Off,
+    }
+}
+
+/// Global exit flag
+static EXIT_FLAG: OnceLock<bool> = OnceLock::new();
+
 fn main() {
     let args = Args::parse();
 
+    // Initialize logger
+    println!("Log level {}", args.log);
     SimpleLogger::new().init().unwrap();
-    log::set_max_level(log::LevelFilter::Trace);
-    info!("Use mountpoint \"{}\"", args.mountpoint);
+    log::set_max_level(parse_log_level(&args.log));
 
     // Initialize block device and filesystem
-    let block_mem = Arc::new(BlockMem::new(args.block));
-    block_mem.mkfs();
-    let fs = StateExt4FuseFs::new(block_mem);
+    let block_mem = if let Some(image) = &args.image {
+        println!("Load image {}", image);
+        Arc::new(BlockMem::load(&image))
+    } else {
+        println!("Create disk image with {} blocks", args.block);
+        let block_mem = Arc::new(BlockMem::new(args.block));
+        block_mem.mkfs();
+        block_mem
+    };
+    // Create filesystem and init if image is newly created
+    let fs = StateExt4FuseFs::new(block_mem.clone(), args.image.is_none());
 
     // Mount fs and enter session loop
+    println!("Mount ext4fs to {}", args.mountpoint);
     let options = Vec::<MountOption>::new();
-    info!("Mount ext4fs to \"{}\"", args.mountpoint);
-    let res = fuser::mount2(fs, &args.mountpoint, &options);
-    if let Err(e) = res {
-        error!("Error occured: {:?}", e);
+    let session =
+        fuser::spawn_mount2(fs, &args.mountpoint, &options).expect("Failed to start FUSE session");
+    
+    // Set EXIT_FLAG when Ctrl+C is received
+    let _ = ctrlc::set_handler(|| {
+        EXIT_FLAG.get_or_init(|| true);
+    });
+    // Loop until EXIT_FLAG is set
+    loop {
+        if EXIT_FLAG.get().is_some() {
+            println!("Received Ctrl+C, exiting...");
+            if let Some(output) = &args.output {
+                println!("Save image {}", output);
+                block_mem.save(output);
+            }
+            break;
+        }
     }
+    session.join();
 }