Bladeren bron

feat: remove direcory

liujingx 11 maanden geleden
bovenliggende
commit
2aaced6b2c
5 gewijzigde bestanden met toevoegingen van 255 en 149 verwijderingen
  1. 34 28
      ext4_test/src/main.rs
  2. 6 7
      src/ext4/alloc.rs
  3. 96 22
      src/ext4/dir.rs
  4. 119 90
      src/ext4/file.rs
  5. 0 2
      src/ext4/link.rs

+ 34 - 28
ext4_test/src/main.rs

@@ -1,5 +1,4 @@
 use ext4_rs::{Block, BlockDevice, Ext4, BLOCK_SIZE};
-use log::warn;
 use simple_logger::SimpleLogger;
 use std::fs::{File, OpenOptions};
 use std::io::{Read, Seek, SeekFrom, Write};
@@ -60,33 +59,32 @@ fn open_ext4() -> Ext4 {
 }
 
 fn mkdir_test(ext4: &mut Ext4) {
-    ext4.mkdir("d1").expect("mkdir failed");
-    ext4.mkdir("d1/d2").expect("mkdir failed");
-    ext4.mkdir("d1/d2/d3").expect("mkdir failed");
-    ext4.mkdir("d1/d2/d3/d4").expect("mkdir failed");
-    ext4.mkdir("d2").expect("mkdir failed");
-    ext4.mkdir("d2/d3").expect("mkdir failed");
-    ext4.mkdir("d2/d3/d4").expect("mkdir failed");
-    ext4.mkdir("d3").expect("mkdir failed");
+    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");
 }
 
 fn open_test(ext4: &mut Ext4) {
-    ext4.open("d1/d2/d3/d4/f1", "w+", true)
-        .expect("open failed");
-    ext4.open("d1/d2/d3/d4/f1", "r", true).expect("open failed");
-    ext4.open("d1/d2/d3/d4/f5", "a", true).expect("open failed");
-    ext4.open("d2/f4", "w+", true).expect("open failed");
-    ext4.open("f1", "w+", true).expect("open failed");
+    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 read_write_test(ext4: &mut Ext4) {
     let wbuffer = "hello world".as_bytes();
-    let mut wfile = ext4.open("d3/f0", "w+", true).expect("open failed");
-    ext4.write(&mut wfile, wbuffer).expect("write failed");
+    let mut wfile = ext4.open_file("d3/f0", "w+").expect("open failed");
+    ext4.write_file(&mut wfile, wbuffer).expect("write failed");
 
     let mut rbuffer = vec![0u8; wbuffer.len()];
-    let mut rfile = ext4.open("d3/f0", "r", true).expect("open failed");
-    ext4.read(&mut rfile, &mut rbuffer, wbuffer.len())
+    let mut rfile = ext4.open_file("d3/f0", "r").expect("open failed");
+    ext4.read_file(&mut rfile, &mut rbuffer)
         .expect("read failed");
 
     assert_eq!(wbuffer, rbuffer);
@@ -94,12 +92,12 @@ fn read_write_test(ext4: &mut Ext4) {
 
 fn large_read_write_test(ext4: &mut Ext4) {
     let wbuffer = vec![99u8; 1024 * 1024 * 16];
-    let mut wfile = ext4.open("d3/f1", "w+", true).expect("open failed");
-    ext4.write(&mut wfile, &wbuffer).expect("write failed");
+    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("d3/f1", "r", true).expect("open failed");
+    let mut rfile = ext4.open_file("d3/f1", "r").expect("open failed");
     let mut rbuffer = vec![0u8; wbuffer.len()];
-    ext4.read(&mut rfile, &mut rbuffer, wbuffer.len())
+    ext4.read_file(&mut rfile, &mut rbuffer)
         .expect("read failed");
 
     assert_eq!(wbuffer, rbuffer);
@@ -107,12 +105,18 @@ fn large_read_write_test(ext4: &mut Ext4) {
 
 fn remove_file_test(ext4: &mut Ext4) {
     ext4.remove_file("d3/f0").expect("remove file failed");
-    ext4.open("d3/f0", "r", true).expect_err("open failed");
+    ext4.open_file("d3/f0", "r").expect_err("open failed");
     ext4.remove_file("d3/f1").expect("remove file failed");
-    ext4.open("d3/f1", "r", true).expect_err("open failed");
+    ext4.open_file("d3/f1", "r").expect_err("open failed");
     ext4.remove_file("f1").expect("remove file failed");
-    ext4.open("f1", "r", true).expect_err("open failed");
-    ext4.remove_file("d1/not_exist").expect_err("remove file failed");
+    ext4.open_file("f1", "r").expect_err("open failed");
+    ext4.remove_file("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");
 }
 
 fn main() {
@@ -130,7 +134,9 @@ fn main() {
     println!("read write test done");
     large_read_write_test(&mut ext4);
     println!("large read write test done");
-    log::set_max_level(log::LevelFilter::Debug);
     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");
 }

+ 6 - 7
src/ext4/alloc.rs

@@ -54,21 +54,20 @@ impl Ext4 {
     }
 
     /// Free an allocated inode and all data blocks allocated for it
-    pub(super) fn free_inode(&mut self, inode_id: InodeId) -> Result<()> {
-        let mut inode = self.read_inode(inode_id);
+    pub(super) fn free_inode(&mut self, inode_ref: &mut InodeRef) -> Result<()> {
         // Free the data blocks allocated for the inode
-        let pblocks = self.extent_get_all_pblocks(&inode)?;
+        let pblocks = self.extent_get_all_pblocks(&inode_ref)?;
         for pblock in pblocks {
             // Deallocate the block
-            self.dealloc_block(&mut inode, pblock)?;
+            self.dealloc_block(inode_ref, pblock)?;
             // Clear the block content
             self.write_block(&Block::new(pblock, [0; BLOCK_SIZE]));
         }
         // Deallocate the inode
-        self.dealloc_inode(&inode)?;
+        self.dealloc_inode(&inode_ref)?;
         // Clear the inode content
-        inode.inode = unsafe { core::mem::zeroed() };
-        self.write_inode_without_csum(&mut inode);
+        inode_ref.inode = unsafe { core::mem::zeroed() };
+        self.write_inode_without_csum(inode_ref);
         Ok(())
     }
 

+ 96 - 22
src/ext4/dir.rs

@@ -4,16 +4,40 @@ 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, parent: &InodeRef, name: &str) -> Result<DirEntry> {
-        info!("Dir find entry {} under parent {}", name, parent.id);
-        let inode_size: u32 = parent.inode.size;
+    pub(super) fn dir_find_entry(&self, dir: &InodeRef, name: &str) -> Result<DirEntry> {
+        info!("Dir find entry: dir {}, name {}", dir.id, name);
+        let inode_size: u32 = dir.inode.size;
         let total_blocks: u32 = inode_size / BLOCK_SIZE as u32;
-        let mut iblock: LBlockId = 0;
 
+        let mut iblock: LBlockId = 0;
         while iblock < total_blocks {
             // Get the fs block id
-            let fblock = self.extent_get_pblock(parent, iblock)?;
+            let fblock = self.extent_get_pblock(dir, iblock)?;
             // Load block from disk
             let block = self.read_block(fblock);
             // Find the entry in block
@@ -29,22 +53,22 @@ impl Ext4 {
     /// Add an entry to a directory
     pub(super) fn dir_add_entry(
         &mut self,
-        parent: &mut InodeRef,
+        dir: &mut InodeRef,
         child: &InodeRef,
         name: &str,
     ) -> Result<()> {
         info!(
-            "Dir add entry: parent {}, child {}, path {}",
-            parent.id, child.id, name
+            "Dir add entry: dir {}, child {}, name {}",
+            dir.id, child.id, name
         );
-        let inode_size = parent.inode.size();
+        let inode_size = dir.inode.size();
         let total_blocks = inode_size as u32 / BLOCK_SIZE as u32;
 
         // Try finding a block with enough space
         let mut iblock: LBlockId = 0;
         while iblock < total_blocks {
             // Get the parent physical block id
-            let fblock = self.extent_get_pblock(parent, iblock)?;
+            let fblock = self.extent_get_pblock(dir, iblock)?;
             // Load the parent block from disk
             let mut block = self.read_block(fblock);
             // Try inserting the entry to parent block
@@ -57,7 +81,7 @@ impl Ext4 {
 
         // No free block found - needed to allocate a new data block
         // Append a new data block
-        let (_, fblock) = self.inode_append_block(parent)?;
+        let (_, fblock) = self.inode_append_block(dir)?;
         // Load new block
         let mut new_block = self.read_block(fblock);
         // Write the entry to block
@@ -67,16 +91,16 @@ impl Ext4 {
     }
 
     /// Remove a entry from a directory
-    pub(super) fn dir_remove_entry(&mut self, parent: &mut InodeRef, name: &str) -> Result<()> {
-        info!("Dir remove entry: parent {}, path {}", parent.id, name);
-        let inode_size = parent.inode.size();
+    pub(super) fn dir_remove_entry(&mut self, dir: &mut InodeRef, name: &str) -> Result<()> {
+        info!("Dir remove entry: dir {}, path {}", dir.id, name);
+        let inode_size = dir.inode.size();
         let total_blocks = inode_size as u32 / BLOCK_SIZE as u32;
 
         // Check each block
         let mut iblock: LBlockId = 0;
         while iblock < total_blocks {
             // Get the parent physical block id
-            let fblock = self.extent_get_pblock(parent, iblock)?;
+            let fblock = self.extent_get_pblock(dir, iblock)?;
             // Load the block from disk
             let mut block = self.read_block(fblock);
             // Try removing the entry
@@ -92,6 +116,26 @@ impl Ext4 {
         Err(Ext4Error::new(ErrCode::ENOENT))
     }
 
+    /// Get all entries under a directory
+    pub(super) fn dir_get_all_entries(&self, dir: &mut 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;
+        let mut entries: Vec<DirEntry> = Vec::new();
+
+        let mut iblock: LBlockId = 0;
+        while iblock < total_blocks {
+            // Get the fs block id
+            let fblock = self.extent_get_pblock(dir, iblock)?;
+            // Load block from disk
+            let block = self.read_block(fblock);
+            // Get all entries from block
+            Self::get_all_entries_from_block(&block, &mut entries);
+            iblock += 1;
+        }
+        Ok(entries)
+    }
+
     /// Find a directory entry that matches a given name in a given block
     fn find_entry_in_block(block: &Block, name: &str) -> Result<DirEntry> {
         info!("Dir find entry {} in block {}", name, block.block_id);
@@ -124,6 +168,20 @@ impl Ext4 {
         Err(Ext4Error::new(ErrCode::ENOENT))
     }
 
+    /// Get all directory entries from a given block
+    fn get_all_entries_from_block(block: &Block, entries: &mut Vec<DirEntry>) {
+        info!("Dir get all entries from block {}", block.block_id);
+        let mut offset = 0;
+        while offset < BLOCK_SIZE {
+            let de: DirEntry = block.read_offset_as(offset);
+            offset += de.rec_len() as usize;
+            if !de.unused() {
+                debug!("Dir entry: {} {:?}", de.rec_len(), de.name());
+                entries.push(de);
+            }
+        }
+    }
+
     /// Insert a directory entry of a child inode into a new parent block.
     /// A new block must have enough space
     fn insert_entry_to_new_block(&self, dst_blk: &mut Block, child: &InodeRef, name: &str) {
@@ -200,12 +258,28 @@ impl Ext4 {
         false
     }
 
-    /// Create a new directory. `path` is the absolute path of the new directory.
-    pub fn mkdir(&mut self, path: &str) -> Result<()> {
-        let open_flags = OpenFlags::from_str("w").unwrap();
-        self.generic_open(EXT4_ROOT_INO, path, FileType::Directory, open_flags)
-            .map(|_| {
-                info!("ext4_dir_mk: {} ok", path);
-            })
+    /// 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(&self.super_block) {
+                // 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")
+                    })?,
+                    Some(FileType::RegularFile),
+                )?;
+            }
+        }
+        Ok(())
     }
 }

+ 119 - 90
src/ext4/file.rs

@@ -6,85 +6,17 @@ use crate::return_errno_with_message;
 use core::cmp::min;
 
 impl Ext4 {
-    fn split_path(path: &str) -> Vec<String> {
-        let _ = path.trim_start_matches("/");
-        path.split("/").map(|s| s.to_string()).collect()
-    }
-
-    /// Open an object of any type in the filesystem. Return the inode
-    /// id of the object if found.
-    ///
-    /// ## Params
-    /// * `root` - The inode id of the starting directory for the search.
-    /// * `path` - The path of the object to open.
-    /// * `ftype` - The expect type of object to open.
-    /// * `flags` - The open flags. If the flags contains `O_CREAT`, the file
-    ///             will be created if it does not exist.
-    pub(super) fn generic_open(
-        &mut self,
-        root: InodeId,
-        path: &str,
-        ftype: FileType,
-        flags: OpenFlags,
-    ) -> Result<InodeId> {
-        info!("generic open: {}", path);
-        // Search from the given parent inode
-        let mut parent_id = root;
-        let search_path = Self::split_path(path);
-
-        for (i, path) in search_path.iter().enumerate() {
-            let mut parent = self.read_inode(parent_id);
-            let res = self.dir_find_entry(&parent, path);
-            match res {
-                Ok(entry) => {
-                    parent_id = entry.inode();
-                }
-                Err(e) => {
-                    if e.code() != ErrCode::ENOENT {
-                        // dir search failed with error other than ENOENT
-                        return_errno_with_message!(ErrCode::ENOTSUP, "dir search failed");
-                    }
-                    if !flags.contains(OpenFlags::O_CREAT) {
-                        return_errno_with_message!(ErrCode::ENOENT, "file not found");
-                    }
-                    // Create file/directory
-                    let mut child = if i == search_path.len() - 1 {
-                        self.create_inode(ftype)
-                    } else {
-                        self.create_inode(FileType::Directory)
-                    }?;
-                    // Link the new inode
-                    self.link(&mut parent, &mut child, path)
-                        .map_err(|_| Ext4Error::with_message(ErrCode::ELINKFAIL, "link fail"))?;
-                    // Write back parent and child
-                    self.write_inode_with_csum(&mut parent);
-                    self.write_inode_with_csum(&mut child);
-                    // Update parent
-                    parent_id = child.id;
-                }
-            }
-        }
-        // `parent` is the target inode
-        Ok(parent_id)
-    }
-
-    pub fn open(&mut self, path: &str, flags: &str, file_expect: bool) -> Result<File> {
+    /// 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();
-        // file type
-        let file_type = if file_expect {
-            FileType::RegularFile
-        } else {
-            FileType::Directory
-        };
         // TODO:journal
         if open_flags.contains(OpenFlags::O_CREAT) {
             self.trans_start();
         }
         // open file
-        let res = self.generic_open(EXT4_ROOT_INO, path, file_type, open_flags);
-        res.map(|inode_id| {
-            let inode = self.read_inode(inode_id);
+        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,
@@ -94,7 +26,9 @@ impl Ext4 {
         })
     }
 
-    pub fn read(&self, file: &mut File, read_buf: &mut [u8], read_size: usize) -> Result<usize> {
+    /// 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);
@@ -146,15 +80,16 @@ impl Ext4 {
         Ok(cursor)
     }
 
-    pub fn write(&mut self, file: &mut File, data: &[u8]) -> Result<()> {
-        let size = data.len();
+    /// 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 + size) / 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
@@ -166,8 +101,8 @@ impl Ext4 {
         // Write data
         let mut cursor = 0;
         let mut iblock = start_iblock;
-        while cursor < size {
-            let write_len = min(BLOCK_SIZE, size - cursor);
+        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]);
@@ -180,29 +115,123 @@ impl Ext4 {
         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(super) 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_errno_with_message!(ErrCode::ENOTSUP, "dir search failed");
+                    }
+                    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_errno_with_message!(ErrCode::ENOENT, "file not found");
+                    }
+                    // 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"))?;
+                    // 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 inode_mode2file_type(cur.inode.mode()) != expect_type {
+                return_errno_with_message!(ErrCode::EISDIR, "inode type mismatch");
+            }
+        }
+        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 parent_inode_id = self.generic_open(
-            EXT4_ROOT_INO,
+        let mut parent_inode = self.generic_open(
+            root,
             &parent_path,
-            FileType::Directory,
             OpenFlags::O_RDONLY,
+            Some(FileType::Directory),
         )?;
         // Get the file inode, check the existence and type
-        let child_inode_id = self.generic_open(
-            parent_inode_id,
-            file_name,
-            FileType::RegularFile,
-            OpenFlags::O_RDONLY,
-        )?;
+        let mut child_inode =
+            self.generic_open(parent_inode.id, file_name, OpenFlags::O_RDONLY, expect_type)?;
+
         // Remove the file from the parent directory
-        let mut parent_inode = self.read_inode(parent_inode_id);
         self.dir_remove_entry(&mut parent_inode, &file_name)?;
-        // Free the inode of the file
-        self.free_inode(child_inode_id)
+        // 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()
     }
 }

+ 0 - 2
src/ext4/link.rs

@@ -17,9 +17,7 @@ impl Ext4 {
             // add '.' and '..' entries
             let child_self = child.clone();
             self.dir_add_entry(child, &child_self, ".")?;
-            child.inode.links_count += 1;
             self.dir_add_entry(child, parent, "..")?;
-            parent.inode.links_count += 1;
         }
         Ok(())
     }