瀏覽代碼

feat: state block dev

liujingx 10 月之前
父節點
當前提交
3208e56daf
共有 4 個文件被更改,包括 172 次插入72 次删除
  1. 79 0
      ext4_fuse/src/block_dev.rs
  2. 0 35
      ext4_fuse/src/block_file.rs
  3. 85 6
      ext4_fuse/src/fuse_fs.rs
  4. 8 31
      ext4_fuse/src/main.rs

+ 79 - 0
ext4_fuse/src/block_dev.rs

@@ -0,0 +1,79 @@
+use ext4_rs::{Block, BlockDevice, BLOCK_SIZE};
+use std::fs::OpenOptions;
+use std::io::Read;
+use std::sync::Mutex;
+
+/// A block device supporting state save and restore
+pub trait StateBlockDevice<T>: BlockDevice
+where
+    T: Sized,
+{
+    fn checkpoint(&self) -> T;
+    fn restore(&self, state: T);
+}
+
+/// An in-memory block device
+#[derive(Debug)]
+pub struct BlockMem(Mutex<Vec<[u8; BLOCK_SIZE]>>);
+
+impl BlockMem {
+    /// Create a new block device with the given number of blocks
+    pub fn new(num_blocks: u64) -> Self {
+        let mut blocks = Vec::with_capacity(num_blocks as usize);
+        for _ in 0..num_blocks {
+            blocks.push([0; BLOCK_SIZE]);
+        }
+        Self(Mutex::new(blocks))
+    }
+    /// Make an ext4 filesystem on the block device
+    pub fn mkfs(&self) {
+        let path = "tmp.img";
+        let mut mem = self.0.lock().unwrap();
+        // Create a temp block file
+        std::process::Command::new("dd")
+            .args([
+                "if=/dev/zero",
+                &format!("of={}", path),
+                &format!("bs={}", BLOCK_SIZE),
+                &format!("count={}", mem.len()),
+            ])
+            .status()
+            .expect("Failed to create temp file");
+        // Make ext4 fs
+        std::process::Command::new("mkfs.ext4")
+            .args([path, &format!("-b {}", BLOCK_SIZE)])
+            .status()
+            .expect("Failed to make ext4 fs");
+        // Open the temp file and copy data to memory
+        let mut file = OpenOptions::new().read(true).open(path).unwrap();
+        for block in mem.iter_mut() {
+            file.read(block).expect("Read failed");
+        }
+        // Remove the temp file
+        std::process::Command::new("rm")
+            .args(["-rf", path])
+            .status()
+            .expect("Failed to remove temp file");
+    }
+}
+
+impl BlockDevice for BlockMem {
+    fn read_block(&self, block_id: u64) -> Block {
+        Block {
+            id: block_id,
+            data: self.0.lock().unwrap()[block_id as usize],
+        }
+    }
+    fn write_block(&self, block: &Block) {
+        self.0.lock().unwrap()[block.id as usize] = block.data;
+    }
+}
+
+impl StateBlockDevice<Vec<[u8; BLOCK_SIZE]>> for BlockMem {
+    fn checkpoint(&self) -> Vec<[u8; BLOCK_SIZE]> {
+        self.0.lock().unwrap().clone()
+    }
+    fn restore(&self, state: Vec<[u8; BLOCK_SIZE]>) {
+        self.0.lock().unwrap().clone_from(&state);
+    }
+}

+ 0 - 35
ext4_fuse/src/block_file.rs

@@ -1,35 +0,0 @@
-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.id * BLOCK_SIZE as u64));
-        let _r = file.write_all(&block.data);
-    }
-}

+ 85 - 6
ext4_fuse/src/fuse_fs.rs

@@ -9,32 +9,55 @@
 //! 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
+//!
+//! To support state checkpoint and restore, `Ext4FuseFs` uses a hash map
+//! to store checkpoint states. By using special `ioctl` commands, `Ext4FuseFs`
+//! can save and restore checkpoint states like `RefFS`, and thus support
+//! Metis model check.
 
 use super::common::{
     sys_time2second, time_or_now2second, translate_attr, translate_ftype, DirHandler, FileHandler,
 };
+use crate::block_dev::StateBlockDevice;
 use ext4_rs::{DirEntry, ErrCode, Ext4, Ext4Error, InodeMode, OpenFlags};
 use fuser::{
     FileAttr, FileType, Filesystem, ReplyAttr, ReplyCreate, ReplyData, ReplyEmpty, ReplyEntry,
     ReplyOpen, ReplyWrite, Request,
 };
-use std::ffi::OsStr;
+use std::collections::HashMap;
+use std::ffi::{c_int, OsStr};
+use std::sync::Arc;
 use std::time::Duration;
 
 type FId = u64;
+type StateKey = u64;
 
-pub struct Ext4FuseFs {
+pub struct StateExt4FuseFs<T> {
+    /// Block device
+    block_dev: Arc<dyn StateBlockDevice<T>>,
+    /// Ext4 filesystem
     fs: Ext4,
+    /// Checkpoint states
+    states: HashMap<StateKey, T>,
+    /// Opened files
     files: Vec<FileHandler>,
+    /// Next file handler id
     next_fid: FId,
+    /// Opened directories
     dirs: Vec<DirHandler>,
+    /// Next directory handler id
     next_did: FId,
 }
 
-impl Ext4FuseFs {
-    pub fn new(fs: Ext4) -> Self {
+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 {
         Self {
-            fs,
+            fs: Ext4::load(block_dev.clone()).unwrap(),
+            block_dev,
+            states: HashMap::new(),
             files: Vec::new(),
             next_fid: 0,
             dirs: Vec::new(),
@@ -42,6 +65,23 @@ impl Ext4FuseFs {
         }
     }
 
+    /// Save a state
+    fn checkpoint(&mut self, key: StateKey) -> bool {
+        self.states
+            .insert(key, self.block_dev.checkpoint())
+            .is_none()
+    }
+
+    /// Restore a state
+    fn restore(&mut self, key: StateKey) -> bool {
+        if let Some(state) = self.states.remove(&key) {
+            self.block_dev.restore(state);
+            true
+        } else {
+            false
+        }
+    }
+
     /// Add a file handler to file list
     fn add_file(&mut self, inode: u32, flags: OpenFlags) -> FId {
         self.files
@@ -73,7 +113,11 @@ impl Ext4FuseFs {
     }
 }
 
-impl Filesystem for Ext4FuseFs {
+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),
@@ -359,6 +403,41 @@ impl Filesystem for Ext4FuseFs {
         }
         reply.error(ErrCode::EACCES as i32);
     }
+
+    fn ioctl(
+        &mut self,
+        _req: &Request<'_>,
+        _ino: u64,
+        _fh: u64,
+        _flags: u32,
+        cmd: u32,
+        in_data: &[u8],
+        _out_size: u32,
+        reply: fuser::ReplyIoctl,
+    ) {
+        match cmd {
+            Self::CHECKPOINT_IOC => {
+                let key = StateKey::from_ne_bytes(in_data[0..8].try_into().unwrap());
+                if self.checkpoint(key) {
+                    reply.ioctl(0, in_data);
+                } else {
+                    reply.error(-1);
+                }
+            }
+            Self::RESTORE_IOC => {
+                let key = StateKey::from_ne_bytes(in_data[0..8].try_into().unwrap());
+                if self.restore(key) {
+                    reply.ioctl(0, in_data);
+                } else {
+                    reply.error(-1);
+                }
+            }
+            _ => {
+                log::error!("Unknown ioctl command: {}", cmd);
+                reply.error(ErrCode::ENOTSUP as i32);
+            }
+        }
+    }
 }
 
 fn get_ttl() -> Duration {

+ 8 - 31
ext4_fuse/src/main.rs

@@ -1,44 +1,20 @@
-mod block_file;
+#![feature(trait_upcasting)]
+
+mod block_dev;
 mod common;
 mod fuse_fs;
 
-use block_file::BlockFile;
-use ext4_rs::Ext4;
-use fuse_fs::Ext4FuseFs;
+use block_dev::BlockMem;
+use fuse_fs::StateExt4FuseFs;
 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,
@@ -47,8 +23,9 @@ fn main() {
     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"));
+    let block_mem = Arc::new(BlockMem::new(512));
+    block_mem.mkfs();
+    let fs = StateExt4FuseFs::new(block_mem);
 
     // Mount fs and enter session loop
     let options = Vec::<MountOption>::new();