Quellcode durchsuchen

feat: ext4 low level ops

liujingx vor 11 Monaten
Ursprung
Commit
de4660bd0d
13 geänderte Dateien mit 399 neuen und 349 gelöschten Zeilen
  1. 6 1
      .gitignore
  2. 1 1
      src/constants.rs
  3. 15 8
      src/error.rs
  4. 12 14
      src/ext4/alloc.rs
  5. 1 53
      src/ext4/dir.rs
  6. 0 238
      src/ext4/file.rs
  7. 31 7
      src/ext4/link.rs
  8. 311 0
      src/ext4/low_level.rs
  9. 5 8
      src/ext4/mod.rs
  10. 2 1
      src/ext4_defs/dir_entry.rs
  11. 9 14
      src/ext4_defs/file.rs
  12. 2 2
      src/ext4_defs/inode.rs
  13. 4 2
      src/lib.rs

+ 6 - 1
.gitignore

@@ -1,4 +1,9 @@
 /target
 
 /ext4_test/target
-/ext4_test/ext4.img
+/ext4_test/ext4.img
+
+/ext4_fuse/target
+
+/.idea
+/.vscode

+ 1 - 1
src/constants.rs

@@ -26,7 +26,7 @@ pub const NAME_MAX: usize = 255;
 pub const SYMLINKS_MAX: usize = 40;
 
 /// The inode number of root inode
-pub const EXT4_ROOT_INO: InodeId = 2;
+pub const EXT4_ROOT_INO: InodeId = 1;
 
 pub const BASE_OFFSET: usize = 1024;
 pub const BLOCK_SIZE: usize = 4096;

+ 15 - 8
src/error.rs

@@ -53,13 +53,20 @@ impl Ext4Error {
         Ext4Error { errno, msg: None }
     }
 
-    pub const fn with_message(errno: ErrCode, msg: String) -> Self {
+    pub const fn with_msg(errno: ErrCode, msg: String) -> Self {
         Ext4Error {
             errno,
             msg: Some(msg),
         }
     }
 
+    pub fn with_msg_str(errno: ErrCode, msg: &str) -> Self {
+        Ext4Error {
+            errno,
+            msg: Some(msg.to_owned()),
+        }
+    }
+
     pub const fn code(&self) -> ErrCode {
         self.errno
     }
@@ -73,31 +80,31 @@ impl From<ErrCode> for Ext4Error {
 
 impl From<core::str::Utf8Error> for Ext4Error {
     fn from(_: core::str::Utf8Error) -> Self {
-        Ext4Error::with_message(ErrCode::EINVAL, "Invalid utf-8 string".to_owned())
+        Ext4Error::with_msg(ErrCode::EINVAL, "Invalid utf-8 string".to_owned())
     }
 }
 
 impl From<alloc::string::FromUtf8Error> for Ext4Error {
     fn from(_: alloc::string::FromUtf8Error) -> Self {
-        Ext4Error::with_message(ErrCode::EINVAL, "Invalid utf-8 string".to_owned())
+        Ext4Error::with_msg(ErrCode::EINVAL, "Invalid utf-8 string".to_owned())
     }
 }
 
 impl From<core::ffi::FromBytesUntilNulError> for Ext4Error {
     fn from(_: core::ffi::FromBytesUntilNulError) -> Self {
-        Ext4Error::with_message(ErrCode::E2BIG, "Cannot find null in cstring".to_owned())
+        Ext4Error::with_msg(ErrCode::E2BIG, "Cannot find null in cstring".to_owned())
     }
 }
 
 impl From<core::ffi::FromBytesWithNulError> for Ext4Error {
     fn from(_: core::ffi::FromBytesWithNulError) -> Self {
-        Ext4Error::with_message(ErrCode::E2BIG, "Cannot find null in cstring".to_owned())
+        Ext4Error::with_msg(ErrCode::E2BIG, "Cannot find null in cstring".to_owned())
     }
 }
 
 impl From<alloc::ffi::NulError> for Ext4Error {
     fn from(_: alloc::ffi::NulError) -> Self {
-        Ext4Error::with_message(ErrCode::E2BIG, "Cannot find null in cstring".to_owned())
+        Ext4Error::with_msg(ErrCode::E2BIG, "Cannot find null in cstring".to_owned())
     }
 }
 
@@ -109,8 +116,8 @@ macro_rules! return_err {
 }
 
 #[macro_export]
-macro_rules! return_err_with_msg {
+macro_rules! return_err_with_msg_str {
     ($errno: expr, $message: expr) => {
-        return Err(Ext4Error::with_message($errno, $message))
+        return Err(Ext4Error::with_msg_str($errno, $message))
     };
 }

+ 12 - 14
src/ext4/alloc.rs

@@ -5,19 +5,13 @@ use crate::prelude::*;
 
 impl Ext4 {
     /// Create a new inode, returning the inode and its number
-    pub(super) fn create_inode(&mut self, file_type: FileType) -> Result<InodeRef> {
+    pub(super) fn create_inode(&mut self, mode: InodeMode) -> Result<InodeRef> {
         // Allocate an inode
-        let is_dir = file_type == FileType::Directory;
+        let is_dir = mode.file_type() == FileType::Directory;
         let id = self.alloc_inode(is_dir)?;
 
         // Initialize the inode
         let mut inode = Inode::default();
-        let mode = match file_type {
-            FileType::SymLink | FileType::Directory => {
-                InodeMode::from_type_and_perm(file_type, InodeMode::ALL_RWX)
-            }
-            _ => InodeMode::from_type_and_perm(file_type, InodeMode::ALL_RW),
-        };
         inode.set_mode(mode);
         inode.extent_init();
         if self.super_block.inode_size() > EXT4_GOOD_OLD_INODE_SIZE {
@@ -110,7 +104,7 @@ impl Ext4 {
         // Find the first free block
         let fblock = bitmap
             .find_and_set_first_clear_bit(0, 8 * BLOCK_SIZE)
-            .ok_or(Ext4Error::with_message(
+            .ok_or(Ext4Error::with_msg(
                 ErrCode::ENOSPC,
                 "No free block".to_owned(),
             ))? as PBlockId;
@@ -159,7 +153,7 @@ impl Ext4 {
 
         // Free the block
         if bitmap.is_bit_clear(pblock as usize) {
-            return Err(Ext4Error::with_message(
+            return Err(Ext4Error::with_msg(
                 ErrCode::EINVAL,
                 "Block double free".to_owned(),
             ));
@@ -211,9 +205,13 @@ impl Ext4 {
             let mut bitmap = Bitmap::new(&mut bitmap_block.data[..inode_count / 8]);
 
             // Find a free inode
-            let idx_in_bg = bitmap.find_and_set_first_clear_bit(0, inode_count).ok_or(
-                Ext4Error::with_message(ErrCode::ENOSPC, "No free inode".to_owned()),
-            )? as u32;
+            let idx_in_bg =
+                bitmap
+                    .find_and_set_first_clear_bit(0, inode_count)
+                    .ok_or(Ext4Error::with_msg(
+                        ErrCode::ENOSPC,
+                        "No free inode".to_owned(),
+                    ))? as u32;
 
             // Update bitmap in disk
             bg.desc.set_inode_bitmap_csum(&self.super_block, &bitmap);
@@ -272,7 +270,7 @@ impl Ext4 {
 
         // Free the inode
         if bitmap.is_bit_clear(idx_in_bg as usize) {
-            return Err(Ext4Error::with_message(
+            return Err(Ext4Error::with_msg(
                 ErrCode::EINVAL,
                 "Inode double free".to_owned(),
             ));

+ 1 - 53
src/ext4/dir.rs

@@ -4,30 +4,6 @@ use crate::ext4_defs::*;
 use crate::prelude::*;
 
 impl Ext4 {
-    /// Create a new directory. `path` is the absolute path of the new directory.
-    pub fn make_dir(&mut self, path: &str) -> Result<()> {
-        self.generic_open(
-            EXT4_ROOT_INO,
-            path,
-            OpenFlags::O_CREAT,
-            Some(FileType::Directory),
-        )
-        .map(|_| {
-            info!("ext4_dir_mk: {} ok", path);
-        })
-    }
-
-    /// Remove a directory recursively. `path` is the absolute path of the directory.
-    pub fn remove_dir(&mut self, path: &str) -> Result<()> {
-        let mut dir = self.generic_open(
-            EXT4_ROOT_INO,
-            path,
-            OpenFlags::O_RDONLY,
-            Some(FileType::Directory),
-        )?;
-        self.remove_dir_recursive(&mut dir)
-    }
-
     /// Find a directory entry that matches a given name under a parent directory
     pub(super) fn dir_find_entry(&self, dir: &InodeRef, name: &str) -> Result<DirEntry> {
         info!("Dir find entry: dir {}, name {}", dir.id, name);
@@ -117,7 +93,7 @@ impl Ext4 {
     }
 
     /// Get all entries under a directory
-    pub(super) fn dir_get_all_entries(&self, dir: &mut InodeRef) -> Result<Vec<DirEntry>> {
+    pub(super) fn dir_get_all_entries(&self, dir: &InodeRef) -> Result<Vec<DirEntry>> {
         info!("Dir get all entries: dir {}", dir.id);
         let inode_size: u32 = dir.inode.size;
         let total_blocks: u32 = inode_size / BLOCK_SIZE as u32;
@@ -248,32 +224,4 @@ impl Ext4 {
         }
         false
     }
-
-    /// Remove a directory recursively.
-    fn remove_dir_recursive(&mut self, dir: &mut InodeRef) -> Result<()> {
-        let entries = self.dir_get_all_entries(dir)?;
-        for entry in entries {
-            if entry.compare_name(".") || entry.compare_name("..") {
-                continue;
-            }
-            let mut child = self.read_inode(entry.inode());
-            if child.inode.is_dir() {
-                // Remove child dir recursively
-                self.remove_dir_recursive(&mut child)?;
-            } else {
-                // Remove file
-                self.generic_remove(
-                    dir.id,
-                    &entry.name().map_err(|_| {
-                        Ext4Error::with_message(
-                            ErrCode::EINVAL,
-                            "Invalid dir entry name".to_owned(),
-                        )
-                    })?,
-                    Some(FileType::RegularFile),
-                )?;
-            }
-        }
-        Ok(())
-    }
 }

+ 0 - 238
src/ext4/file.rs

@@ -1,238 +0,0 @@
-use super::Ext4;
-use crate::constants::*;
-use crate::ext4_defs::*;
-use crate::prelude::*;
-use crate::return_err_with_msg;
-use core::cmp::min;
-
-impl Ext4 {
-    /// Open a regular file, return a file descriptor
-    pub fn open_file(&mut self, path: &str, flags: &str) -> Result<File> {
-        // open flags
-        let open_flags = OpenFlags::from_str(flags).unwrap();
-        // TODO:journal
-        if open_flags.contains(OpenFlags::O_CREAT) {
-            self.trans_start();
-        }
-        // open file
-        let res = self.generic_open(EXT4_ROOT_INO, path, open_flags, Some(FileType::RegularFile));
-        res.map(|inode| {
-            File::new(
-                self.mount_point.clone(),
-                inode.id,
-                open_flags.bits(),
-                inode.inode.size(),
-            )
-        })
-    }
-
-    /// Read `read_buf.len()` bytes from the file
-    pub fn read_file(&self, file: &mut File, read_buf: &mut [u8]) -> Result<usize> {
-        let read_size = read_buf.len();
-        // Read no bytes
-        if read_size == 0 {
-            return Ok(0);
-        }
-        // Get the inode of the file
-        let mut inode_ref = self.read_inode(file.inode);
-        // sync file size
-        file.fsize = inode_ref.inode.size();
-
-        // Check if the file is a softlink
-        if inode_ref.inode.is_softlink() {
-            // TODO: read softlink
-            log::debug!("ext4_read unsupported softlink");
-        }
-
-        // Calc the actual size to read
-        let size_to_read = min(read_size, file.fsize as usize - file.fpos);
-        // Calc the start block of reading
-        let start_iblock = (file.fpos / BLOCK_SIZE) as LBlockId;
-        // Calc the length that is not aligned to the block size
-        let misaligned = file.fpos % BLOCK_SIZE;
-
-        let mut cursor = 0;
-        let mut iblock = start_iblock;
-        // Read first block
-        if misaligned > 0 {
-            let read_len = min(BLOCK_SIZE - misaligned, size_to_read);
-            let fblock = self.extent_get_pblock(&mut inode_ref, start_iblock)?;
-            let block = self.read_block(fblock);
-            // Copy data from block to the user buffer
-            read_buf[cursor..cursor + read_len]
-                .copy_from_slice(block.read_offset(misaligned, read_len));
-            cursor += read_len;
-            file.fpos += read_len;
-            iblock += 1;
-        }
-        // Continue with full block reads
-        while cursor < size_to_read {
-            let read_len = min(BLOCK_SIZE, size_to_read - cursor);
-            let fblock = self.extent_get_pblock(&mut inode_ref, iblock)?;
-            let block = self.read_block(fblock);
-            // Copy data from block to the user buffer
-            read_buf[cursor..cursor + read_len].copy_from_slice(block.read_offset(0, read_len));
-            cursor += read_len;
-            file.fpos += read_len;
-            iblock += 1;
-        }
-
-        Ok(cursor)
-    }
-
-    /// Write `data` to file
-    pub fn write_file(&mut self, file: &mut File, data: &[u8]) -> Result<()> {
-        let write_size = data.len();
-        let mut inode_ref = self.read_inode(file.inode);
-        // Sync ext file
-        file.fsize = inode_ref.inode.size();
-
-        // Calc the start and end block of reading
-        let start_iblock = (file.fpos / BLOCK_SIZE) as LBlockId;
-        let end_iblock = ((file.fpos + write_size) / BLOCK_SIZE) as LBlockId;
-        // Calc the block count of the file
-        let block_count = (file.fsize as usize + BLOCK_SIZE - 1) / BLOCK_SIZE;
-        // Append enough block for writing
-        let append_block_count = end_iblock + 1 - block_count as LBlockId;
-        for _ in 0..append_block_count {
-            self.inode_append_block(&mut inode_ref)?;
-        }
-
-        // Write data
-        let mut cursor = 0;
-        let mut iblock = start_iblock;
-        while cursor < write_size {
-            let write_len = min(BLOCK_SIZE, write_size - cursor);
-            let fblock = self.extent_get_pblock(&mut inode_ref, iblock)?;
-            let mut block = self.read_block(fblock);
-            block.write_offset(file.fpos % BLOCK_SIZE, &data[cursor..cursor + write_len]);
-            self.write_block(&block);
-
-            cursor += write_len;
-            file.fpos += write_len;
-            iblock += 1;
-        }
-        Ok(())
-    }
-
-    /// Remove a regular file
-    pub fn remove_file(&mut self, path: &str) -> Result<()> {
-        self.generic_remove(EXT4_ROOT_INO, path, Some(FileType::RegularFile))
-    }
-
-    /// Open an object of any type in the filesystem. Return the inode
-    /// of the object if found.
-    ///
-    /// ## Params
-    /// * `root` - The inode id of the starting directory for the search.
-    /// * `path` - The path of the object to be opened.
-    /// * `flags` - The open flags. If the flags contains `O_CREAT`, and `expect_type`
-    ///    is provided, the function will create a new inode of the specified type.
-    /// * `expect_type` - The expect type of object to open, optional. If this
-    ///    parameter is provided, the function will check the type of the object
-    ///    to open.
-    pub fn generic_open(
-        &mut self,
-        root: InodeId,
-        path: &str,
-        flags: OpenFlags,
-        expect_type: Option<FileType>,
-    ) -> Result<InodeRef> {
-        // Search from the given parent inode
-        info!("generic_open: root {}, path {}", root, path);
-        let mut cur = self.read_inode(root);
-        let search_path = Self::split_path(path);
-
-        for (i, path) in search_path.iter().enumerate() {
-            let res = self.dir_find_entry(&cur, path);
-            match res {
-                Ok(entry) => {
-                    cur = self.read_inode(entry.inode());
-                }
-                Err(e) => {
-                    if e.code() != ErrCode::ENOENT {
-                        // dir search failed with error other than ENOENT
-                        return_err_with_msg!(ErrCode::ENOTSUP, "dir search failed".to_owned());
-                    }
-                    if !flags.contains(OpenFlags::O_CREAT) || expect_type.is_none() {
-                        // `O_CREAT` and `expect_type` must be provided together to
-                        // create a new object
-                        return_err_with_msg!(ErrCode::ENOENT, "file not found".to_owned());
-                    }
-                    // Create file/directory
-                    let mut child = if i == search_path.len() - 1 {
-                        self.create_inode(expect_type.unwrap())
-                    } else {
-                        self.create_inode(FileType::Directory)
-                    }?;
-                    // Link the new inode
-                    self.link(&mut cur, &mut child, path).map_err(|_| {
-                        Ext4Error::with_message(ErrCode::ELINKFAIL, "link fail".to_owned())
-                    })?;
-                    // Write back parent and child
-                    self.write_inode_with_csum(&mut cur);
-                    self.write_inode_with_csum(&mut child);
-                    // Update parent
-                    cur = child;
-                }
-            }
-        }
-        // `cur` is the target inode, check type if `expect_type` os provided
-        if let Some(expect_type) = expect_type {
-            if cur.inode.file_type() != expect_type {
-                return_err_with_msg!(ErrCode::EISDIR, "inode type mismatch".to_owned());
-            }
-        }
-        Ok(cur)
-    }
-
-    /// Remove an object of any type from the filesystem. Return the inode
-    /// of the object if found.
-    ///
-    /// ## Params
-    /// * `root` - The inode id of the starting directory for the search.
-    /// * `path` - The path of the object to be removed.
-    /// * `expect_type` - The expect type of object to open, optional. If this
-    ///    parameter is provided, the function will check the type of the object
-    ///    to open.
-    pub(super) fn generic_remove(
-        &mut self,
-        root: InodeId,
-        path: &str,
-        expect_type: Option<FileType>,
-    ) -> Result<()> {
-        // Get the parent directory path and the file name
-        let mut search_path = Self::split_path(path);
-        let file_name = &search_path.split_off(search_path.len() - 1)[0];
-        let parent_path = search_path.join("/");
-        // Get the parent directory inode
-        let mut parent_inode = self.generic_open(
-            root,
-            &parent_path,
-            OpenFlags::O_RDONLY,
-            Some(FileType::Directory),
-        )?;
-        // Get the file inode, check the existence and type
-        let mut child_inode =
-            self.generic_open(parent_inode.id, file_name, OpenFlags::O_RDONLY, expect_type)?;
-
-        // Remove the file from the parent directory
-        self.dir_remove_entry(&mut parent_inode, &file_name)?;
-        // Update the link count of inode
-        let link_cnt = child_inode.inode.links_cnt() - 1;
-        if link_cnt == 0 {
-            // Free the inode of the file if link count is 0
-            return self.free_inode(&mut child_inode);
-        }
-        child_inode.inode.set_links_cnt(link_cnt);
-        Ok(())
-    }
-
-    fn split_path(path: &str) -> Vec<String> {
-        let _ = path.trim_start_matches("/");
-        if path.is_empty() {
-            return vec![]; // root
-        }
-        path.split("/").map(|s| s.to_string()).collect()
-    }
-}

+ 31 - 7
src/ext4/link.rs

@@ -1,24 +1,48 @@
 use super::Ext4;
-use crate::prelude::*;
 use crate::ext4_defs::*;
+use crate::prelude::*;
 
 impl Ext4 {
-    pub fn link(
+    /// Link a child inode to a parent directory.
+    pub(super) fn link_inode(
         &mut self,
         parent: &mut InodeRef,
         child: &mut InodeRef,
         name: &str,
     ) -> Result<()> {
         // Add entry to parent directory
-        let _r = self.dir_add_entry(parent, child, name);
-        child.inode.links_count += 1;
-
-        if child.inode.is_dir() {
-            // add '.' and '..' entries
+        self.dir_add_entry(parent, child, name)?;
+        // Update link count of child
+        let link_cnt = child.inode.links_cnt() + 1;
+        child.inode.set_links_cnt(link_cnt);
+        // Add '.' and '..' entries if child is a newly created directory
+        if link_cnt == 1 && child.inode.is_dir() {
             let child_self = child.clone();
             self.dir_add_entry(child, &child_self, ".")?;
             self.dir_add_entry(child, parent, "..")?;
         }
+        self.write_inode_with_csum(child);
+        Ok(())
+    }
+
+    /// Unlink a child inode from a parent directory.
+    /// Free the inode if link count is 0.
+    pub(super) fn unlink_inode(
+        &mut self,
+        parent: &mut InodeRef,
+        child: &mut InodeRef,
+        name: &str,
+    ) -> Result<()> {
+        // Remove entry from parent directory
+        self.dir_remove_entry(parent, name)?;
+        // Update link count of child
+        let link_cnt = child.inode.links_cnt() - 1;
+        if link_cnt == 0 {
+            // Free the inode if link count is 0
+            return self.free_inode(child);
+        }
+        child.inode.set_links_cnt(link_cnt);
+        self.write_inode_with_csum(child);
         Ok(())
     }
 }

+ 311 - 0
src/ext4/low_level.rs

@@ -0,0 +1,311 @@
+//! Low-level operations of Ext4 filesystem.
+//!
+//! These interfaces are designed and arranged coresponding to FUSE low-level ops.
+//! Ref: https://libfuse.github.io/doxygen/structfuse__lowlevel__ops.html
+
+use super::Ext4;
+use crate::constants::*;
+use crate::ext4_defs::*;
+use crate::prelude::*;
+use crate::return_err_with_msg_str;
+use core::cmp::min;
+
+impl Ext4 {
+    /// Read an indoe
+    ///
+    /// # Params
+    ///
+    /// * `id` - inode id
+    ///
+    /// # Return
+    ///
+    /// An inode reference, combing id and the inode itself
+    pub fn inode(&self, id: InodeId) -> InodeRef {
+        self.read_inode(id)
+    }
+
+    /// Create and open a file. This function will not check the existence
+    /// of the file. Call `lookup` to check beforehand.
+    ///
+    /// # Params
+    ///
+    /// * `parent` - parent directory inode id
+    /// * `name` - file name
+    /// * `mode` - file type and mode with which to create the new file
+    /// * `flags` - open flags
+    ///
+    /// # Return
+    ///
+    /// `Ok(fh)` - File handler of the new file
+    pub fn create(
+        &mut self,
+        parent: InodeId,
+        name: &str,
+        mode: InodeMode,
+        flags: OpenFlags,
+    ) -> Result<FileHandler> {
+        let mut parent = self.read_inode(parent);
+        // Can only create a file in a directory
+        if !parent.inode.is_dir() {
+            return_err_with_msg_str!(ErrCode::ENOTDIR, "Not a directory");
+        }
+        // Create child inode and link it to parent directory
+        let mut child = self.create_inode(mode)?;
+        self.link_inode(&mut parent, &mut child, name)
+            .map_err(|_| Ext4Error::with_msg_str(ErrCode::ELINKFAIL, "link fail"))?;
+        // Create file handler
+        Ok(FileHandler::new(child.id, flags, child.inode.size()))
+    }
+
+    /// Open a regular file, return a file handler if the given inode id
+    /// indicates a regular file.
+    ///
+    /// # Params
+    ///
+    /// * `inode_id` - inode id of the file
+    /// * `flags` - open flags
+    ///
+    /// # Return
+    ///
+    /// File handler of the file
+    pub fn open(&mut self, inode_id: InodeId, flags: OpenFlags) -> Result<FileHandler> {
+        let inode_ref = self.read_inode(inode_id);
+        // Can only open a regular file
+        if !inode_ref.inode.is_file() {
+            return_err_with_msg_str!(ErrCode::EINVAL, "Not a regular file");
+        }
+        Ok(FileHandler::new(inode_id, flags, inode_ref.inode.size()))
+    }
+
+    /// Read data from a file. This function will read exactly `buf.len()`
+    /// bytes unless the end of the file is reached.
+    ///
+    /// # Params
+    ///
+    /// * `file` - the file handler, acquired by `open` or `create`
+    /// * `buf` - the buffer to store the data
+    ///
+    /// # Return
+    ///
+    /// `Ok(usize)` - the actual number of bytes read
+    /// 
+    /// TODO: handle EOF
+    pub fn read(&mut self, file: &mut FileHandler, buf: &mut [u8]) -> Result<usize> {
+        let read_size = buf.len();
+        // Read no bytes
+        if read_size == 0 {
+            return Ok(0);
+        }
+        // Get the inode of the file
+        let mut inode_ref = self.read_inode(file.inode);
+        // sync file size
+        file.fsize = inode_ref.inode.size();
+
+        // Calc the actual size to read
+        let size_to_read = min(read_size, file.fsize as usize - file.fpos);
+        // Calc the start block of reading
+        let start_iblock = (file.fpos / BLOCK_SIZE) as LBlockId;
+        // Calc the length that is not aligned to the block size
+        let misaligned = file.fpos % BLOCK_SIZE;
+
+        let mut cursor = 0;
+        let mut iblock = start_iblock;
+        // Read first block
+        if misaligned > 0 {
+            let read_len = min(BLOCK_SIZE - misaligned, size_to_read);
+            let fblock = self.extent_get_pblock(&mut inode_ref, start_iblock)?;
+            let block = self.read_block(fblock);
+            // Copy data from block to the user buffer
+            buf[cursor..cursor + read_len].copy_from_slice(block.read_offset(misaligned, read_len));
+            cursor += read_len;
+            file.fpos += read_len;
+            iblock += 1;
+        }
+        // Continue with full block reads
+        while cursor < size_to_read {
+            let read_len = min(BLOCK_SIZE, size_to_read - cursor);
+            let fblock = self.extent_get_pblock(&mut inode_ref, iblock)?;
+            let block = self.read_block(fblock);
+            // Copy data from block to the user buffer
+            buf[cursor..cursor + read_len].copy_from_slice(block.read_offset(0, read_len));
+            cursor += read_len;
+            file.fpos += read_len;
+            iblock += 1;
+        }
+
+        Ok(cursor)
+    }
+
+    /// Write data to a file. This function will write exactly `data.len()` bytes.
+    ///
+    /// # Params
+    ///
+    /// * `file` - the file handler, acquired by `open` or `create`
+    /// * `data` - the data to write
+    /// 
+    /// TODO: handle EOF
+    pub fn write(&mut self, file: &mut FileHandler, data: &[u8]) -> Result<()> {
+        let write_size = data.len();
+        let mut inode_ref = self.read_inode(file.inode);
+        file.fsize = inode_ref.inode.size();
+
+        // Calc the start and end block of reading
+        let start_iblock = (file.fpos / BLOCK_SIZE) as LBlockId;
+        let end_iblock = ((file.fpos + write_size) / BLOCK_SIZE) as LBlockId;
+        // Calc the block count of the file
+        let block_count = (file.fsize as usize + BLOCK_SIZE - 1) / BLOCK_SIZE;
+        // Append enough block for writing
+        let append_block_count = end_iblock + 1 - block_count as LBlockId;
+        for _ in 0..append_block_count {
+            self.inode_append_block(&mut inode_ref)?;
+        }
+
+        // Write data
+        let mut cursor = 0;
+        let mut iblock = start_iblock;
+        while cursor < write_size {
+            let write_len = min(BLOCK_SIZE, write_size - cursor);
+            let fblock = self.extent_get_pblock(&mut inode_ref, iblock)?;
+            let mut block = self.read_block(fblock);
+            block.write_offset(file.fpos % BLOCK_SIZE, &data[cursor..cursor + write_len]);
+            self.write_block(&block);
+
+            cursor += write_len;
+            file.fpos += write_len;
+            iblock += 1;
+        }
+        Ok(())
+    }
+
+    /// Create a hard link. This function will not check name conflict.
+    /// Call `lookup` to check beforehand.
+    ///
+    /// # Params
+    ///
+    /// * `child` - the inode of the file to link
+    /// * `parent` - the inode of the directory to link to
+    ///
+    /// # Return
+    ///
+    /// `Ok(child)` - An inode reference to the child inode.
+    pub fn link(&mut self, child: InodeId, parent: InodeId, name: &str) -> Result<InodeRef> {
+        let mut parent = self.read_inode(parent);
+        // Can only link to a directory
+        if !parent.inode.is_dir() {
+            return_err_with_msg_str!(ErrCode::ENOTDIR, "Not a directory");
+        }
+        let mut child = self.read_inode(child);
+        self.link_inode(&mut parent, &mut child, name)?;
+        Ok(child)
+    }
+
+    /// Unlink a file. This function will not check the existence of the file.
+    /// Call `lookup` to check beforehand.
+    ///
+    /// # Params
+    ///
+    /// * `parent` - the inode of the directory to unlink from
+    /// * `name` - the name of the file to unlink
+    pub fn unlink(&mut self, parent: InodeId, name: &str) -> Result<()> {
+        let mut parent = self.read_inode(parent);
+        // Can only unlink from a directory
+        if !parent.inode.is_dir() {
+            return_err_with_msg_str!(ErrCode::ENOTDIR, "Not a directory");
+        }
+        let child_id = self.dir_find_entry(&parent, name)?.inode();
+        let mut child = self.read_inode(child_id);
+        self.unlink_inode(&mut parent, &mut child, name)
+    }
+
+    /// Create a directory. This function will not check name conflict.
+    /// Call `lookup` to check beforehand.
+    ///
+    /// # Params
+    ///
+    /// * `parent` - the inode of the directory to create in
+    /// * `name` - the name of the directory to create
+    /// * `mode` - the mode of the directory to create, type field will be ignored
+    ///
+    /// # Return
+    ///
+    /// `Ok(child)` - An inode reference to the new directory.
+    pub fn mkdir(&mut self, parent: InodeId, name: &str, mode: InodeMode) -> Result<InodeRef> {
+        let mut parent = self.read_inode(parent);
+        // Can only create a directory in a directory
+        if !parent.inode.is_dir() {
+            return_err_with_msg_str!(ErrCode::ENOTDIR, "Not a directory");
+        }
+        // Create file/directory
+        let mode = mode & InodeMode::PERM_MASK | InodeMode::DIRECTORY;
+        let mut child = self.create_inode(mode)?;
+        // Link the new inode
+        self.link_inode(&mut parent, &mut child, name)
+            .map_err(|_| Ext4Error::with_msg_str(ErrCode::ELINKFAIL, "link fail"))?;
+
+        Ok(child)
+    }
+
+    /// Look up a directory entry by name.
+    ///
+    /// # Params
+    ///
+    /// * `parent` - the inode of the directory to look in
+    /// * `name` - the name of the entry to look for
+    ///
+    /// # Return
+    ///
+    /// `Ok(child)`: The inode id to which the directory entry points.
+    pub fn lookup(&mut self, parent: InodeId, name: &str) -> Result<InodeId> {
+        let parent = self.read_inode(parent);
+        // Can only lookup in a directory
+        if !parent.inode.is_dir() {
+            return_err_with_msg_str!(ErrCode::ENOTDIR, "Not a directory");
+        }
+        self.dir_find_entry(&parent, name)
+            .map(|entry| entry.inode())
+    }
+
+    /// List all directory entries in a directory.
+    ///
+    /// # Params
+    ///
+    /// * `inode` - the inode of the directory to list
+    ///
+    /// # Return
+    ///
+    /// `Ok(entries)` - A vector of directory entries in the directory.
+    pub fn list(&self, inode: InodeId) -> Result<Vec<DirEntry>> {
+        let inode_ref = self.read_inode(inode);
+        // Can only list a directory
+        if inode_ref.inode.file_type() != FileType::Directory {
+            return_err_with_msg_str!(ErrCode::ENOTDIR, "Not a directory");
+        }
+        self.dir_get_all_entries(&inode_ref)
+    }
+
+    /// Remove an empty directory. Return `ENOTEMPTY` if the child directory
+    /// is not empty.
+    ///
+    /// # Params
+    ///
+    /// * `parent` - the parent directory where the directory is located
+    /// * `name` - the name of the directory to remove
+    pub fn rmdir(&mut self, parent: InodeId, name: &str) -> Result<()> {
+        let mut parent = self.read_inode(parent);
+        // Can only remove a directory in a directory
+        if !parent.inode.is_dir() {
+            return_err_with_msg_str!(ErrCode::ENOTDIR, "Parent not a directory");
+        }
+        let mut child = self.read_inode(self.dir_find_entry(&parent, name)?.inode());
+        // Child must be a directory
+        if !child.inode.is_dir() {
+            return_err_with_msg_str!(ErrCode::ENOTDIR, "Child not a directory");
+        }
+        // Child must be empty
+        if self.dir_get_all_entries(&child)?.len() > 2 {
+            return_err_with_msg_str!(ErrCode::ENOTEMPTY, "Directory not empty");
+        }
+        // Remove directory entry
+        self.unlink_inode(&mut parent, &mut child, name)
+    }
+}

+ 5 - 8
src/ext4/mod.rs

@@ -5,16 +5,16 @@ use crate::prelude::*;
 mod alloc;
 mod dir;
 mod extent;
-mod file;
-mod journal;
 mod link;
+mod journal;
 mod rw;
+mod low_level;
+mod high_level;
 
 #[derive(Debug)]
 pub struct Ext4 {
-    pub block_device: Arc<dyn BlockDevice>,
-    pub super_block: SuperBlock,
-    pub mount_point: MountPoint,
+    block_device: Arc<dyn BlockDevice>,
+    super_block: SuperBlock,
 }
 
 impl Ext4 {
@@ -27,13 +27,10 @@ impl Ext4 {
         // TODO: if the main superblock is corrupted, should we load the backup?
         let block = block_device.read_block(0);
         let super_block = block.read_offset_as::<SuperBlock>(BASE_OFFSET);
-        // Root mount point
-        let mount_point = MountPoint::new("/");
         // Create Ext4 instance
         let mut ext4 = Self {
             super_block,
             block_device,
-            mount_point,
         };
         // Create root directory
         ext4.create_root_inode()?;

+ 2 - 1
src/ext4_defs/dir_entry.rs

@@ -11,6 +11,7 @@ use crate::prelude::*;
 use crate::AsBytes;
 
 #[repr(C)]
+#[derive(Clone, Copy)]
 pub union DirEnInner {
     pub name_length_high: u8, // 高8位的文件名长度
     pub inode_type: FileType, // 引用的inode的类型(在rev >= 0.5中)
@@ -31,7 +32,7 @@ impl Default for DirEnInner {
 }
 
 #[repr(C)]
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub struct DirEntry {
     inode: InodeId,    // 该目录项指向的inode的编号
     rec_len: u16,      // 到下一个目录项的距离

+ 9 - 14
src/ext4_defs/file.rs

@@ -1,25 +1,20 @@
-use super::MountPoint;
 use crate::prelude::*;
 
-/// 文件描述符
 #[derive(Debug)]
-pub struct File {
-    /// 挂载点句柄
-    pub mp: MountPoint,
-    /// 文件 inode id
+pub struct FileHandler {
+    /// The inode number of the file
     pub inode: InodeId,
-    /// 打开标志
-    pub flags: u32,
-    /// 文件大小
+    /// The open flags for the file
+    pub flags: OpenFlags,
+    /// The size of the file
     pub fsize: u64,
-    /// 实际文件位置
+    /// The current position in the file
     pub fpos: usize,
 }
 
-impl File {
-    pub fn new(mp: MountPoint, inode: InodeId, flags: u32, fsize: u64) -> Self {
-        File {
-            mp,
+impl FileHandler {
+    pub fn new(inode: InodeId, flags: OpenFlags, fsize: u64) -> Self {
+        FileHandler {
             inode,
             flags,
             fsize,

+ 2 - 2
src/ext4_defs/inode.rs

@@ -63,7 +63,7 @@ impl InodeMode {
         }) | (perm & InodeMode::PERM_MASK)
     }
     /// Get permission bits of an inode mode.
-    pub fn perm(&self) -> u16 {
+    pub fn perm_bits(&self) -> u16 {
         (*self & InodeMode::PERM_MASK).bits() as u16
     }
     /// Get the file type of an inode mode.
@@ -286,7 +286,7 @@ impl InodeRef {
         let block = block_device.read_block(block_id);
         Self {
             id,
-            inode: Inode::from_bytes(block.read_offset(offset, size_of::<Inode>())),
+            inode: block.read_offset_as(offset),
         }
     }
 

+ 4 - 2
src/lib.rs

@@ -2,12 +2,14 @@
 #![no_std]
 
 mod constants;
+mod error;
 mod ext4;
 mod ext4_defs;
-mod error;
 mod jbd2;
 mod prelude;
 
+pub use error::*;
 pub use ext4::*;
 pub use ext4_defs::*;
-pub use error::*;
+
+pub use constants::{BLOCK_SIZE, INODE_BLOCK_SIZE};