From 039d984ebe1c1fb2ffdce71727ea2339b15d715e Mon Sep 17 00:00:00 2001 From: LittleSand <1840309785@qq.com> Date: Tue, 23 Jun 2026 23:13:54 +0800 Subject: [PATCH 01/19] fix(loongarch): use uncached window for mmio --- os/src/arch/mod.rs | 17 +++++++++++++++++ os/src/device/virtio_hal.rs | 2 +- os/src/mm/memory_space/space/kernel_space.rs | 13 ++++++++++--- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/os/src/arch/mod.rs b/os/src/arch/mod.rs index 6102ac2d..61978208 100644 --- a/os/src/arch/mod.rs +++ b/os/src/arch/mod.rs @@ -247,6 +247,23 @@ pub fn pa_to_va(pa: address::PA) -> address::VA { PlatformImpl::pa_to_va(pa) } +/// MMIO 物理地址 → 虚拟地址。 +/// +/// 普通 RAM 直映和 MMIO 直映在部分架构上使用不同窗口。LoongArch 使用 +/// DMW0 访问 uncached MMIO,避免把设备寄存器放进 DMW1 cached RAM 窗口。 +#[inline] +pub fn mmio_pa_to_va(pa: address::PA) -> address::VA { + #[cfg(target_arch = "loongarch64")] + { + address::VA::from_usize(pa.as_usize() | constant::DMW0_BASE) + } + + #[cfg(not(target_arch = "loongarch64"))] + { + pa_to_va(pa) + } +} + /// 虚拟地址 → 物理地址(直接映射) /// /// # Safety diff --git a/os/src/device/virtio_hal.rs b/os/src/device/virtio_hal.rs index e88f6ef1..0e182b69 100644 --- a/os/src/device/virtio_hal.rs +++ b/os/src/device/virtio_hal.rs @@ -78,7 +78,7 @@ unsafe impl Hal for VirtIOHal { unsafe fn mmio_phys_to_virt(paddr: PhysAddr, _size: usize) -> NonNull { // 提取物理地址值并使用架构特定的转换函数 let phys_addr = PA::from_usize(paddr as usize); - let virt = crate::arch::pa_to_va(phys_addr); + let virt = crate::arch::mmio_pa_to_va(phys_addr); // 验证虚拟地址的合法性 diff --git a/os/src/mm/memory_space/space/kernel_space.rs b/os/src/mm/memory_space/space/kernel_space.rs index 44eb175d..48bbd9f4 100644 --- a/os/src/mm/memory_space/space/kernel_space.rs +++ b/os/src/mm/memory_space/space/kernel_space.rs @@ -218,7 +218,7 @@ impl MemorySpace { let vpn_start = Vpn::from_addr_floor(addr); let vpn_end = Vpn::from_addr_ceil(VA::from_usize(addr.as_usize() + size)); - let mut area = MappingArea::new( + let area = MappingArea::new( VpnRange::new(vpn_start, vpn_end), AreaType::KernelMmio, MapType::Direct, @@ -226,7 +226,14 @@ impl MemorySpace { None, // MMIO 映射无文件 ); - area.map(&mut self.page_table)?; + #[cfg(not(target_arch = "loongarch64"))] + { + let mut area = area; + area.map(&mut self.page_table)?; + self.areas.push(area); + return Ok(()); + } + self.areas.push(area); Ok(()) } @@ -234,7 +241,7 @@ impl MemorySpace { /// 进程手动映射MMIO区域 pub fn map_mmio(&mut self, paddr: PA, size: usize) -> Result { // 将物理地址转换为虚拟地址 - let vaddr = crate::arch::pa_to_va(paddr); + let vaddr = crate::arch::mmio_pa_to_va(paddr); let vaddr_usize = vaddr.as_usize(); // 计算VPN范围 From 6bbfaca075743004661ce2d35f7050d1f3a297a2 Mon Sep 17 00:00:00 2001 From: LittleSand <1840309785@qq.com> Date: Tue, 23 Jun 2026 23:18:28 +0800 Subject: [PATCH 02/19] fix(loongarch): clone mmio metadata without remapping --- os/src/mm/memory_space/space/address_space.rs | 22 ++++++++++++++----- os/src/mm/memory_space/space/elf_loader.rs | 4 +--- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/os/src/mm/memory_space/space/address_space.rs b/os/src/mm/memory_space/space/address_space.rs index 7a85ff88..9e7337fb 100644 --- a/os/src/mm/memory_space/space/address_space.rs +++ b/os/src/mm/memory_space/space/address_space.rs @@ -71,9 +71,7 @@ impl MemorySpace { if !is_kernel { continue; } - let mut new_area = area.clone_metadata(); - new_area.map(&mut space.page_table)?; - space.areas.push(new_area); + space.clone_direct_area(area)?; } // Userspace rt_sigreturn trampoline (Linux ABI). @@ -89,6 +87,20 @@ impl MemorySpace { self.heap_start = Some(heap_start); } + pub(super) fn clone_direct_area(&mut self, area: &MappingArea) -> Result<(), PagingError> { + let mut new_area = area.clone_metadata(); + + #[cfg(target_arch = "loongarch64")] + if new_area.area_type() == AreaType::KernelMmio { + self.areas.push(new_area); + return Ok(()); + } + + new_area.map(&mut self.page_table)?; + self.areas.push(new_area); + Ok(()) + } + pub(super) fn map_user_sigreturn_trampoline(&mut self) -> Result<(), PagingError> { let start = USER_SIGRETURN_TRAMPOLINE; let end = start @@ -324,9 +336,7 @@ impl MemorySpace { match area.map_type() { MapType::Direct => { // 直接映射:克隆元数据并重新映射到新的页表 - let mut new_area = area.clone_metadata(); - new_area.map(&mut new_space.page_table)?; - new_space.areas.push(new_area); + new_space.clone_direct_area(area)?; } MapType::Framed => { // 帧映射:深层复制数据 diff --git a/os/src/mm/memory_space/space/elf_loader.rs b/os/src/mm/memory_space/space/elf_loader.rs index 8531b026..4fa0bdf6 100644 --- a/os/src/mm/memory_space/space/elf_loader.rs +++ b/os/src/mm/memory_space/space/elf_loader.rs @@ -73,9 +73,7 @@ impl MemorySpace { ); if is_kernel { // 对于内核区域,只需要克隆元数据并重新映射(不复制数据) - let mut new_area = area.clone_metadata(); - new_area.map(&mut space.page_table)?; - space.areas.push(new_area); + space.clone_direct_area(area)?; } } From 2ee1c632fce4911e769f82afbe4464231c297afb Mon Sep 17 00:00:00 2001 From: LittleSand <1840309785@qq.com> Date: Wed, 24 Jun 2026 00:15:48 +0800 Subject: [PATCH 03/19] fix(loongarch): walk all levels in tlb refill --- os/src/arch/loongarch/trap/trap_entry.S | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/os/src/arch/loongarch/trap/trap_entry.S b/os/src/arch/loongarch/trap/trap_entry.S index 93736e2d..96d856fc 100644 --- a/os/src/arch/loongarch/trap/trap_entry.S +++ b/os/src/arch/loongarch/trap/trap_entry.S @@ -133,9 +133,11 @@ __restore: tlb_refill_entry: csrwr $t0, 0x8b # TLBRSAVE <- t0 csrrd $t0, 0x1b # CSR.PGD (auto-selects PGDL/PGDH) + # PWCH enables Dir3 for the top-level directory, so the refill + # walk must descend Dir3 -> Dir2 -> Dir1 before loading PTEs. + lddir $t0, $t0, 4 lddir $t0, $t0, 3 lddir $t0, $t0, 2 - lddir $t0, $t0, 1 ldpte $t0, 0 ldpte $t0, 1 tlbfill From bacb2f385d19bbc4801692562573e6a0115de892 Mon Sep 17 00:00:00 2001 From: LittleSand <1840309785@qq.com> Date: Wed, 24 Jun 2026 00:54:29 +0800 Subject: [PATCH 04/19] fix(loongarch): keep kernel root in high half pgd --- os/src/arch/loongarch/mm/mod.rs | 14 ++++++++++++++ os/src/arch/loongarch/mm/page_table.rs | 9 +++++---- os/src/mm/mod.rs | 3 +++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/os/src/arch/loongarch/mm/mod.rs b/os/src/arch/loongarch/mm/mod.rs index 8969cf1e..815a21e0 100644 --- a/os/src/arch/loongarch/mm/mod.rs +++ b/os/src/arch/loongarch/mm/mod.rs @@ -26,6 +26,8 @@ mod page_table; mod page_table_entry; use crate::arch::address::{PA, VA}; +use crate::mm::address::{PageNum, Ppn, UsizeConvert}; +use core::sync::atomic::{AtomicUsize, Ordering}; pub use page_table::{PageTableInner, TlbBatchContext}; pub use page_table_entry::PageTableEntry; @@ -39,6 +41,18 @@ pub const VADDR_START: usize = 0x9000_0000_0000_0000; /// 用于从虚拟地址提取物理地址,保留低 48 位。 pub const PADDR_MASK: usize = 0x0000_FFFF_FFFF_FFFF; +static KERNEL_ROOT_PPN: AtomicUsize = AtomicUsize::new(0); + +/// Register the final kernel page-table root used for the high-half address space. +pub fn set_kernel_root_ppn(ppn: Ppn) { + KERNEL_ROOT_PPN.store(ppn.as_usize(), Ordering::Release); +} + +pub(crate) fn kernel_root_paddr() -> Option { + let ppn = KERNEL_ROOT_PPN.load(Ordering::Acquire); + (ppn != 0).then(|| Ppn::from_usize(ppn).start_addr().as_usize()) +} + /// 虚拟地址转物理地址 /// /// # 参数 diff --git a/os/src/arch/loongarch/mm/page_table.rs b/os/src/arch/loongarch/mm/page_table.rs index ba0c8fe6..951c1281 100644 --- a/os/src/arch/loongarch/mm/page_table.rs +++ b/os/src/arch/loongarch/mm/page_table.rs @@ -75,9 +75,10 @@ impl PageTableInnerTrait for PageTableInner { /// 激活页表 /// - /// 将页表根 PPN 写入 PGDL(低半地址空间)或 PGDH(高半地址空间) + /// 将当前地址空间根写入 PGDL,并将最终内核根写入 PGDH。 fn activate(ppn: Ppn) { - let pgd_paddr = ppn.start_addr().as_usize(); + let pgdl_paddr = ppn.start_addr().as_usize(); + let pgdh_paddr = super::kernel_root_paddr().unwrap_or(pgdl_paddr); unsafe { // 设置 STLBPS (CSR 0x1E) - STLB 页大小为 4KB (PS=12) core::arch::asm!( @@ -109,13 +110,13 @@ impl PageTableInnerTrait for PageTableInner { // 设置 PGDL (CSR 0x19) - 低半地址空间页全局目录基址 core::arch::asm!( "csrwr {0}, 0x19", - in(reg) pgd_paddr, + in(reg) pgdl_paddr, options(nostack, preserves_flags) ); // 设置 PGDH (CSR 0x1A) - 高半地址空间页全局目录基址 core::arch::asm!( "csrwr {0}, 0x1A", - in(reg) pgd_paddr, + in(reg) pgdh_paddr, options(nostack, preserves_flags) ); // 启用分页并关闭直接地址翻译 diff --git a/os/src/mm/mod.rs b/os/src/mm/mod.rs index 6a7a45e7..b170f455 100644 --- a/os/src/mm/mod.rs +++ b/os/src/mm/mod.rs @@ -110,6 +110,9 @@ pub fn init() -> alloc::sync::Arc Date: Wed, 24 Jun 2026 00:59:36 +0800 Subject: [PATCH 05/19] fix(loongarch): let ertn restore user privilege --- os/src/arch/loongarch/trap/trap_entry.S | 2 -- 1 file changed, 2 deletions(-) diff --git a/os/src/arch/loongarch/trap/trap_entry.S b/os/src/arch/loongarch/trap/trap_entry.S index 96d856fc..2293ec0d 100644 --- a/os/src/arch/loongarch/trap/trap_entry.S +++ b/os/src/arch/loongarch/trap/trap_entry.S @@ -86,8 +86,6 @@ __restore: csrwr $t0, 0x6 ld.d $t0, $r21, 280 # PRMD csrwr $t0, 0x1 - ld.d $t0, $r21, 272 # CRMD - csrwr $t0, 0x0 # 恢复通用寄存器(保持 r21 基址到最后) ld.d $ra, $r21, 8 From 3dbba9d9237fc67e1b1b8be0756893274d8ba7ff Mon Sep 17 00:00:00 2001 From: LittleSand <1840309785@qq.com> Date: Wed, 24 Jun 2026 01:13:50 +0800 Subject: [PATCH 06/19] fix(loongarch): skip disabled dir4 in tlb refill --- os/src/arch/loongarch/trap/trap_entry.S | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/os/src/arch/loongarch/trap/trap_entry.S b/os/src/arch/loongarch/trap/trap_entry.S index 2293ec0d..0a7bb97f 100644 --- a/os/src/arch/loongarch/trap/trap_entry.S +++ b/os/src/arch/loongarch/trap/trap_entry.S @@ -131,11 +131,11 @@ __restore: tlb_refill_entry: csrwr $t0, 0x8b # TLBRSAVE <- t0 csrrd $t0, 0x1b # CSR.PGD (auto-selects PGDL/PGDH) - # PWCH enables Dir3 for the top-level directory, so the refill - # walk must descend Dir3 -> Dir2 -> Dir1 before loading PTEs. - lddir $t0, $t0, 4 + # PWCH enables Dir3 as the top-level directory; Dir4 is disabled. + # Walk Dir3 -> Dir2 -> Dir1 before loading the even/odd PTE pair. lddir $t0, $t0, 3 lddir $t0, $t0, 2 + lddir $t0, $t0, 1 ldpte $t0, 0 ldpte $t0, 1 tlbfill From 4d5292a40c61af6095bc8d00d1e9a0ed8b0ca49d Mon Sep 17 00:00:00 2001 From: LittleSand <1840309785@qq.com> Date: Wed, 24 Jun 2026 01:51:18 +0800 Subject: [PATCH 07/19] test(loongarch): run musl tests from mounted image --- data/loongarch_musl/etc/init.d/rcS | 52 ++---------------------------- 1 file changed, 3 insertions(+), 49 deletions(-) diff --git a/data/loongarch_musl/etc/init.d/rcS b/data/loongarch_musl/etc/init.d/rcS index 5a93f5f9..0711fe71 100755 --- a/data/loongarch_musl/etc/init.d/rcS +++ b/data/loongarch_musl/etc/init.d/rcS @@ -95,56 +95,15 @@ mount_official_test_image_if_present() { return 1 } -stage_musl_group_to_tmpfs() { - src="/tests/musl" - dst="/tmp/musl" - script="$1" - - [ -d "$src" ] || return 1 - [ -n "$script" ] || return 1 - - [ -d "$dst" ] || mkdir "$dst" || return 1 - - echo "[Tests] staging $script into tmpfs at $dst" - - case "$script" in - basic_testcode.sh) - items="basic basic_testcode.sh busybox" - ;; - busybox_testcode.sh) - items="busybox busybox_cmd.txt busybox_testcode.sh" - ;; - lua_testcode.sh) - items="lua lua_testcode.sh test.sh date.lua file_io.lua max_min.lua random.lua remove.lua round_num.lua sin30.lua sort.lua strings.lua" - ;; - iperf_testcode.sh) - items="iperf3 iperf_testcode.sh" - ;; - *) - return 1 - ;; - esac - - for item in $items; do - [ -e "$src/$item" ] || continue - [ -e "$dst/$item" ] && continue - cp -R "$src/$item" "$dst/" || return 1 - done - - [ -f "$dst/$script" ] || return 1 - echo "[Tests] staged $script into tmpfs" - return 0 -} - run_musl_tests_if_present() { # 官方测试镜像挂载到 /tests 后,当前自动入口只跑 musl 分组; # glibc 分组保留在测试镜像中,后续需要时直接调整本脚本。 mount_official_test_image_if_present || true - [ -d /tests ] || return 1 + [ -d /tests/musl ] || return 1 ran=0 echo "[Tests] detected whitelisted musl test scripts; running in rcS" - export PATH="/bin:/sbin:/usr/bin:/usr/sbin:/tmp/musl:/tests/musl" + export PATH="/bin:/sbin:/usr/bin:/usr/sbin:/tests/musl" export HOME="/" for name in \ @@ -153,12 +112,7 @@ run_musl_tests_if_present() { lua_testcode.sh \ iperf_testcode.sh do - if stage_musl_group_to_tmpfs "$name"; then - f="/tmp/musl/$name" - else - echo "[Tests] tmpfs staging failed for $name; running directly from /tests" - f="/tests/musl/$name" - fi + f="/tests/musl/$name" [ -f "$f" ] || continue dir="${f%/*}" From 6af87ac93f4fdefbfbee68e000793d0093f838fa Mon Sep 17 00:00:00 2001 From: LittleSand <1840309785@qq.com> Date: Wed, 24 Jun 2026 02:03:58 +0800 Subject: [PATCH 08/19] fix(ext4): prevent directory checksum stack overflow --- os/Cargo.lock | 2 - os/Cargo.toml | 3 + vendor/ext4_rs/Cargo.toml | 39 + vendor/ext4_rs/LICENSE | 21 + vendor/ext4_rs/README.md | 172 +++ vendor/ext4_rs/src/ext4_defs/block.rs | 83 ++ vendor/ext4_rs/src/ext4_defs/block_group.rs | 254 ++++ vendor/ext4_rs/src/ext4_defs/consts.rs | 69 + vendor/ext4_rs/src/ext4_defs/direntry.rs | 256 ++++ vendor/ext4_rs/src/ext4_defs/ext4.rs | 16 + vendor/ext4_rs/src/ext4_defs/extents.rs | 796 ++++++++++++ vendor/ext4_rs/src/ext4_defs/file.rs | 163 +++ vendor/ext4_rs/src/ext4_defs/inode.rs | 642 ++++++++++ vendor/ext4_rs/src/ext4_defs/mod.rs | 23 + vendor/ext4_rs/src/ext4_defs/mount_point.rs | 22 + vendor/ext4_rs/src/ext4_defs/super_block.rs | 261 ++++ vendor/ext4_rs/src/ext4_impls/balloc.rs | 771 ++++++++++++ vendor/ext4_rs/src/ext4_impls/dir.rs | 438 +++++++ vendor/ext4_rs/src/ext4_impls/ext4.rs | 178 +++ vendor/ext4_rs/src/ext4_impls/extents.rs | 1255 +++++++++++++++++++ vendor/ext4_rs/src/ext4_impls/file.rs | 756 +++++++++++ vendor/ext4_rs/src/ext4_impls/ialloc.rs | 122 ++ vendor/ext4_rs/src/ext4_impls/inode.rs | 511 ++++++++ vendor/ext4_rs/src/ext4_impls/mod.rs | 15 + vendor/ext4_rs/src/fuse_interface/mod.rs | 683 ++++++++++ vendor/ext4_rs/src/lib.rs | 23 + vendor/ext4_rs/src/main.rs | 230 ++++ vendor/ext4_rs/src/prelude.rs | 29 + vendor/ext4_rs/src/simple_interface/mod.rs | 171 +++ vendor/ext4_rs/src/utils/bitmap.rs | 103 ++ vendor/ext4_rs/src/utils/crc.rs | 78 ++ vendor/ext4_rs/src/utils/errors.rs | 94 ++ vendor/ext4_rs/src/utils/mod.rs | 11 + vendor/ext4_rs/src/utils/path.rs | 63 + 34 files changed, 8351 insertions(+), 2 deletions(-) create mode 100644 vendor/ext4_rs/Cargo.toml create mode 100644 vendor/ext4_rs/LICENSE create mode 100644 vendor/ext4_rs/README.md create mode 100644 vendor/ext4_rs/src/ext4_defs/block.rs create mode 100644 vendor/ext4_rs/src/ext4_defs/block_group.rs create mode 100644 vendor/ext4_rs/src/ext4_defs/consts.rs create mode 100644 vendor/ext4_rs/src/ext4_defs/direntry.rs create mode 100644 vendor/ext4_rs/src/ext4_defs/ext4.rs create mode 100644 vendor/ext4_rs/src/ext4_defs/extents.rs create mode 100644 vendor/ext4_rs/src/ext4_defs/file.rs create mode 100644 vendor/ext4_rs/src/ext4_defs/inode.rs create mode 100644 vendor/ext4_rs/src/ext4_defs/mod.rs create mode 100644 vendor/ext4_rs/src/ext4_defs/mount_point.rs create mode 100644 vendor/ext4_rs/src/ext4_defs/super_block.rs create mode 100644 vendor/ext4_rs/src/ext4_impls/balloc.rs create mode 100644 vendor/ext4_rs/src/ext4_impls/dir.rs create mode 100644 vendor/ext4_rs/src/ext4_impls/ext4.rs create mode 100644 vendor/ext4_rs/src/ext4_impls/extents.rs create mode 100644 vendor/ext4_rs/src/ext4_impls/file.rs create mode 100644 vendor/ext4_rs/src/ext4_impls/ialloc.rs create mode 100644 vendor/ext4_rs/src/ext4_impls/inode.rs create mode 100644 vendor/ext4_rs/src/ext4_impls/mod.rs create mode 100644 vendor/ext4_rs/src/fuse_interface/mod.rs create mode 100644 vendor/ext4_rs/src/lib.rs create mode 100644 vendor/ext4_rs/src/main.rs create mode 100644 vendor/ext4_rs/src/prelude.rs create mode 100644 vendor/ext4_rs/src/simple_interface/mod.rs create mode 100644 vendor/ext4_rs/src/utils/bitmap.rs create mode 100644 vendor/ext4_rs/src/utils/crc.rs create mode 100644 vendor/ext4_rs/src/utils/errors.rs create mode 100644 vendor/ext4_rs/src/utils/mod.rs create mode 100644 vendor/ext4_rs/src/utils/path.rs diff --git a/os/Cargo.lock b/os/Cargo.lock index 793ba040..3864f431 100644 --- a/os/Cargo.lock +++ b/os/Cargo.lock @@ -132,8 +132,6 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "ext4_rs" version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a1a97344bde15b0ace15e265dab27228d4bdc37a0bfa8548c5645d7cfa6a144" dependencies = [ "bitflags 2.10.0", "log", diff --git a/os/Cargo.toml b/os/Cargo.toml index 6018c6ec..91ac8b3c 100644 --- a/os/Cargo.toml +++ b/os/Cargo.toml @@ -65,6 +65,9 @@ todo = "warn" needless_borrow = "deny" redundant_clone = "deny" +[patch.crates-io] +ext4_rs = { path = "../vendor/ext4_rs" } + [profile.dev] opt-level = 1 # 启用基本优化以减小代码体积 panic = "abort" diff --git a/vendor/ext4_rs/Cargo.toml b/vendor/ext4_rs/Cargo.toml new file mode 100644 index 00000000..467d2163 --- /dev/null +++ b/vendor/ext4_rs/Cargo.toml @@ -0,0 +1,39 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +name = "ext4_rs" +version = "1.3.2" +authors = ["yuoo655 "] +build = false +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "Cross-platform rust ext4." +readme = "README.md" +license = "MIT" +repository = "https://github.com/yuoo655/ext4_rs" + +[lib] +name = "ext4_rs" +path = "src/lib.rs" + +[[bin]] +name = "ext4_rs" +path = "src/main.rs" + +[dependencies.bitflags] +version = "2.2.1" + +[dependencies.log] +version = "0.4" diff --git a/vendor/ext4_rs/LICENSE b/vendor/ext4_rs/LICENSE new file mode 100644 index 00000000..88c972de --- /dev/null +++ b/vendor/ext4_rs/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 yuoo655 (lenuulan@gmail.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/ext4_rs/README.md b/vendor/ext4_rs/README.md new file mode 100644 index 00000000..e1e67cf6 --- /dev/null +++ b/vendor/ext4_rs/README.md @@ -0,0 +1,172 @@ +# An os independent rust ext4 file system + +[![Crates.io Version](https://img.shields.io/crates/v/ext4_rs)](https://crates.io/crates/ext4_rs) +[![Crates.io License](https://img.shields.io/crates/l/ext4_rs)](LICENSE) +[![docs.rs](https://img.shields.io/docsrs/ext4_rs)](https://docs.rs/ext4_rs) + +## env +wsl2 ubuntu22.04 + +rust version nightly-2024-06-01 + +rustc 1.80.0-nightly (ada5e2c7b 2024-05-31) + +mkfs.ext4 1.46.5 (30-Dec-2021) + +For small images, the newer mkfs.ext4 uses a 512-byte block size. Use **mkfs.ext4 -b 4096** to set a 4096-byte block size. + +## run example +```sh +git clone https://github.com/yuoo655/ext4_rs.git +sh run.sh +``` +## fuse example +``` +git clone https://github.com/yuoo655/ext4libtest.git +cd ext4libtest +sh gen_img.sh +# cargo run /path/to/mountpoint +cargo run ./foo/ +``` +# features + +| 操作 |支持情况| +|--------------|------| +| mount | ✅ | +| open | ✅ | +| close | ✅ | +| lsdir | ✅ | +| mkdir | ✅ | +| read_file | ✅ | +| read_link | ✅ | +| create_file | ✅ | +| write_file | ✅ | +| link | ✅ | +| unlink | ✅ | +| file_truncate| ✅ | +| file_remove | ✅ | +| umount | ✅ | +| dir_remove | ✅ | + + + +# how to use + +## impl BlockDevice Trait + +```rust +#[derive(Debug)] +pub struct Disk {} + +impl BlockDevice for Disk { + fn read_offset(&self, offset: usize) -> Vec { + use std::fs::OpenOptions; + use std::io::{Read, Seek}; + let mut file = OpenOptions::new() + .read(true) + .write(true) + .open("ex4.img") + .unwrap(); + let mut buf = vec![0u8; BLOCK_SIZE as usize]; + let _r = file.seek(std::io::SeekFrom::Start(offset as u64)); + let _r = file.read_exact(&mut buf); + + buf + } + + fn write_offset(&self, offset: usize, data: &[u8]) { + use std::fs::OpenOptions; + use std::io::{Seek, Write}; + let mut file = OpenOptions::new() + .read(true) + .write(true) + .open("ex4.img") + .unwrap(); + + let _r = file.seek(std::io::SeekFrom::Start(offset as u64)); + let _r = file.write_all(&data); + } +} + +``` + +## open ext4 + +```rust +let disk = Arc::new(Disk {}); +let ext4 = Ext4::open(disk); +``` + +### read regular file +```rust +let path = "test_files/0.txt"; +let mut read_buf = vec![0u8; READ_SIZE as usize]; +let child_inode = ext4.generic_open(path, &mut 2, false, 0, &mut 0).unwrap(); +// 1G +let mut data = vec![0u8; 0x100000 * 1024 as usize]; +let read_data = ext4.read_at(child_inode, 0 as usize, &mut data); +log::info!("read data {:?}", &data[..10]); +``` + +### read link +```rust +let path = "test_files/linktest"; +let mut read_buf = vec![0u8; READ_SIZE as usize]; +// 2 is root inode +let child_inode = ext4.generic_open(path, &mut 2, false, 0, &mut 0).unwrap(); +let mut data = vec![0u8; 0x100000 * 1024 as usize]; +let read_data = ext4.read_at(child_inode, 0 as usize, &mut data); +log::info!("read data {:?}", &data[..10]); +``` + +### mkdir +```rust +for i in 0..10 { + let path = format!("dirtest{}", i); + let path = path.as_str(); + let r = ext4.dir_mk(&path); + assert!(r.is_ok(), "dir make error {:?}", r.err()); +} +let path = "dir1/dir2/dir3/dir4/dir5/dir6"; +let r = ext4.dir_mk(&path); +assert!(r.is_ok(), "dir make error {:?}", r.err()); +``` + +### file write test +```rust +// file create/write +let inode_mode = InodeFileType::S_IFREG.bits(); +let inode_ref = ext4.create(ROOT_INODE, "512M.txt", inode_mode).unwrap(); + +const WRITE_SIZE: usize = (0x100000 * 512); +let write_buf = vec![0x41 as u8; WRITE_SIZE]; +let r = ext4.write_at(inode_ref.inode_num, 0, &write_buf); +``` + + +### ls +```rust +let entries = ext4.dir_get_entries(ROOT_INODE); +log::info!("dir ls root"); +for entry in entries { + log::info!("{:?}", entry.get_name()); +} +``` + +### file remove +```rust +let path = "test_files/file_to_remove"; +let r = ext4.file_remove(&path); +``` + +### dir remove +```rust +let path = "dir_to_remove"; +let r = ext4.dir_remove(ROOT_INODE, &path); +``` + + +# known bugs +1. ~~ext4_valid_extent check fails in linux due to block allocation using system reserved blocks.~~ (Fixed: Block allocator now checks for system reserved blocks before allocation) + +2. ~~extent block checksum not set~~ (Fixed: Added support for computing and setting extent block checksums) diff --git a/vendor/ext4_rs/src/ext4_defs/block.rs b/vendor/ext4_rs/src/ext4_defs/block.rs new file mode 100644 index 00000000..668c741d --- /dev/null +++ b/vendor/ext4_rs/src/ext4_defs/block.rs @@ -0,0 +1,83 @@ +use crate::prelude::*; + +pub trait BlockDevice: Send + Sync + Any { + fn read_offset(&self, offset: usize) -> Vec; + fn write_offset(&self, offset: usize, data: &[u8]); +} + +pub struct Block { + pub disk_offset: usize, + pub data: Vec, +} + +impl Block { + /// Load the block from the disk. + pub fn load(block_device: Arc, offset: usize) -> Self { + let data = block_device.read_offset(offset); + Block { + disk_offset: offset, + data, + } + } + + /// Load the block from inode block + pub fn load_inode_root_block(data: &[u32; 15]) -> Self { + let data_bytes: &[u8; 60] = unsafe { + core::mem::transmute(data) + }; + Block { + disk_offset: 0, + data: data_bytes.to_vec(), + } + } + + /// Read the block as a specific type. + pub fn read_as(&self) -> T { + unsafe { + let ptr = self.data.as_ptr() as *const T; + ptr.read_unaligned() + } + } + + /// Read the block as a specific type at a specific offset. + pub fn read_offset_as(&self, offset: usize) -> T { + unsafe { + let ptr = self.data.as_ptr().add(offset) as *const T; + ptr.read_unaligned() + } + } + + /// Read the block as a specific type mutably. + pub fn read_as_mut(&mut self) -> &mut T { + unsafe { + let ptr = self.data.as_mut_ptr() as *mut T; + &mut *ptr + } + } + + /// Read the block as a specific type mutably at a specific offset. + pub fn read_offset_as_mut(&mut self, offset: usize) -> &mut T { + unsafe { + let ptr = self.data.as_mut_ptr().add(offset) as *mut T; + &mut *ptr + } + } + + /// Write data to the block starting at a specific offset. + pub fn write_offset(&mut self, offset: usize, data: &[u8], len: usize) { + let end = offset + len; + if end <= self.data.len() { + let slice_end = len.min(data.len()); + self.data[offset..end].copy_from_slice(&data[..slice_end]); + } else { + panic!("Write would overflow the block buffer"); + } + } +} + + +impl Block{ + pub fn sync_blk_to_disk(&self, block_device: Arc){ + block_device.write_offset(self.disk_offset, &self.data); + } +} \ No newline at end of file diff --git a/vendor/ext4_rs/src/ext4_defs/block_group.rs b/vendor/ext4_rs/src/ext4_defs/block_group.rs new file mode 100644 index 00000000..43fb3a7b --- /dev/null +++ b/vendor/ext4_rs/src/ext4_defs/block_group.rs @@ -0,0 +1,254 @@ +use crate::prelude::*; +use crate::utils::*; + +use super::*; + +/// Represents the structure of an Ext4 block group descriptor. +#[derive(Debug, Default, Clone, Copy)] +#[repr(C, packed)] +pub struct Ext4BlockGroup { + pub block_bitmap_lo: u32, // Block bitmap block + pub inode_bitmap_lo: u32, // Inode bitmap block + pub inode_table_first_block_lo: u32, // Inode table block + pub free_blocks_count_lo: u16, // Free blocks count + pub free_inodes_count_lo: u16, // Free inodes count + pub used_dirs_count_lo: u16, // Directories count + pub flags: u16, // EXT4_BG_flags (INODE_UNINIT, etc) + pub exclude_bitmap_lo: u32, // Snapshot exclusion bitmap + pub block_bitmap_csum_lo: u16, // crc32c(s_uuid+grp_num+bbitmap) LE + pub inode_bitmap_csum_lo: u16, // crc32c(s_uuid+grp_num+ibitmap) LE + pub itable_unused_lo: u16, // Unused inodes count + pub checksum: u16, // crc16(sb_uuid+group+desc) + + pub block_bitmap_hi: u32, // Block bitmap block MSB + pub inode_bitmap_hi: u32, // Inode bitmap block MSB + pub inode_table_first_block_hi: u32, // Inode table block MSB + pub free_blocks_count_hi: u16, // Free blocks count MSB + pub free_inodes_count_hi: u16, // Free inodes count MSB + pub used_dirs_count_hi: u16, // Directories count MSB + pub itable_unused_hi: u16, // Unused inodes count MSB + pub exclude_bitmap_hi: u32, // Snapshot exclusion bitmap MSB + pub block_bitmap_csum_hi: u16, // crc32c(s_uuid+grp_num+bbitmap) BE + pub inode_bitmap_csum_hi: u16, // crc32c(s_uuid+grp_num+ibitmap) BE + pub reserved: u32, // Padding +} + +impl Ext4BlockGroup { + /// Load the block group descriptor from the disk. + pub fn load_new( + block_device: Arc, + super_block: &Ext4Superblock, + block_group_idx: usize, + ) -> Self { + let dsc_cnt = BLOCK_SIZE / super_block.desc_size as usize; + let dsc_id = block_group_idx / dsc_cnt; + let first_data_block = super_block.first_data_block; + let block_id = first_data_block as usize + dsc_id + 1; + let offset = (block_group_idx % dsc_cnt) * super_block.desc_size as usize; + + let ext4block = Block::load(block_device, block_id * BLOCK_SIZE); + let bg: Ext4BlockGroup = ext4block.read_offset_as(offset); + + bg + } +} + +impl Ext4BlockGroup { + /// Get the block number of the block bitmap for this block group. + pub fn get_block_bitmap_block(&self, s: &Ext4Superblock) -> u64 { + let mut v = self.block_bitmap_lo as u64; + let desc_size = s.desc_size; + if desc_size > EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE { + v |= (self.block_bitmap_hi as u64) << 32; + } + v + } + + /// Get the block number of the inode bitmap for this block group. + pub fn get_inode_bitmap_block(&self, s: &Ext4Superblock) -> u64 { + let mut v = self.inode_bitmap_lo as u64; + let desc_size = s.desc_size; + if desc_size > EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE { + v |= (self.inode_bitmap_hi as u64) << 32; + } + v + } + + /// Get the count of unused inodes in this block group. + pub fn get_itable_unused(&mut self, s: &Ext4Superblock) -> u32 { + let mut v = self.itable_unused_lo as u32; + if s.desc_size() > EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE { + v |= ((self.itable_unused_hi as u64) << 32) as u32; + } + v + } + + /// Get the count of used directories in this block group. + pub fn get_used_dirs_count(&self, s: &Ext4Superblock) -> u32 { + let mut v = self.used_dirs_count_lo as u32; + if s.desc_size() > EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE { + v |= ((self.used_dirs_count_hi as u64) << 32) as u32; + } + v + } + + /// Set the count of used directories in this block group. + pub fn set_used_dirs_count(&mut self, s: &Ext4Superblock, cnt: u32) { + self.itable_unused_lo = (cnt & 0xffff) as u16; + if s.desc_size() > EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE { + self.itable_unused_hi = (cnt >> 16) as u16; + } + } + + /// Set the count of unused inodes in this block group. + pub fn set_itable_unused(&mut self, s: &Ext4Superblock, cnt: u32) { + self.itable_unused_lo = (cnt & 0xffff) as u16; + if s.desc_size() > EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE { + self.itable_unused_hi = (cnt >> 16) as u16; + } + } + + /// Set the count of free inodes in this block group. + pub fn set_free_inodes_count(&mut self, s: &Ext4Superblock, cnt: u32) { + self.free_inodes_count_lo = (cnt & 0xffff) as u16; + if s.desc_size() > EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE { + self.free_inodes_count_hi = (cnt >> 16) as u16; + } + } + + /// Get the count of free inodes in this block group. + pub fn get_free_inodes_count(&self) -> u32 { + ((self.free_inodes_count_hi as u64) << 32) as u32 | self.free_inodes_count_lo as u32 + } + + /// Get the block number of the inode table for this block group. + pub fn get_inode_table_blk_num(&self) -> u32 { + ((self.inode_table_first_block_hi as u64) << 32) as u32 | self.inode_table_first_block_lo + } +} + +/// sync block group to disk +impl Ext4BlockGroup { + /// Calculate and return the checksum of the block group descriptor. + #[allow(unused)] + pub fn get_block_group_checksum(&mut self, bgid: u32, super_block: &Ext4Superblock) -> u16 { + let desc_size = super_block.desc_size(); + + let mut orig_checksum = 0; + let mut checksum = 0; + + orig_checksum = self.checksum; + + // Preparation: temporarily set bg checksum to 0 + self.checksum = 0; + + // uuid checksum + checksum = ext4_crc32c( + EXT4_CRC32_INIT, + &super_block.uuid, + super_block.uuid.len() as u32, + ); + + // bgid checksum + checksum = ext4_crc32c(checksum, &bgid.to_le_bytes(), 4); + + // cast self to &[u8] + let self_bytes = + unsafe { core::slice::from_raw_parts(self as *const _ as *const u8, 0x40) }; + + // bg checksum + checksum = ext4_crc32c(checksum, self_bytes, desc_size as u32); + + self.checksum = orig_checksum; + + (checksum & 0xFFFF) as u16 + } + + /// Synchronize the block group data to disk. + pub fn sync_block_group_to_disk( + &self, + block_device: Arc, + bgid: usize, + super_block: &Ext4Superblock, + ) { + let dsc_cnt = BLOCK_SIZE / super_block.desc_size as usize; + // let dsc_per_block = dsc_cnt; + let dsc_id = bgid / dsc_cnt; + // let first_meta_bg = super_block.first_meta_bg; + let first_data_block = super_block.first_data_block; + let block_id = first_data_block as usize + dsc_id + 1; + let offset = (bgid % dsc_cnt) * super_block.desc_size as usize; + + let data = unsafe { + core::slice::from_raw_parts(self as *const _ as *const u8, size_of::()) + }; + block_device.write_offset(block_id * BLOCK_SIZE + offset, data); + } + + /// Set the checksum of the block group descriptor. + pub fn set_block_group_checksum(&mut self, bgid: u32, super_block: &Ext4Superblock) { + let csum = self.get_block_group_checksum(bgid, super_block); + self.checksum = csum; + } + + /// Synchronize the block group data to disk with checksum. + pub fn sync_to_disk_with_csum( + &mut self, + block_device: Arc, + bgid: usize, + super_block: &Ext4Superblock, + ) { + self.set_block_group_checksum(bgid as u32, super_block); + self.sync_block_group_to_disk(block_device, bgid, super_block) + } + + /// Set the block allocation bitmap checksum for this block group. + pub fn set_block_group_balloc_bitmap_csum(&mut self, s: &Ext4Superblock, bitmap: &[u8]) { + let desc_size = s.desc_size(); + + let csum = s.ext4_balloc_bitmap_csum(bitmap); + let lo_csum = (csum & 0xFFFF).to_le(); + let hi_csum = (csum >> 16).to_le(); + + if (s.features_read_only & 0x400) >> 10 == 0 { + return; + } + self.block_bitmap_csum_lo = lo_csum as u16; + if desc_size == EXT4_MAX_BLOCK_GROUP_DESCRIPTOR_SIZE { + self.block_bitmap_csum_hi = hi_csum as u16; + } + } + + /// Get the count of free blocks in this block group. + pub fn get_free_blocks_count(&self) -> u64 { + let mut v = self.free_blocks_count_lo as u64; + if self.free_blocks_count_hi != 0 { + v |= (self.free_blocks_count_hi as u64) << 32; + } + v + } + + /// Set the count of free blocks in this block group. + pub fn set_free_blocks_count(&mut self, cnt: u32) { + self.free_blocks_count_lo = (cnt & 0xffff) as u16; + self.free_blocks_count_hi = (cnt >> 16) as u16; + } + + + /// Set the inode allocation bitmap checksum for this block group. + pub fn set_block_group_ialloc_bitmap_csum(&mut self, s: &Ext4Superblock, bitmap: &[u8]) { + let desc_size = s.desc_size(); + + let csum = s.ext4_ialloc_bitmap_csum(bitmap); + let lo_csum = (csum & 0xFFFF).to_le(); + let hi_csum = (csum >> 16).to_le(); + + if (s.features_read_only & 0x400) >> 10 == 0 { + return; + } + self.inode_bitmap_csum_lo = lo_csum as u16; + if desc_size == EXT4_MAX_BLOCK_GROUP_DESCRIPTOR_SIZE { + self.inode_bitmap_csum_hi = hi_csum as u16; + } + } +} diff --git a/vendor/ext4_rs/src/ext4_defs/consts.rs b/vendor/ext4_rs/src/ext4_defs/consts.rs new file mode 100644 index 00000000..72d1b9af --- /dev/null +++ b/vendor/ext4_rs/src/ext4_defs/consts.rs @@ -0,0 +1,69 @@ +use bitflags::bitflags; + +pub const BLOCK_SIZE: usize = 0x1000; // 4KB +pub const SECTORS_PER_BLOCK: usize = BLOCK_SIZE / 512; + +pub type Ext4Lblk = u32; +pub type Ext4Fsblk = u64; + +pub const EOK: usize = 0; + +/// Inode +pub const ROOT_INODE: u32 = 2; // Root directory inode +pub const JOURNAL_INODE: u32 = 8; // Journal file inode +pub const UNDEL_DIR_INODE: u32 = 6; // Undelete directory inode +pub const LOST_AND_FOUND_INODE: u32 = 11; // lost+found directory inode +pub const EXT4_INODE_MODE_FILE: usize = 0x8000; +pub const EXT4_INODE_MODE_TYPE_MASK: u16 = 0xF000; +pub const EXT4_INODE_MODE_PERM_MASK: u16 = 0x0FFF; +pub const EXT4_INODE_BLOCK_SIZE: usize = 512; +pub const EXT4_GOOD_OLD_INODE_SIZE: u16 = 128; +pub const EXT4_INODE_FLAG_EXTENTS: usize = 0x00080000; /* Inode uses extents */ + +/// Extent +pub const EXT_INIT_MAX_LEN: u16 = 32768; +pub const EXT_UNWRITTEN_MAX_LEN: u16 = 65535; +pub const EXT_MAX_BLOCKS: Ext4Lblk = u32::MAX; +pub const EXT4_EXTENT_MAGIC: u16 = 0xF30A; +pub const EXT4_EXTENT_HEADER_SIZE: usize = 12; +pub const EXT4_EXTENT_SIZE: usize = 12; +pub const EXT4_EXTENT_INDEX_SIZE: usize = 12; +pub const MAX_EXTENT_INDEX_COUNT: usize = 340; + +/// BLock group descriptor flags. +pub const EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE: u16 = 32; +pub const EXT4_MAX_BLOCK_GROUP_DESCRIPTOR_SIZE: u16 = 64; + +/// SuperBlock +pub const SUPERBLOCK_OFFSET: usize = 1024; +pub const EXT4_SUPERBLOCK_OS_HURD: u32 = 1; + +/// File +pub const EXT4_MAX_FILE_SIZE: u64 = 16 * 1024 * 1024 * 1024; // 16TB + +/// libc file open flags +pub const O_ACCMODE: i32 = 0o0003; +pub const O_RDONLY: i32 = 0o00; +pub const O_WRONLY: i32 = 0o01; +pub const O_RDWR: i32 = 0o02; +pub const O_CREAT: i32 = 0o0100; +pub const O_EXCL: i32 = 0o0200; +pub const O_NOCTTY: i32 = 0o0400; +pub const O_TRUNC: i32 = 0o01000; +pub const O_APPEND: i32 = 0o02000; +pub const O_NONBLOCK: i32 = 0o04000; +pub const O_SYNC: i32 = 0o4010000; +pub const O_ASYNC: i32 = 0o020000; +pub const O_LARGEFILE: i32 = 0o0100000; +pub const O_DIRECTORY: i32 = 0o0200000; +pub const O_NOFOLLOW: i32 = 0o0400000; +pub const O_CLOEXEC: i32 = 0o2000000; +pub const O_DIRECT: i32 = 0o040000; +pub const O_NOATIME: i32 = 0o1000000; +pub const O_PATH: i32 = 0o10000000; +pub const O_DSYNC: i32 = 0o010000; +/// linux access syscall flags +pub const F_OK: i32 = 0; +pub const R_OK: i32 = 4; +pub const W_OK: i32 = 2; +pub const X_OK: i32 = 1; \ No newline at end of file diff --git a/vendor/ext4_rs/src/ext4_defs/direntry.rs b/vendor/ext4_rs/src/ext4_defs/direntry.rs new file mode 100644 index 00000000..50ba0859 --- /dev/null +++ b/vendor/ext4_rs/src/ext4_defs/direntry.rs @@ -0,0 +1,256 @@ +use crate::prelude::*; +use crate::utils::*; + +use super::*; + +bitflags! { + #[derive(PartialEq, Eq)] + pub struct DirEntryType: u8 { + const EXT4_DE_UNKNOWN = 0; + const EXT4_DE_REG_FILE = 1; + const EXT4_DE_DIR = 2; + const EXT4_DE_CHRDEV = 3; + const EXT4_DE_BLKDEV = 4; + const EXT4_DE_FIFO = 5; + const EXT4_DE_SOCK = 6; + const EXT4_DE_SYMLINK = 7; + } +} + +/// Directory entry for Ext4 +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct Ext4DirEntry { + pub inode: u32, // Inode number this entry points to + pub entry_len: u16, // Distance to the next directory entry + pub name_len: u8, // Lower 8 bits of name length + pub inner: Ext4DirEnInternal, // Union member + pub name: [u8; 255], // File name +} + +/// Internal directory entry structure. +#[repr(C)] +#[derive(Clone, Copy)] +pub union Ext4DirEnInternal { + pub name_length_high: u8, // Higher 8 bits of name length + pub inode_type: u8, // Type of the referenced inode (in rev >= 0.5) +} + +/// Fake directory entry structure. Used for directory entry iteration. +#[repr(C)] +pub struct Ext4FakeDirEntry { + inode: u32, + entry_length: u16, + name_length: u8, + inode_type: u8, +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct Ext4DirEntryTail { + pub reserved_zero1: u32, + pub rec_len: u16, + pub reserved_zero2: u8, + pub reserved_ft: u8, + pub checksum: u32, // crc32c(uuid+inum+dirblock) +} + +pub struct Ext4DirSearchResult{ + pub dentry: Ext4DirEntry, + pub pblock_id: usize, // disk block id + pub offset: usize, // offset in block + pub prev_offset: usize, //prev direntry offset +} + +impl Ext4DirSearchResult { + pub fn new(dentry: Ext4DirEntry) -> Self { + Self { + dentry, + pblock_id: 0, + offset: 0, + prev_offset: 0, + } + } +} + +impl Debug for Ext4DirEnInternal { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + unsafe { + write!( + f, + "Ext4DirEnInternal {{ name_length_high: {:?} }}", + self.name_length_high + ) + } + } +} + +impl Default for Ext4DirEnInternal { + fn default() -> Self { + Self { + name_length_high: 0, + } + } +} + +impl Default for Ext4DirEntry { + fn default() -> Self { + Self { + inode: 0, + entry_len: 0, + name_len: 0, + inner: Ext4DirEnInternal::default(), + name: [0; 255], + } + } +} + +impl TryFrom<&[T]> for Ext4DirEntry { + type Error = u64; + fn try_from(data: &[T]) -> core::result::Result { + // let data = data; + Ok(unsafe { core::ptr::read(data.as_ptr() as *const _) }) + } +} + +/// Directory entry implementation. +impl Ext4DirEntry { + + /// Check if the directory entry is unused. + pub fn unused(&self) -> bool { + self.inode == 0 + } + + /// Set the directory entry as unused. + pub fn set_unused(&mut self) { + self.inode = 0 + } + + /// Check name + pub fn compare_name(&self, name: &str) -> bool { + if self.name_len as usize == name.len(){ + return &self.name[..name.len()] == name.as_bytes() + } + false + } + + /// Entry length + pub fn entry_len(&self) -> u16 { + self.entry_len + } + + /// Dir type + pub fn get_de_type(&self) -> u8 { + unsafe { self.inner.inode_type } + } + + /// Get name to string + pub fn get_name(&self) -> String { + let name_len = self.name_len as usize; + let name = &self.name[..name_len]; + let name = core::str::from_utf8(name).unwrap(); + name.to_string() + } + + /// Get name len + pub fn get_name_len(&self) -> usize { + self.name_len as usize + } + + /// Calculate the actual length of a directory entry (excluding padding bytes) + pub fn actual_len(&self) -> usize { + size_of::() + self.name_len as usize + } + + /// Calculate the aligned length of a directory entry (including padding bytes) + pub fn align_len(&self) -> usize { + let mut len = self.actual_len(); + len = (len + 3) & !3; + len + } + + pub fn write_entry(&mut self, entry_len: u16, inode: u32, name: &str, de_type:DirEntryType) { + self.inode = inode; + self.entry_len = entry_len; + self.name_len = name.len() as u8; + self.inner.inode_type = de_type.bits(); + self.name[..name.len()].copy_from_slice(name.as_bytes()); + } + +} + +impl Ext4DirEntry { + + /// Get the checksum of the directory entry. + #[allow(unused)] + pub fn ext4_dir_get_csum(&self, s: &Ext4Superblock, blk_data: &[u8], ino_gen: u32) -> u32 { + let ino_index = self.inode; + + let mut csum = 0; + + let uuid = s.uuid; + + csum = ext4_crc32c(EXT4_CRC32_INIT, &uuid, uuid.len() as u32); + csum = ext4_crc32c(csum, &ino_index.to_le_bytes(), 4); + csum = ext4_crc32c(csum, &ino_gen.to_le_bytes(), 4); + let mut data = [0u8; 0xff4]; + let csum_data_len = data.len().min(blk_data.len()); + data[..csum_data_len].copy_from_slice(&blk_data[..csum_data_len]); + + csum = ext4_crc32c(csum, &data[..], 0xff4); + csum + } + + /// Write de to block + pub fn write_de_to_blk(&self, dst_blk: &mut Block, offset: usize) { + let count = core::mem::size_of::() / core::mem::size_of::(); + let data = unsafe { core::slice::from_raw_parts(self as *const _ as *const u8, count) }; + dst_blk.data.splice( + offset..offset + core::mem::size_of::(), + data.iter().cloned(), + ); + // assert_eq!(dst_blk.block_data[offset..offset + core::mem::size_of::()], data[..]); + } + + /// Copy the directory entry to a slice. + pub fn copy_to_slice(&self, array: &mut [u8], offset: usize) { + let de_ptr = self as *const Ext4DirEntry as *const u8; + let array_ptr = array as *mut [u8] as *mut u8; + let count = core::mem::size_of::() / core::mem::size_of::(); + unsafe { + core::ptr::copy_nonoverlapping(de_ptr, array_ptr.add(offset), count); + } + } +} + +impl Ext4DirEntryTail{ + pub fn new() -> Self { + Self { + reserved_zero1: 0, + rec_len: size_of::() as u16, + reserved_zero2: 0, + reserved_ft: 0xDE, + checksum: 0, + } + } + pub fn tail_set_csum( + &mut self, + s: &Ext4Superblock, + diren: &Ext4DirEntry, + blk_data: &[u8], + ino_gen: u32, + ) { + let csum = diren.ext4_dir_get_csum(s, blk_data, ino_gen); + self.checksum = csum; + } + + pub fn copy_to_slice(&self, array: &mut [u8]) { + unsafe { + let offset = BLOCK_SIZE - core::mem::size_of::(); + let de_ptr = self as *const Ext4DirEntryTail as *const u8; + let array_ptr = array as *mut [u8] as *mut u8; + let count = core::mem::size_of::(); + core::ptr::copy_nonoverlapping(de_ptr, array_ptr.add(offset), count); + } + } +} diff --git a/vendor/ext4_rs/src/ext4_defs/ext4.rs b/vendor/ext4_rs/src/ext4_defs/ext4.rs new file mode 100644 index 00000000..0aab871b --- /dev/null +++ b/vendor/ext4_rs/src/ext4_defs/ext4.rs @@ -0,0 +1,16 @@ +use crate::prelude::*; + +use super::*; + +#[derive(Debug, Clone)] +pub struct SystemZone { + pub group: u32, + pub start_blk: u64, + pub end_blk: u64, +} + +pub struct Ext4 { + pub block_device: Arc, + pub super_block: Ext4Superblock, + pub system_zone_cache: Option>, +} diff --git a/vendor/ext4_rs/src/ext4_defs/extents.rs b/vendor/ext4_rs/src/ext4_defs/extents.rs new file mode 100644 index 00000000..793f2dfc --- /dev/null +++ b/vendor/ext4_rs/src/ext4_defs/extents.rs @@ -0,0 +1,796 @@ +use crate::prelude::*; +use crate::return_errno_with_message; + +use super::*; + +#[derive(Debug, Default, Clone, Copy)] +#[repr(C)] +pub struct Ext4ExtentHeader { + /// Magic number, 0xF30A. + pub magic: u16, + + /// Number of valid entries following the header. + pub entries_count: u16, + + /// Maximum number of entries that could follow the header. + pub max_entries_count: u16, + + /// Depth of this extent node in the extent tree. Depth 0 indicates that this node points to data blocks. + pub depth: u16, + + /// Generation of the tree (used by Lustre, but not standard in ext4). + pub generation: u32, +} + +/// Structure representing an index node within an extent tree. +#[derive(Debug, Default, Clone, Copy)] +#[repr(C)] +pub struct Ext4ExtentIndex { + /// Block number from which this index node starts. + pub first_block: u32, + + /// Lower 32-bits of the block number to which this index points. + pub leaf_lo: u32, + + /// Upper 16-bits of the block number to which this index points. + pub leaf_hi: u16, + + /// Padding for alignment. + pub padding: u16, +} + +/// Structure representing an Ext4 extent. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +#[repr(C)] +pub struct Ext4Extent { + /// First file block number that this extent covers. + pub first_block: u32, + + /// Number of blocks covered by this extent. + pub block_count: u16, + + /// Upper 16-bits of the block number to which this extent points. + pub start_hi: u16, + + /// Lower 32-bits of the block number to which this extent points. + pub start_lo: u32, +} + +/// Extent tail structure +/// This is at the end of the extent block and contains a checksum +#[repr(C, packed)] +#[derive(Debug, Clone, Copy)] +pub struct Ext4ExtentTail { + /// crc32c(uuid+inum+generation+extent_block) + pub et_checksum: u32, +} + +/// Extent tree node. Includes the header, the data. +#[derive(Clone, Debug)] +pub struct ExtentNode { + pub header: Ext4ExtentHeader, + pub data: NodeData, + pub is_root: bool, +} + +/// Data of extent tree. +#[derive(Clone, Debug)] +pub enum NodeData { + Root([u32; 15]), + Internal(Vec), // size = BLOCK_SIZE +} + +/// Search path in the extent tree. +#[derive(Clone, Debug)] +pub struct SearchPath { + pub depth: u16, // current depth + pub maxdepth: u16, // max depth + pub path: Vec, // search result of each level +} + +/// Extent tree node search result +#[derive(Clone, Debug)] +pub struct ExtentPathNode { + pub header: Ext4ExtentHeader, // save header for convenience + pub index: Option, // for convenience(you can get index through pos of extent node) + pub extent: Option, // same reason as above + pub position: usize, // position of search result in the node + pub pblock: u64, // physical block of search result + pub pblock_of_node: usize, // physical block of this node +} + +/// load methods for Ext4ExtentHeader +impl Ext4ExtentHeader { + /// Load the extent header from u32 array. + pub fn load_from_u32(data: &[u32]) -> Self { + unsafe { core::ptr::read(data.as_ptr() as *const _) } + } + + /// Load the extent header from u32 array mutably. + pub fn load_from_u32_mut(data: &mut [u32]) -> &mut Self { + let ptr = data.as_mut_ptr() as *mut Self; + unsafe { &mut *ptr } + } + + /// Load the extent header from u8 array. + pub fn load_from_u8(data: &[u8]) -> Self { + unsafe { core::ptr::read(data.as_ptr() as *const _) } + } + + /// Load the extent header from u8 array mutably. + pub fn load_from_u8_mut(data: &mut [u8]) -> &mut Self { + let ptr = data.as_mut_ptr() as *mut Self; + unsafe { &mut *ptr } + } + + /// Is the node a leaf node? + pub fn is_leaf(&self) -> bool { + self.depth == 0 + } + + pub fn from_bytes(bytes: &[u8]) -> Self { + let mut header = Ext4ExtentHeader { + magic: 0, + entries_count: 0, + max_entries_count: 0, + depth: 0, + generation: 0, + }; + header.magic = u16::from_le_bytes(bytes[0..2].try_into().unwrap()); + header.entries_count = u16::from_le_bytes(bytes[2..4].try_into().unwrap()); + header.max_entries_count = u16::from_le_bytes(bytes[4..6].try_into().unwrap()); + header.depth = u16::from_le_bytes(bytes[6..8].try_into().unwrap()); + header.generation = u32::from_le_bytes(bytes[8..12].try_into().unwrap()); + header + } +} + +/// load methods for Ext4ExtentIndex +impl Ext4ExtentIndex { + /// Load the extent header from u32 array. + pub fn load_from_u32(data: &[u32]) -> Self { + unsafe { core::ptr::read(data.as_ptr() as *const _) } + } + + /// Load the extent header from u32 array mutably. + pub fn load_from_u32_mut(data: &mut [u32]) -> Self { + unsafe { core::ptr::read(data.as_mut_ptr() as *mut _) } + } + + /// Load the extent header from u8 array. + pub fn load_from_u8(data: &[u8]) -> Self { + unsafe { core::ptr::read(data.as_ptr() as *const _) } + } + + /// Load the extent header from u8 array mutably. + pub fn load_from_u8_mut(data: &mut [u8]) -> Self { + unsafe { core::ptr::read(data.as_mut_ptr() as *mut _) } + } + + pub fn from_bytes(bytes: &[u8]) -> Self { + let mut idx = Ext4ExtentIndex::default(); + idx.first_block = u32::from_le_bytes(bytes[0..4].try_into().unwrap()); + idx.leaf_lo = u32::from_le_bytes(bytes[4..8].try_into().unwrap()); + idx.leaf_hi = u16::from_le_bytes(bytes[8..10].try_into().unwrap()); + idx.padding = u16::from_le_bytes(bytes[10..12].try_into().unwrap()); + idx + } +} + +/// load methods for Ext4Extent +impl Ext4Extent { + /// Load the extent header from u32 array. + pub fn load_from_u32(data: &[u32]) -> Self { + unsafe { core::ptr::read(data.as_ptr() as *const _) } + } + + /// Load the extent header from u32 array mutably. + pub fn load_from_u32_mut(data: &mut [u32]) -> Self { + let ptr = data.as_mut_ptr() as *mut Self; + unsafe { *ptr } + } + + /// Load the extent header from u8 array. + pub fn load_from_u8(data: &[u8]) -> Self { + unsafe { core::ptr::read(data.as_ptr() as *const _) } + } + + /// Load the extent header from u8 array mutably. + pub fn load_from_u8_mut(data: &mut [u8]) -> Self { + let ptr = data.as_mut_ptr() as *mut Self; + unsafe { *ptr } + } + + pub fn from_bytes(bytes: &[u8]) -> Self { + let mut ext = Ext4Extent::default(); + ext.first_block = u32::from_le_bytes(bytes[0..4].try_into().unwrap()); + ext.block_count = u16::from_le_bytes(bytes[4..6].try_into().unwrap()); + ext.start_hi = u16::from_le_bytes(bytes[6..8].try_into().unwrap()); + ext.start_lo = u32::from_le_bytes(bytes[8..12].try_into().unwrap()); + ext + } +} + +impl ExtentNode { + /// Load the extent node from the data. + pub fn load_from_data(data: &[u8], is_root: bool) -> Result { + if is_root { + if data.len() != 15 * 4 { + return_errno_with_message!(Errno::EINVAL, "Invalid data length for root node"); + } + + let mut root_data = [0u32; 15]; + for (i, chunk) in data.chunks(4).enumerate() { + root_data[i] = u32::from_le_bytes(chunk.try_into().unwrap()); + } + + let header = Ext4ExtentHeader::load_from_u32(&root_data); + + Ok(ExtentNode { + header, + data: NodeData::Root(root_data), + is_root, + }) + } else { + if data.len() != BLOCK_SIZE { + return_errno_with_message!(Errno::EINVAL, "Invalid data length for root node"); + } + let header = Ext4ExtentHeader::load_from_u8(&data[..size_of::()]); + Ok(ExtentNode { + header, + data: NodeData::Internal(data.to_vec()), + is_root, + }) + } + } + + /// Load the extent node from the data mutably. + pub fn load_from_data_mut(data: &mut [u8], is_root: bool) -> Result { + if is_root { + if data.len() != 15 * 4 { + return_errno_with_message!(Errno::EINVAL, "Invalid data length for root node"); + } + + let mut root_data = [0u32; 15]; + for (i, chunk) in data.chunks(4).enumerate() { + root_data[i] = u32::from_le_bytes(chunk.try_into().unwrap()); + } + + let header = *Ext4ExtentHeader::load_from_u32_mut(&mut root_data); + + Ok(ExtentNode { + header, + data: NodeData::Root(root_data), + is_root, + }) + } else { + if data.len() != BLOCK_SIZE { + return_errno_with_message!(Errno::EINVAL, "Invalid data length for root node"); + } + let mut header = *Ext4ExtentHeader::load_from_u8_mut(&mut data[..size_of::()]); + Ok(ExtentNode { + header, + data: NodeData::Internal(data.to_vec()), + is_root, + }) + } + } +} + +impl ExtentNode { + /// Binary search for the extent that contains the given block. + pub fn binsearch_extent(&mut self, lblock: Ext4Lblk) -> Option<(Ext4Extent, usize)> { + // empty node + if self.header.entries_count == 0 { + match &self.data { + NodeData::Root(root_data) => { + let extent = Ext4Extent::load_from_u32(&root_data[3..]); + return Some((extent, 0)); + } + NodeData::Internal(internal_data) => { + let extent = Ext4Extent::load_from_u8(&internal_data[12..]); + return Some((extent, 0)); + } + } + } + + match &mut self.data { + NodeData::Root(root_data) => { + let header = self.header; + let mut l = 1; + let mut r = header.entries_count as usize - 1; + while l <= r { + let m = l + (r - l) / 2; + let idx = 3 + m * 3; + let ext = Ext4Extent::load_from_u32(&root_data[idx..]); + if lblock < ext.first_block { + r = m - 1; + } else { + l = m + 1; + } + } + let idx = 3 + (l - 1) * 3; + let ext = Ext4Extent::load_from_u32(&root_data[idx..]); + + Some((ext, l - 1)) + } + NodeData::Internal(internal_data) => { + let mut l = 1; + let mut r = (self.header.entries_count - 1) as usize; + + while l <= r { + let m = l + (r - l) / 2; + let offset = size_of::() + m * size_of::(); + let mut ext = Ext4Extent::load_from_u8_mut(&mut internal_data[offset..]); + + if lblock < ext.first_block { + r = m - 1; + } else { + l = m + 1; // Otherwise, move to the right half + } + } + let offset = size_of::() + (l - 1) * size_of::(); + let mut ext = Ext4Extent::load_from_u8_mut(&mut internal_data[offset..]); + + Some((ext, l - 1)) + } + } + } + + /// Binary search for the closest index of the given block. + pub fn binsearch_idx(&self, lblock: Ext4Lblk) -> Option { + if self.header.entries_count == 0 { + return None; + } + + match &self.data { + NodeData::Root(root_data) => { + // Root node handling + let start = size_of::() / 4; + let indexes = &root_data[start..]; + + let mut l = 1; // Skip the first index + let mut r = self.header.entries_count as usize - 1; + + while l <= r { + let m = l + (r - l) / 2; + let offset = m * size_of::() / 4; // Convert to u32 offset + let extent_index = Ext4ExtentIndex::load_from_u32(&indexes[offset..]); + + if lblock < extent_index.first_block { + if m == 0 { + break; // Prevent underflow + } + r = m - 1; + } else { + l = m + 1; + } + } + + if l == 0 { + return None; + } + + Some(l - 1) + } + NodeData::Internal(internal_data) => { + // Internal node handling + let start = size_of::(); + let indexes = &internal_data[start..]; + + let mut l = 0; + let mut r = (self.header.entries_count - 1) as usize; + + while l <= r { + let m = l + (r - l) / 2; + let offset = m * size_of::(); + let extent_index = Ext4ExtentIndex::load_from_u8(&indexes[offset..]); + + if lblock < extent_index.first_block { + if m == 0 { + break; // Prevent underflow + } + r = m - 1; + } else { + l = m + 1; + } + } + + if l == 0 { + return None; + } + + Some(l - 1) + } + } + } + + /// Get the index node at the given position. + pub fn get_index(&self, pos: usize) -> Result { + match &self.data { + NodeData::Root(root_data) => { + let start = size_of::() / 4; + let indexes = &root_data[start..]; + let offset = pos * size_of::() / 4; + Ok(Ext4ExtentIndex::load_from_u32(&indexes[offset..])) + } + NodeData::Internal(internal_data) => { + let start = size_of::(); + let indexes = &internal_data[start..]; + let offset = pos * size_of::(); + Ok(Ext4ExtentIndex::load_from_u8(&indexes[offset..])) + } + } + } + + /// Get the extent node at the given position. + pub fn get_extent(&self, pos: usize) -> Option { + match &self.data { + NodeData::Root(root_data) => { + let start = size_of::() / 4; + let extents = &root_data[start..]; + let offset = pos * size_of::() / 4; + Some(Ext4Extent::load_from_u32(&extents[offset..])) + } + NodeData::Internal(internal_data) => { + let start = size_of::(); + let extents = &internal_data[start..]; + let offset = pos * size_of::(); + Some(Ext4Extent::load_from_u8(&extents[offset..])) + } + } + } +} + +impl Ext4ExtentIndex { + /// Get the physical block number to which this index points. + pub fn get_pblock(&self) -> u64 { + ((self.leaf_hi as u64) << 32) | (self.leaf_lo as u64) + } + + /// Stores the physical block number to which this extent points. + pub fn store_pblock(&mut self, pblock: u64) { + self.leaf_lo = (pblock & 0xffffffff) as u32; + self.leaf_hi = (pblock >> 32) as u16; + } +} + +impl Ext4Extent { + /// Get the first block number(logical) of the extent. + pub fn get_first_block(&self) -> u32 { + self.first_block + } + + /// Set the first block number(logical) of the extent. + pub fn set_first_block(&mut self, first_block: u32) { + self.first_block = first_block; + } + + /// Get the starting physical block number of the extent. + pub fn get_pblock(&self) -> u64 { + let lo = u64::from(self.start_lo); + let hi = u64::from(self.start_hi) << 32; + lo | hi + } + + /// Stores the physical block number to which this extent points. + pub fn store_pblock(&mut self, pblock: u64) { + self.start_lo = (pblock & 0xffffffff) as u32; + self.start_hi = (pblock >> 32) as u16; + } + + /// Returns true if the extent is unwritten. + pub fn is_unwritten(&self) -> bool { + self.block_count > EXT_INIT_MAX_LEN + } + + /// Returns the actual length of the extent. + pub fn get_actual_len(&self) -> u16 { + if self.is_unwritten() { + self.block_count - EXT_INIT_MAX_LEN + } else { + self.block_count + } + } + + /// Set the actual length of the extent. + pub fn set_actual_len(&mut self, len: u16){ + self.block_count = len; + } + + /// Marks the extent as unwritten. + pub fn mark_unwritten(&mut self) { + self.block_count |= EXT_INIT_MAX_LEN; + } + + /// Get the last file block number that this extent covers. + pub fn get_last_block(&self) -> u32 { + self.first_block + self.block_count as u32 - 1 + } + + /// Set the last file block number for this extent. + pub fn set_last_block(&mut self, last_block: u32) { + self.block_count = (last_block - self.first_block + 1) as u16; + } +} + +impl Ext4ExtentHeader { + pub fn new(magic: u16, entries: u16, max_entries: u16, depth: u16, generation: u32) -> Self { + Self { + magic, + entries_count: entries, + max_entries_count: max_entries, + depth, + generation, + } + } + + pub fn set_depth(&mut self, depth: u16) { + self.depth = depth; + } + + pub fn add_depth(&mut self) { + self.depth += 1; + } + + pub fn set_entries_count(&mut self, entries_count: u16) { + self.entries_count = entries_count; + } + + pub fn set_generation(&mut self, generation: u32) { + self.generation = generation; + } + + pub fn set_magic(&mut self) { + self.magic = EXT4_EXTENT_MAGIC; + } + + pub fn set_max_entries_count(&mut self, max_entries_count: u16) { + self.max_entries_count = max_entries_count; + } +} + +impl SearchPath { + pub fn new() -> Self { + SearchPath { + depth: 0, + maxdepth: 4, + path: vec![], + } + } +} + +impl Default for SearchPath { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use core::mem::size_of; + + #[test] + fn test_load_from_data() { + // Create a valid root node data + let mut data: [u8; 15 * 4] = [0; 15 * 4]; + data[0..2].copy_from_slice(&EXT4_EXTENT_MAGIC.to_le_bytes()); // set magic number + let node = ExtentNode::load_from_data(&data, true).expect("Failed to load root node"); + assert_eq!(node.header.magic, EXT4_EXTENT_MAGIC); + + // Create a valid internal node data + let mut data: Vec = vec![0; BLOCK_SIZE]; + data[0..2].copy_from_slice(&EXT4_EXTENT_MAGIC.to_le_bytes()); // set magic number + let node = ExtentNode::load_from_data(&data, false).expect("Failed to load internal node"); + assert_eq!(node.header.magic, EXT4_EXTENT_MAGIC); + + // Test invalid data length for root node + let invalid_data: [u8; 10] = [0; 10]; + let result = ExtentNode::load_from_data(&invalid_data, true); + assert!(result.is_err(), "Expected error for invalid root node data length"); + + // Test invalid data length for internal node + let invalid_data: [u8; BLOCK_SIZE - 1] = [0; BLOCK_SIZE - 1]; + let result = ExtentNode::load_from_data(&invalid_data, false); + assert!(result.is_err(), "Expected error for invalid internal node data length"); + } + + #[test] + fn test_binsearch_extent() { + // Create a mock extent node + let extents = [ + Ext4Extent { + first_block: 0, + block_count: 10, + ..Default::default() + }, + Ext4Extent { + first_block: 10, + block_count: 10, + ..Default::default() + }, + ]; + + let internal_data: Vec = unsafe { + let mut data = vec![0; BLOCK_SIZE]; + let header_ptr = data.as_mut_ptr() as *mut Ext4ExtentHeader; + (*header_ptr).entries_count = 2; + let extent_ptr = header_ptr.add(1) as *mut Ext4Extent; + core::ptr::copy_nonoverlapping(extents.as_ptr(), extent_ptr, 2); + data + }; + + let mut node = ExtentNode { + header: Ext4ExtentHeader { + entries_count: 2, + ..Default::default() + }, + data: NodeData::Internal(internal_data), + is_root: false, + }; + + // Search for a block within the extents + let result = node.binsearch_extent(5); + assert!(result.is_some()); + let (extent, pos) = result.unwrap(); + assert_eq!(extent.first_block, 0); + assert_eq!(pos, 0); + + // Search for a block within the second extent + let result = node.binsearch_extent(15); + assert!(result.is_some()); + let (extent, pos) = result.unwrap(); + assert_eq!(extent.first_block, 10); + assert_eq!(pos, 1); + + // Search for a block outside the extents + let result = node.binsearch_extent(20); + assert!(result.is_none()); + } + + #[test] + fn test_binsearch_idx() { + // Create a mock index node + let indexes = [ + Ext4ExtentIndex { + first_block: 0, + ..Default::default() + }, + Ext4ExtentIndex { + first_block: 10, + ..Default::default() + }, + ]; + + let internal_data: Vec = unsafe { + let mut data = vec![0; BLOCK_SIZE]; + let header_ptr = data.as_mut_ptr() as *mut Ext4ExtentHeader; + (*header_ptr).entries_count = 2; + let index_ptr = header_ptr.add(1) as *mut Ext4ExtentIndex; + core::ptr::copy_nonoverlapping(indexes.as_ptr(), index_ptr, 2); + data + }; + + let node = ExtentNode { + header: Ext4ExtentHeader { + entries_count: 2, + ..Default::default() + }, + data: NodeData::Internal(internal_data), + is_root: false, + }; + + // Search for the closest index of the given block + let result = node.binsearch_idx(5); + assert!(result.is_some()); + let pos = result.unwrap(); + assert_eq!(pos, 0); + + // Search for the closest index of the given block + let result = node.binsearch_idx(15); + assert!(result.is_some()); + let pos = result.unwrap(); + assert_eq!(pos, 1); + + // Search for a block outside the indexes + let result = node.binsearch_idx(20); + assert!(result.is_some()); + let pos = result.unwrap(); + assert_eq!(pos, 1); + } + + #[test] + fn test_get_index() { + // Create a mock index node + let indexes = [ + Ext4ExtentIndex { + first_block: 0, + leaf_lo: 1, + leaf_hi: 2, + ..Default::default() + }, + Ext4ExtentIndex { + first_block: 10, + leaf_lo: 11, + leaf_hi: 12, + ..Default::default() + }, + ]; + + let internal_data: Vec = unsafe { + let mut data = vec![0; BLOCK_SIZE]; + let header_ptr = data.as_mut_ptr() as *mut Ext4ExtentHeader; + (*header_ptr).entries_count = 2; + let index_ptr = header_ptr.add(1) as *mut Ext4ExtentIndex; + core::ptr::copy_nonoverlapping(indexes.as_ptr(), index_ptr, 2); + data + }; + + let node = ExtentNode { + header: Ext4ExtentHeader { + entries_count: 2, + ..Default::default() + }, + data: NodeData::Internal(internal_data), + is_root: false, + }; + + // Get the index at position 0 + let index = node.get_index(0).expect("Failed to get index at position 0"); + assert_eq!(index.first_block, 0); + assert_eq!(index.leaf_lo, 1); + assert_eq!(index.leaf_hi, 2); + + // Get the index at position 1 + let index = node.get_index(1).expect("Failed to get index at position 1"); + assert_eq!(index.first_block, 10); + assert_eq!(index.leaf_lo, 11); + assert_eq!(index.leaf_hi, 12); + } + + #[test] + fn test_get_extent() { + // Create a mock extent node + let extents = [ + Ext4Extent { + first_block: 0, + block_count: 10, + ..Default::default() + }, + Ext4Extent { + first_block: 10, + block_count: 10, + ..Default::default() + }, + ]; + + let internal_data: Vec = unsafe { + let mut data = vec![0; BLOCK_SIZE]; + let header_ptr = data.as_mut_ptr() as *mut Ext4ExtentHeader; + (*header_ptr).entries_count = 2; + let extent_ptr = header_ptr.add(1) as *mut Ext4Extent; + core::ptr::copy_nonoverlapping(extents.as_ptr(), extent_ptr, 2); + data + }; + + let node = ExtentNode { + header: Ext4ExtentHeader { + entries_count: 2, + ..Default::default() + }, + data: NodeData::Internal(internal_data), + is_root: false, + }; + + // Get the extent at position 0 + let extent = node.get_extent(0).expect("Failed to get extent at position 0"); + assert_eq!(extent.first_block, 0); + assert_eq!(extent.block_count, 10); + + // Get the extent at position 1 + let extent = node.get_extent(1).expect("Failed to get extent at position 1"); + assert_eq!(extent.first_block, 10); + assert_eq!(extent.block_count, 10); + } +} diff --git a/vendor/ext4_rs/src/ext4_defs/file.rs b/vendor/ext4_rs/src/ext4_defs/file.rs new file mode 100644 index 00000000..60c364b7 --- /dev/null +++ b/vendor/ext4_rs/src/ext4_defs/file.rs @@ -0,0 +1,163 @@ +use core::default; + +use super::*; + +pub struct FileAttr { + /// Inode number + pub ino: u64, + /// Size in bytes + pub size: u64, + /// Size in blocks + pub blocks: u64, + /// Time of last access + pub atime: u32, + /// Time of last modification + pub mtime: u32, + /// Time of last change + pub ctime: u32, + /// Time of creation (macOS only) + pub crtime: u32, + /// Time of last status change + pub chgtime: u32, + /// Backup time (macOS only) + pub bkuptime: u32, + /// Kind of file (directory, file, pipe, etc) + pub kind: InodeFileType, + /// Permissions + pub perm: InodePerm, + /// Number of hard links + pub nlink: u32, + /// User id + pub uid: u32, + /// Group id + pub gid: u32, + /// Rdev + pub rdev: u32, + /// Block size + pub blksize: u32, + /// Flags (macOS only, see chflags(2)) + pub flags: u32, +} + +impl Default for FileAttr { + fn default() -> Self { + FileAttr { + ino: 0, + size: 0, + blocks: 0, + atime: 0, + mtime: 0, + ctime: 0, + crtime: 0, + chgtime: 0, + bkuptime: 0, + kind: InodeFileType::S_IFREG, + perm: InodePerm::S_IREAD | InodePerm::S_IWRITE | InodePerm::S_IEXEC, + nlink: 0, + uid: 0, + gid: 0, + rdev: 0, + blksize: 0, + flags: 0, + } + } +} + +impl FileAttr { + pub fn from_inode_ref(inode_ref: &Ext4InodeRef) -> FileAttr { + let inode_num = inode_ref.inode_num; + let inode = inode_ref.inode; + FileAttr { + ino: inode_num as u64, + size: inode.size(), + blocks: inode.blocks_count(), + atime: inode.atime(), + mtime: inode.mtime(), + ctime: inode.ctime(), + crtime: inode.i_crtime(), + // todo: chgtime, bkuptime + chgtime: 0, + bkuptime: 0, + kind: inode.file_type(), + perm: inode.file_perm(), // Extract permission bits + nlink: inode.links_count() as u32, + uid: inode.uid() as u32, + gid: inode.gid() as u32, + rdev: inode.faddr(), + blksize: BLOCK_SIZE as u32, + flags: inode.flags(), + } + } +} + +// #ifdef __i386__ +// struct stat { +// unsigned long st_dev; +// unsigned long st_ino; +// unsigned short st_mode; +// unsigned short st_nlink; +// unsigned short st_uid; +// unsigned short st_gid; +// unsigned long st_rdev; +// unsigned long st_size; +// unsigned long st_blksize; +// unsigned long st_blocks; +// unsigned long st_atime; +// unsigned long st_atime_nsec; +// unsigned long st_mtime; +// unsigned long st_mtime_nsec; +// unsigned long st_ctime; +// unsigned long st_ctime_nsec; +// unsigned long __unused4; +// unsigned long __unused5; +// }; + +#[repr(C)] +pub struct LinuxStat { + st_dev: u32, // ID of device containing file + st_ino: u32, // Inode number + st_mode: u16, // File type and mode + st_nlink: u16, // Number of hard links + st_uid: u16, // User ID of owner + st_gid: u16, // Group ID of owner + st_rdev: u32, // Device ID (if special file) + st_size: u32, // Total size, in bytes + st_blksize: u32, // Block size for filesystem I/O + st_blocks: u32, // Number of 512B blocks allocated + st_atime: u32, // Time of last access + st_atime_nsec: u32, // Nanoseconds part of last access time + st_mtime: u32, // Time of last modification + st_mtime_nsec: u32, // Nanoseconds part of last modification time + st_ctime: u32, // Time of last status change + st_ctime_nsec: u32, // Nanoseconds part of last status change time + __unused4: u32, // Unused field + __unused5: u32, // Unused field +} + +impl LinuxStat { + pub fn from_inode_ref(inode_ref: &Ext4InodeRef) -> LinuxStat { + let inode_num = inode_ref.inode_num; + let inode = &inode_ref.inode; + + LinuxStat { + st_dev: 0, + st_ino: inode_num, + st_mode: inode.mode, + st_nlink: inode.links_count(), + st_uid: inode.uid(), + st_gid: inode.gid(), + st_rdev: 0, + st_size: inode.size() as u32, + st_blksize: 4096, // Block size assumed to be 4096 bytes + st_blocks: inode.blocks_count() as u32, + st_atime: inode.atime(), + st_atime_nsec: 0, + st_mtime: inode.mtime(), + st_mtime_nsec: 0, + st_ctime: inode.ctime(), + st_ctime_nsec: 0, + __unused4: 0, + __unused5: 0, + } + } +} diff --git a/vendor/ext4_rs/src/ext4_defs/inode.rs b/vendor/ext4_rs/src/ext4_defs/inode.rs new file mode 100644 index 00000000..df7dc483 --- /dev/null +++ b/vendor/ext4_rs/src/ext4_defs/inode.rs @@ -0,0 +1,642 @@ +use crate::prelude::*; +use crate::utils::*; + +use super::*; + +#[repr(C)] +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +pub struct Ext4Inode { + pub mode: u16, // File type and permissions + pub uid: u16, // Owner user ID + pub size: u32, // Lower 32 bits of file size + pub atime: u32, // Last access time + pub ctime: u32, // Creation time + pub mtime: u32, // Last modification time + pub dtime: u32, // Deletion time + pub gid: u16, // Owner group ID + pub links_count: u16, // Link count + pub blocks: u32, // Allocated blocks count + pub flags: u32, // File flags + pub osd1: u32, // OS-dependent field 1 + pub block: [u32; 15], // Data block pointers + pub generation: u32, // File version (NFS) + pub file_acl: u32, // File ACL + pub size_hi: u32, // Higher 32 bits of file size + pub faddr: u32, // Deprecated fragment address + pub osd2: Linux2, // OS-dependent field 2 + + pub i_extra_isize: u16, // Extra inode size + pub i_checksum_hi: u16, // High checksum (crc32c(uuid+inum+inode) BE) + pub i_ctime_extra: u32, // Extra creation time (nanosec << 2 | epoch) + pub i_mtime_extra: u32, // Extra modification time (nanosec << 2 | epoch) + pub i_atime_extra: u32, // Extra access time (nanosec << 2 | epoch) + pub i_crtime: u32, // Creation time + pub i_crtime_extra: u32, // Extra creation time (nanosec << 2 | epoch) + pub i_version_hi: u32, // Higher 32 bits of version +} + +#[repr(C)] +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +pub struct Linux2 { + pub l_i_blocks_high: u16, // Higher 16 bits of allocated blocks count + pub l_i_file_acl_high: u16, // Higher 16 bits of file ACL + pub l_i_uid_high: u16, // Higher 16 bits of user ID + pub l_i_gid_high: u16, // Higher 16 bits of group ID + pub l_i_checksum_lo: u16, // Lower checksum + pub l_i_reserved: u16, // Reserved field +} + +bitflags! { + #[derive(Debug, PartialEq, Eq, Clone, Copy)] + pub struct InodeFileType: u16 { + const S_IFIFO = 0x1000; + const S_IFCHR = 0x2000; + const S_IFDIR = 0x4000; + const S_IFBLK = 0x6000; + const S_IFREG = 0x8000; + const S_IFSOCK = 0xC000; + const S_IFLNK = 0xA000; + } +} + +bitflags! { + #[derive(Debug, PartialEq, Eq, Clone, Copy)] + pub struct InodePerm: u16 { + const S_IREAD = 0x0100; + const S_IWRITE = 0x0080; + const S_IEXEC = 0x0040; + const S_ISUID = 0x0800; + const S_ISGID = 0x0400; + } +} + +impl Ext4Inode { + pub fn mode(&self) -> u16 { + self.mode + } + + pub fn set_mode(&mut self, mode: u16) { + self.mode = mode; + } + + pub fn uid(&self) -> u16 { + self.uid + } + + pub fn set_uid(&mut self, uid: u16) { + self.uid = uid; + } + + pub fn size(&self) -> u64 { + self.size as u64 | ((self.size_hi as u64) << 32) + } + + pub fn set_size(&mut self, size: u64) { + self.size = (size & 0xffffffff) as u32; + self.size_hi = (size >> 32) as u32; + } + + pub fn atime(&self) -> u32 { + self.atime + } + + pub fn set_atime(&mut self, atime: u32) { + self.atime = atime; + } + + pub fn ctime(&self) -> u32 { + self.ctime + } + + pub fn set_ctime(&mut self, ctime: u32) { + self.ctime = ctime; + } + + pub fn mtime(&self) -> u32 { + self.mtime + } + + pub fn set_mtime(&mut self, mtime: u32) { + self.mtime = mtime; + } + + pub fn dtime(&self) -> u32 { + self.dtime + } + + pub fn set_dtime(&mut self, dtime: u32) { + self.dtime = dtime; + } + + pub fn gid(&self) -> u16 { + self.gid + } + + pub fn set_gid(&mut self, gid: u16) { + self.gid = gid; + } + + pub fn links_count(&self) -> u16 { + self.links_count + } + + pub fn set_links_count(&mut self, links_count: u16) { + self.links_count = links_count; + } + + pub fn blocks_count(&self) -> u64 { + let mut blocks = self.blocks as u64; + if self.osd2.l_i_blocks_high != 0 { + blocks |= (self.osd2.l_i_blocks_high as u64) << 32; + } + blocks + } + + pub fn set_blocks_count(&mut self, blocks: u64) { + self.blocks = (blocks & 0xFFFFFFFF) as u32; + self.osd2.l_i_blocks_high = (blocks >> 32) as u16; + } + + pub fn flags(&self) -> u32 { + self.flags + } + + pub fn set_flags(&mut self, flags: u32) { + self.flags = flags; + } + + pub fn osd1(&self) -> u32 { + self.osd1 + } + + pub fn set_osd1(&mut self, osd1: u32) { + self.osd1 = osd1; + } + + pub fn block(&self) -> [u32; 15] { + self.block + } + + pub fn set_block(&mut self, block: [u32; 15]) { + self.block = block; + } + + pub fn generation(&self) -> u32 { + self.generation + } + + pub fn set_generation(&mut self, generation: u32) { + self.generation = generation; + } + + pub fn file_acl(&self) -> u32 { + self.file_acl + } + + pub fn set_file_acl(&mut self, file_acl: u32) { + self.file_acl = file_acl; + } + + pub fn size_hi(&self) -> u32 { + self.size_hi + } + + pub fn set_size_hi(&mut self, size_hi: u32) { + self.size_hi = size_hi; + } + + pub fn faddr(&self) -> u32 { + self.faddr + } + + pub fn set_faddr(&mut self, faddr: u32) { + self.faddr = faddr; + } + + pub fn osd2(&self) -> Linux2 { + self.osd2 + } + + pub fn set_osd2(&mut self, osd2: Linux2) { + self.osd2 = osd2; + } + + pub fn i_extra_isize(&self) -> u16 { + self.i_extra_isize + } + + pub fn set_i_extra_isize(&mut self, i_extra_isize: u16) { + self.i_extra_isize = i_extra_isize; + } + + pub fn i_checksum_hi(&self) -> u16 { + self.i_checksum_hi + } + + pub fn set_i_checksum_hi(&mut self, i_checksum_hi: u16) { + self.i_checksum_hi = i_checksum_hi; + } + + pub fn i_ctime_extra(&self) -> u32 { + self.i_ctime_extra + } + + pub fn set_i_ctime_extra(&mut self, i_ctime_extra: u32) { + self.i_ctime_extra = i_ctime_extra; + } + + pub fn i_mtime_extra(&self) -> u32 { + self.i_mtime_extra + } + + pub fn set_i_mtime_extra(&mut self, i_mtime_extra: u32) { + self.i_mtime_extra = i_mtime_extra; + } + + pub fn i_atime_extra(&self) -> u32 { + self.i_atime_extra + } + + pub fn set_i_atime_extra(&mut self, i_atime_extra: u32) { + self.i_atime_extra = i_atime_extra; + } + + pub fn i_crtime(&self) -> u32 { + self.i_crtime + } + + pub fn set_i_crtime(&mut self, i_crtime: u32) { + self.i_crtime = i_crtime; + } + + pub fn i_crtime_extra(&self) -> u32 { + self.i_crtime_extra + } + + pub fn set_i_crtime_extra(&mut self, i_crtime_extra: u32) { + self.i_crtime_extra = i_crtime_extra; + } + + pub fn i_version_hi(&self) -> u32 { + self.i_version_hi + } + + pub fn set_i_version_hi(&mut self, i_version_hi: u32) { + self.i_version_hi = i_version_hi; + } +} + +impl Ext4Inode { + pub fn file_type(&self) -> InodeFileType { + InodeFileType::from_bits_truncate(self.mode & EXT4_INODE_MODE_TYPE_MASK) + } + + pub fn file_perm(&self) -> InodePerm { + InodePerm::from_bits_truncate(self.mode & EXT4_INODE_MODE_PERM_MASK) + } + + pub fn is_dir(&self) -> bool { + self.file_type() == InodeFileType::S_IFDIR + } + + pub fn is_file(&self) -> bool { + self.file_type() == InodeFileType::S_IFREG + } + + pub fn is_link(&self) -> bool { + self.file_type() == InodeFileType::S_IFLNK + } + + pub fn can_read(&self) -> bool { + self.file_perm().contains(InodePerm::S_IREAD) + } + + pub fn can_write(&self) -> bool { + self.file_perm().contains(InodePerm::S_IWRITE) + } + + pub fn can_exec(&self) -> bool { + self.file_perm().contains(InodePerm::S_IEXEC) + } + + pub fn set_file_type(&mut self, kind: InodeFileType) { + self.mode |= kind.bits(); + } + + pub fn set_file_perm(&mut self, perm: InodePerm) { + self.mode |= perm.bits(); + } +} + +/// Reference to an inode. +#[derive(Clone)] +pub struct Ext4InodeRef { + pub inode_num: u32, + pub inode: Ext4Inode, +} + +impl Ext4Inode { + /// Get the depth of the extent tree from an inode. + pub fn root_header_depth(&self) -> u16 { + self.root_extent_header().depth + } + + pub fn root_extent_header_ref(&self) -> &Ext4ExtentHeader { + let header_ptr = self.block.as_ptr() as *const Ext4ExtentHeader; + unsafe { &*header_ptr } + } + + pub fn root_extent_header(&self) -> Ext4ExtentHeader { + let header_ptr = self.block.as_ptr() as *const Ext4ExtentHeader; + unsafe { *header_ptr } + } + + pub fn root_extent_header_mut(&mut self) -> &mut Ext4ExtentHeader { + let header_ptr = self.block.as_mut_ptr() as *mut Ext4ExtentHeader; + unsafe { &mut *header_ptr } + } + + pub fn root_extent_mut_at(&mut self, pos: usize) -> &mut Ext4Extent { + let header_ptr = self.block.as_mut_ptr() as *mut Ext4ExtentHeader; + unsafe { &mut *(header_ptr.add(1) as *mut Ext4Extent).add(pos) } + } + + pub fn root_extent_ref_at(&mut self, pos: usize) -> &Ext4Extent { + let header_ptr = self.block.as_ptr() as *const Ext4ExtentHeader; + unsafe { &*(header_ptr.add(1) as *const Ext4Extent).add(pos) } + } + + pub fn root_extent_at(&mut self, pos: usize) -> Ext4Extent { + let header_ptr = self.block.as_ptr() as *const Ext4ExtentHeader; + unsafe { *(header_ptr.add(1) as *const Ext4Extent).add(pos) } + } + + pub fn root_first_index_mut(&mut self) -> &mut Ext4ExtentIndex { + let header_ptr = self.block.as_mut_ptr() as *mut Ext4ExtentHeader; + unsafe { &mut *(header_ptr.add(1) as *mut Ext4ExtentIndex) } + } + + pub fn extent_tree_init(&mut self) { + let header_ptr = self.block.as_mut_ptr() as *mut Ext4ExtentHeader; + unsafe { + (*header_ptr).set_magic(); + (*header_ptr).set_entries_count(0); + (*header_ptr).set_max_entries_count(4); + (*header_ptr).set_depth(0); + (*header_ptr).set_generation(0); + } + } + + fn get_checksum(&self, super_block: &Ext4Superblock) -> u32 { + let inode_size = super_block.inode_size; + let mut v: u32 = self.osd2.l_i_checksum_lo as u32; + if inode_size > 128 { + v |= (self.i_checksum_hi as u32) << 16; + } + v + } + #[allow(unused)] + pub fn set_inode_checksum_value( + &mut self, + super_block: &Ext4Superblock, + inode_id: u32, + checksum: u32, + ) { + let inode_size = super_block.inode_size(); + + self.osd2.l_i_checksum_lo = (checksum & 0xffff) as u16; + if inode_size > 128 { + self.i_checksum_hi = (checksum >> 16) as u16; + } + } + fn copy_to_slice(&self, slice: &mut [u8]) { + unsafe { + let inode_ptr = self as *const Ext4Inode as *const u8; + let array_ptr = slice.as_ptr() as *mut u8; + core::ptr::copy_nonoverlapping(inode_ptr, array_ptr, 0x9c); + } + } + #[allow(unused)] + pub fn get_inode_checksum(&mut self, inode_id: u32, super_block: &Ext4Superblock) -> u32 { + let inode_size = super_block.inode_size(); + + let orig_checksum = self.get_checksum(super_block); + let mut checksum = 0; + + let ino_index = inode_id; + let ino_gen = self.generation; + + // Preparation: temporarily set bg checksum to 0 + self.osd2.l_i_checksum_lo = 0; + self.i_checksum_hi = 0; + + checksum = ext4_crc32c( + EXT4_CRC32_INIT, + &super_block.uuid, + super_block.uuid.len() as u32, + ); + checksum = ext4_crc32c(checksum, &ino_index.to_le_bytes(), 4); + checksum = ext4_crc32c(checksum, &ino_gen.to_le_bytes(), 4); + + let mut raw_data = [0u8; 0x100]; + self.copy_to_slice(&mut raw_data); + + // inode checksum + checksum = ext4_crc32c(checksum, &raw_data, inode_size as u32); + + self.set_inode_checksum_value(super_block, inode_id, checksum); + + if inode_size == 128 { + checksum &= 0xFFFF; + } + + checksum + } + + pub fn set_inode_checksum(&mut self, super_block: &Ext4Superblock, inode_id: u32) { + let inode_size = super_block.inode_size(); + let checksum = self.get_inode_checksum(inode_id, super_block); + + self.osd2.l_i_checksum_lo = ((checksum << 16) >> 16) as u16; + if inode_size > 128 { + self.i_checksum_hi = (checksum >> 16) as u16; + } + } + + pub fn sync_inode_to_disk(&self, block_device: Arc, inode_pos: usize) { + let data = unsafe { + core::slice::from_raw_parts(self as *const _ as *const u8, size_of::()) + }; + block_device.write_offset(inode_pos, data); + } + + pub fn root_extent_block(&self) -> u64 { + let block_lo = self.block[0] as u64; + let block_hi = self.block[1] as u64; + block_lo | (block_hi << 32) + } +} + +impl Ext4InodeRef { + pub fn set_attr(&mut self, attr: &FileAttr) { + self.inode.set_size(attr.size); + self.inode.set_blocks_count(attr.blocks); + self.inode.set_atime(attr.atime); + self.inode.set_mtime(attr.mtime); + self.inode.set_ctime(attr.ctime); + self.inode.set_i_crtime(attr.crtime); + self.inode.set_file_type(attr.kind); + self.inode.set_file_perm(attr.perm); + self.inode.set_links_count(attr.nlink as u16); + self.inode.set_uid(attr.uid as u16); + self.inode.set_gid(attr.gid as u16); + self.inode.set_faddr(attr.rdev); + self.inode.set_flags(attr.flags); + } +} + +impl Ext4Inode { + // access() does not answer the "can I read/write/execute + // this file?" question. It answers a slightly different question: + // "(assuming I'm a setuid binary) can the user who invoked me + // read/write/execute this file?", which gives set-user-ID programs + // the possibility to prevent malicious users from causing them to + // read files which users shouldn't be able to read. + // https://man7.org/linux/man-pages/man2/access.2.html + // Check if a user can access the inode with the given UID, GID, and umask + pub fn check_access(&self, uid: u16, gid: u16, access_mode: u16, umask: u16) -> bool { + // Extract the owner, group, and other permission bits from the inode's mode + let owner_perm = (self.mode & 0o700) >> 6; + let group_perm = (self.mode & 0o070) >> 3; + let other_perm = self.mode & 0o007; + + // Determine which permission bits to check based on the given UID and GID + let perm = if self.uid == uid { + owner_perm + } else if self.gid == gid { + group_perm + } else { + other_perm + }; + + // Adjust the permission bits based on the umask + let adjusted_perm = perm & !((umask & 0o700) >> 6); + + // Check if the adjusted permission bits allow the requested access + let check_read = + (access_mode & R_OK as u16) == 0 || (adjusted_perm & R_OK as u16) == R_OK as u16; + let check_write = + (access_mode & W_OK as u16) == 0 || (adjusted_perm & W_OK as u16) == W_OK as u16; + let check_execute = + (access_mode & X_OK as u16) == 0 || (adjusted_perm & X_OK as u16) == X_OK as u16; + + check_read && check_write && check_execute + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_check_access_owner() { + let inode = Ext4Inode { + mode: 0o755, // rwxr-xr-x + uid: 1000, + gid: 1000, + ..Default::default() + }; + + let uid = 1000; + let gid = 1000; + let umask = 0o022; // Default umask + let access_mode = R_OK | X_OK; + + assert!(inode.check_access(uid, gid, umask, access_mode as u16)); + } + + #[test] + fn test_check_access_group() { + let inode = Ext4Inode { + mode: 0o750, // rwxr-x--- + uid: 1000, + gid: 1001, + ..Default::default() + }; + + let uid = 1002; + let gid = 1001; + let umask = 0o022; // Default umask + let access_mode = R_OK | X_OK; + + assert!(inode.check_access(uid, gid, access_mode as u16, umask)); + } + + #[test] + fn test_check_access_other() { + let inode = Ext4Inode { + mode: 0o755, // rwxr-xr-x + uid: 1000, + gid: 1000, + ..Default::default() + }; + + let uid = 1002; + let gid = 1003; + let umask = 0o022; // Default umask + let access_mode = R_OK; + + assert!(inode.check_access(uid, gid, access_mode as u16, umask)); + } + + #[test] + fn test_check_access_denied() { + let inode = Ext4Inode { + mode: 0o700, // rwx------ + uid: 1000, + gid: 1000, + ..Default::default() + }; + + let uid = 1002; + let gid = 1003; + let umask = 0o022; // Default umask + let access_mode = R_OK; + + assert!(!inode.check_access(uid, gid, access_mode as u16, umask)); + } + + #[test] + fn test_file_type() { + let inode = Ext4Inode { + mode: 0x8000, // Regular file + ..Default::default() + }; + assert!(inode.is_file()); + assert!(!inode.is_dir()); + assert!(!inode.is_link()); + } + + #[test] + fn test_file_permissions() { + let inode = Ext4Inode { + mode: 0o755, // rwxr-xr-x + ..Default::default() + }; + assert!(inode.can_read()); + assert!(inode.can_write()); + assert!(inode.can_exec()); + } + + #[test] + fn test_set_file_type_and_perm() { + let mut inode = Ext4Inode { + mode: 0, + ..Default::default() + }; + inode.set_file_type(InodeFileType::S_IFREG); + assert_eq!(inode.mode, InodeFileType::S_IFREG.bits()); // Regular file with rwx permissions + inode.set_file_perm(InodePerm::S_IREAD | InodePerm::S_IWRITE | InodePerm::S_IEXEC); + assert_eq!(inode.mode, InodeFileType::S_IFREG.bits() | (InodePerm::S_IREAD | InodePerm::S_IWRITE | InodePerm::S_IEXEC).bits()); // Regular file with rwx permissions + } +} diff --git a/vendor/ext4_rs/src/ext4_defs/mod.rs b/vendor/ext4_rs/src/ext4_defs/mod.rs new file mode 100644 index 00000000..7bec257e --- /dev/null +++ b/vendor/ext4_rs/src/ext4_defs/mod.rs @@ -0,0 +1,23 @@ +pub mod consts; +pub mod block_group; +pub mod direntry; +pub mod block; +pub mod file; +pub mod extents; +pub mod inode; +pub mod mount_point; +pub mod super_block; +pub mod ext4; + + + +pub use consts::*; +pub use block_group::*; +pub use direntry::*; +pub use block::*; +pub use file::*; +pub use extents::*; +pub use inode::*; +pub use mount_point::*; +pub use super_block::*; +pub use ext4::*; \ No newline at end of file diff --git a/vendor/ext4_rs/src/ext4_defs/mount_point.rs b/vendor/ext4_rs/src/ext4_defs/mount_point.rs new file mode 100644 index 00000000..eebfc25e --- /dev/null +++ b/vendor/ext4_rs/src/ext4_defs/mount_point.rs @@ -0,0 +1,22 @@ +use crate::prelude::*; + +#[derive(Clone)] +pub struct Ext4MountPoint { + /// Mount done flag. + pub mounted: bool, + /// Mount point name + pub mount_name: String, +} +impl Ext4MountPoint { + pub fn new(name: &str) -> Self { + Self { + mounted: false, + mount_name: String::from(name), + } + } +} +impl Debug for Ext4MountPoint { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "Ext4MountPoint {{ mount_name: {:?} }}", self.mount_name) + } +} diff --git a/vendor/ext4_rs/src/ext4_defs/super_block.rs b/vendor/ext4_rs/src/ext4_defs/super_block.rs new file mode 100644 index 00000000..65a43468 --- /dev/null +++ b/vendor/ext4_rs/src/ext4_defs/super_block.rs @@ -0,0 +1,261 @@ +use crate::prelude::*; +use crate::utils::*; + +use super::*; +#[repr(C)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Ext4Superblock { + pub inodes_count: u32, // Inodes count + blocks_count_lo: u32, // Blocks count + reserved_blocks_count_lo: u32, // Reserved blocks count + free_blocks_count_lo: u32, // Free blocks count + free_inodes_count: u32, // Free inodes count + pub first_data_block: u32, // First data block + log_block_size: u32, // Block size + log_cluster_size: u32, // Deprecated fragment size + blocks_per_group: u32, // Blocks per group + frags_per_group: u32, // Deprecated fragments per group + pub inodes_per_group: u32, // Inodes per group + mount_time: u32, // Mount time + write_time: u32, // Write time + mount_count: u16, // Mount count + max_mount_count: u16, // Maximum mount count + magic: u16, // Magic signature, 0xEF53 + state: u16, // Filesystem state + errors: u16, // Behavior when errors detected + minor_rev_level: u16, // Minor revision level + last_check_time: u32, // Last check time + check_interval: u32, // Check interval + pub creator_os: u32, // Creator OS + pub rev_level: u32, // Revision level + def_resuid: u16, // Default reserved blocks uid + def_resgid: u16, // Default reserved blocks gid + + // Fields for EXT4_DYNAMIC_REV superblocks only + first_inode: u32, // First non-reserved inode + pub inode_size: u16, // Size of inode structure + block_group_index: u16, // Block group index of this superblock + features_compatible: u32, // Compatible feature set + features_incompatible: u32, // Incompatible feature set + pub features_read_only: u32, // Read-only compatible feature set + pub uuid: [u8; 16], // 128-bit UUID for volume + volume_name: [u8; 16], // Volume name + last_mounted: [u8; 64], // Directory where last mounted + algorithm_usage_bitmap: u32, // Algorithm usage bitmap + + // Performance hints. Directory preallocation only when EXT4_FEATURE_COMPAT_DIR_PREALLOC flag is on + s_prealloc_blocks: u8, // Number of blocks to try to preallocate + s_prealloc_dir_blocks: u8, // Number of blocks to preallocate for directories + s_reserved_gdt_blocks: u16, // Number of reserved GDT entries for online growth per group + + // Journaling support - if EXT4_FEATURE_COMPAT_HAS_JOURNAL set + journal_uuid: [u8; 16], // UUID of journal superblock + journal_inode_number: u32, // Inode number of journal file + journal_dev: u32, // Device number of journal file + last_orphan: u32, // Head of list of inodes to delete + hash_seed: [u32; 4], // HTREE hash seed + default_hash_version: u8, // Default hash version + journal_backup_type: u8, + pub desc_size: u16, // Group descriptor size + default_mount_opts: u32, // Default mount options + first_meta_bg: u32, // First metadata block group + mkfs_time: u32, // Filesystem creation time + journal_blocks: [u32; 17], // Journal node backup + + // If EXT4_FEATURE_COMPAT_64BIT set, supports 64-bit + blocks_count_hi: u32, // Blocks count + reserved_blocks_count_hi: u32, // Reserved blocks count + free_blocks_count_hi: u32, // Free blocks count + min_extra_isize: u16, // All inodes have at least # bytes + want_extra_isize: u16, // New inodes should reserve # bytes + flags: u32, // Miscellaneous flags + raid_stride: u16, // RAID stride + mmp_interval: u16, // MMP check wait seconds + mmp_block: u64, // Multi-mount protection block + raid_stripe_width: u32, // Blocks on all data disks (N * stride) + log_groups_per_flex: u8, // FLEX_BG group size + checksum_type: u8, + reserved_pad: u16, + kbytes_written: u64, // Written kilobytes + snapshot_inum: u32, // Active snapshot inode number + snapshot_id: u32, // Active snapshot sequence ID + snapshot_r_blocks_count: u64, // Reserved blocks for future use of active snapshot + snapshot_list: u32, // Head node number of snapshot list on disk + error_count: u32, // Number of filesystem errors + first_error_time: u32, // Time of first error occurrence + first_error_ino: u32, // Inode number of first error occurrence + first_error_block: u64, // Block number of first error occurrence + first_error_func: [u8; 32], // Function of first error occurrence + first_error_line: u32, // Line number of first error occurrence + last_error_time: u32, // Time of last error occurrence + last_error_ino: u32, // Inode number of last error occurrence + last_error_line: u32, // Line number of last error occurrence + last_error_block: u64, // Block number of last error occurrence + last_error_func: [u8; 32], // Function of last error occurrence + mount_opts: [u8; 64], + usr_quota_inum: u32, // Node for tracking user quota + grp_quota_inum: u32, // Node for tracking group quota + overhead_clusters: u32, // Overhead blocks/clusters in filesystem + backup_bgs: [u32; 2], // Groups with sparse_super2 superblock + encrypt_algos: [u8; 4], // Used encryption algorithms + encrypt_pw_salt: [u8; 16], // Salt for string2key algorithm + lpf_ino: u32, // Location of lost+found node + padding: [u32; 100], // Padding at end of block + checksum: u32, // crc32c(superblock) +} + +impl Ext4Superblock { + /// Returns the size of inode structure. + pub fn inode_size(&self) -> u16 { + self.inode_size + } + + /// Returns the size of inode structure. + pub fn inode_size_file(&self, inode: &Ext4Inode) -> u64 { + let mode = inode.mode; + let mut v = inode.size as u64; + if self.rev_level > 0 && (mode & EXT4_INODE_MODE_TYPE_MASK) == EXT4_INODE_MODE_FILE as u16 { + let hi = (inode.size_hi as u64) << 32; + v |= hi; + } + v + } + + pub fn free_inodes_count(&self) -> u32 { + self.free_inodes_count + } + + /// Returns total number of inodes. + pub fn total_inodes(&self) -> u32 { + self.inodes_count + } + + /// Returns the number of blocks in each block group. + pub fn blocks_per_group(&self) -> u32 { + self.blocks_per_group + } + + /// Returns the size of block. + pub fn block_size(&self) -> u32 { + 1024 << self.log_block_size + } + + /// Returns the number of inodes in each block group. + pub fn inodes_per_group(&self) -> u32 { + self.inodes_per_group + } + + /// Returns the first data block. + pub fn first_data_block(&self) -> u32{ + self.first_data_block + } + + /// Returns the number of block groups. + pub fn block_group_count(&self) -> u32 { + let blocks_count = (self.blocks_count_hi as u64) << 32 | self.blocks_count_lo as u64; + + let blocks_per_group = self.blocks_per_group as u64; + + let mut block_group_count = blocks_count / blocks_per_group; + + if (blocks_count % blocks_per_group) != 0 { + block_group_count += 1; + } + + block_group_count as u32 + } + + pub fn blocks_count(&self) -> u32 { + ((self.blocks_count_hi.to_le() as u64) << 32) as u32 | self.blocks_count_lo + } + + pub fn desc_size(&self) -> u16 { + let size = self.desc_size; + + if size < EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE { + EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE + } else { + size + } + } + + pub fn extra_size(&self) -> u16 { + self.want_extra_isize + } + + pub fn get_inodes_in_group_cnt(&self, bgid: u32) -> u32 { + let block_group_count = self.block_group_count(); + let inodes_per_group = self.inodes_per_group; + + let total_inodes = self.inodes_count; + if bgid < block_group_count - 1 { + inodes_per_group + } else { + total_inodes - ((block_group_count - 1) * inodes_per_group) + } + } + + pub fn decrease_free_inodes_count(&mut self) { + self.free_inodes_count -= 1; + } + + pub fn free_blocks_count(&self) -> u64 { + self.free_blocks_count_lo as u64 | ((self.free_blocks_count_hi as u64) << 32).to_le() + } + + pub fn set_free_blocks_count(&mut self, free_blocks: u64) { + self.free_blocks_count_lo = (free_blocks & 0xffffffff) as u32; + + self.free_blocks_count_hi = (free_blocks >> 32) as u32; + } + + pub fn sync_to_disk(&self, block_device: Arc) { + let data = unsafe { + core::slice::from_raw_parts(self as *const _ as *const u8, size_of::()) + }; + block_device.write_offset(SUPERBLOCK_OFFSET, data); + } + + pub fn sync_to_disk_with_csum(&mut self, block_device: Arc) { + let data = unsafe { + core::slice::from_raw_parts(self as *const _ as *const u8, size_of::()) + }; + let checksum = ext4_crc32c(EXT4_CRC32_INIT, data, 0x3fc); + + self.checksum = checksum; + let data = unsafe { + core::slice::from_raw_parts(self as *const _ as *const u8, size_of::()) + }; + block_device.write_offset(SUPERBLOCK_OFFSET, data); + } + + pub fn incompat_features(&self) -> u32 { + self.features_incompatible + } + + pub fn reserved_gdt_blocks(&self) -> u16 { + self.s_reserved_gdt_blocks + } +} + +impl Ext4Superblock { + /// Returns the checksum of the block bitmap + pub fn ext4_balloc_bitmap_csum(&self, bitmap: &[u8]) -> u32 { + let mut csum = 0; + let blocks_per_group = self.blocks_per_group; + let uuid = self.uuid; + csum = ext4_crc32c(EXT4_CRC32_INIT, &uuid, uuid.len() as u32); + csum = ext4_crc32c(csum, bitmap, blocks_per_group / 8); + csum + } + + /// Returns the checksum of the inode bitmap + pub fn ext4_ialloc_bitmap_csum(&self, bitmap: &[u8]) -> u32 { + let mut csum = 0; + let inodes_per_group = self.inodes_per_group; + let uuid = self.uuid; + csum = ext4_crc32c(EXT4_CRC32_INIT, &uuid, uuid.len() as u32); + csum = ext4_crc32c(csum, bitmap, (inodes_per_group + 7) / 8); + csum + } +} diff --git a/vendor/ext4_rs/src/ext4_impls/balloc.rs b/vendor/ext4_rs/src/ext4_impls/balloc.rs new file mode 100644 index 00000000..15b43e9a --- /dev/null +++ b/vendor/ext4_rs/src/ext4_impls/balloc.rs @@ -0,0 +1,771 @@ +use crate::ext4_defs::*; +use crate::prelude::*; +use crate::return_errno_with_message; +use crate::utils::bitmap::*; +use core::array; + +// Cache for block group information +#[derive(Clone, Copy)] +struct BlockGroupCache { + bitmap: [u8; BLOCK_SIZE], + free_blocks: u64, + last_used_idx: u32, +} + +impl BlockGroupCache { + fn new(bitmap: &[u8], free_blocks: u64) -> Self { + let mut new_bitmap = [0u8; BLOCK_SIZE]; + new_bitmap.copy_from_slice(bitmap); + Self { + bitmap: new_bitmap, + free_blocks, + last_used_idx: 0, + } + } +} + +// Simple fixed-size cache for block groups +struct BlockGroupCacheManager { + caches: [(u32, BlockGroupCache); 8], // Cache for up to 8 block groups + len: usize, +} + +impl BlockGroupCacheManager { + fn new() -> Self { + let empty_cache = BlockGroupCache { + bitmap: [0; BLOCK_SIZE], + free_blocks: 0, + last_used_idx: 0, + }; + Self { + caches: array::from_fn(|_| (0, empty_cache)), + len: 0, + } + } + + fn get(&mut self, bgid: u32) -> Option<&mut BlockGroupCache> { + for i in 0..self.len { + if self.caches[i].0 == bgid { + return Some(&mut self.caches[i].1); + } + } + None + } + + fn insert(&mut self, bgid: u32, cache: BlockGroupCache) { + if self.len < 8 { + self.caches[self.len] = (bgid, cache); + self.len += 1; + } else { + // Simple LRU: remove the first entry and shift others + for i in 0..self.len-1 { + self.caches[i] = self.caches[i+1]; + } + self.caches[self.len-1] = (bgid, cache); + } + } + + fn iter_caches(&self) -> impl Iterator { + self.caches[..self.len].iter() + } +} + +impl Ext4 { + /// Compute number of block group from block address. + /// + /// Params: + /// + /// `baddr` - Absolute address of block. + /// + /// # Returns + /// `u32` - Block group index + pub fn get_bgid_of_block(&self, baddr: u64) -> u32 { + let mut baddr = baddr; + if self.super_block.first_data_block() != 0 && baddr != 0 { + baddr -= 1; + } + (baddr / self.super_block.blocks_per_group() as u64) as u32 + } + + /// Compute the starting block address of a block group. + /// + /// Params: + /// `bgid` - Block group index + /// + /// Returns: + /// `u64` - Block address + pub fn get_block_of_bgid(&self, bgid: u32) -> u64 { + let mut baddr = 0; + if self.super_block.first_data_block() != 0 { + baddr += 1; + } + baddr + bgid as u64 * self.super_block.blocks_per_group() as u64 + } + + /// Convert block address to relative index in block group. + /// + /// Params: + /// `baddr` - Block number to convert. + /// + /// Returns: + /// `u32` - Relative number of block. + pub fn addr_to_idx_bg(&self, baddr: u64) -> u32 { + let mut baddr = baddr; + if self.super_block.first_data_block() != 0 && baddr != 0 { + baddr -= 1; + } + (baddr % self.super_block.blocks_per_group() as u64) as u32 + } + + /// Convert relative block address in group to absolute address. + /// + /// # Arguments + /// + /// * `index` - Relative block address. + /// * `bgid` - Block group. + /// + /// # Returns + /// + /// * `Ext4Fsblk` - Absolute block address. + pub fn bg_idx_to_addr(&self, index: u32, bgid: u32) -> Ext4Fsblk { + let mut index = index; + if self.super_block.first_data_block() != 0 { + index += 1; + } + (self.super_block.blocks_per_group() as u64 * bgid as u64) + index as u64 + } + + + /// Allocate a new block. + /// + /// Params: + /// `inode_ref` - Reference to the inode. + /// `goal` - Absolute address of the block. + /// + /// Returns: + /// `Result` - The physical block number allocated. + pub fn balloc_alloc_block( + &self, + inode_ref: &mut Ext4InodeRef, + goal: Option, + ) -> Result { + let mut alloc: Ext4Fsblk = 0; + let super_block = &self.super_block; + let blocks_per_group = super_block.blocks_per_group(); + let mut bgid; + let mut idx_in_bg; + + if let Some(goal) = goal { + bgid = self.get_bgid_of_block(goal); + idx_in_bg = self.addr_to_idx_bg(goal); + } else { + bgid = 1; + idx_in_bg = 0; + } + + let block_group_count = super_block.block_group_count(); + let mut count = block_group_count; + + while count > 0 { + // Load block group reference + let mut block_group = + Ext4BlockGroup::load_new(self.block_device.clone(), super_block, bgid as usize); + + let free_blocks = block_group.get_free_blocks_count(); + if free_blocks == 0 { + // Try next block group + bgid = (bgid + 1) % block_group_count; + count -= 1; + + if count == 0 { + log::trace!("No free blocks available in all block groups"); + return_errno_with_message!(Errno::ENOSPC, "No free blocks available in all block groups"); + } + continue; + } + + // Compute indexes + let first_in_bg = self.get_block_of_bgid(bgid); + let first_in_bg_index = self.addr_to_idx_bg(first_in_bg); + + if idx_in_bg < first_in_bg_index { + idx_in_bg = first_in_bg_index; + } + + // Load block with bitmap + let bmp_blk_adr = block_group.get_block_bitmap_block(super_block); + let mut bitmap_block = + Block::load(self.block_device.clone(), bmp_blk_adr as usize * BLOCK_SIZE); + + // Check if goal is free + if ext4_bmap_is_bit_clr(&bitmap_block.data, idx_in_bg) { + let block_num = self.bg_idx_to_addr(idx_in_bg, bgid); + if self.is_system_reserved_block(block_num, bgid) { + // 跳过 system zone + } else { + ext4_bmap_bit_set(&mut bitmap_block.data, idx_in_bg); + block_group.set_block_group_balloc_bitmap_csum(super_block, &bitmap_block.data); + self.block_device + .write_offset(bmp_blk_adr as usize * BLOCK_SIZE, &bitmap_block.data); + alloc = self.bg_idx_to_addr(idx_in_bg, bgid); + + /* Update free block counts */ + self.update_free_block_counts(inode_ref, &mut block_group, bgid as usize)?; + return Ok(alloc); + } + } + + // Try to find free block near to goal + let blk_in_bg = blocks_per_group; + let end_idx = min((idx_in_bg + 63) & !63, blk_in_bg); + + for tmp_idx in (idx_in_bg + 1)..end_idx { + if ext4_bmap_is_bit_clr(&bitmap_block.data, tmp_idx) { + // Check if this is a system reserved block + let block_num = self.bg_idx_to_addr(tmp_idx, bgid); + if self.is_system_reserved_block(block_num, bgid) { + continue; + } + + ext4_bmap_bit_set(&mut bitmap_block.data, tmp_idx); + block_group.set_block_group_balloc_bitmap_csum(super_block, &bitmap_block.data); + self.block_device + .write_offset(bmp_blk_adr as usize * BLOCK_SIZE, &bitmap_block.data); + alloc = self.bg_idx_to_addr(tmp_idx, bgid); + self.update_free_block_counts(inode_ref, &mut block_group, bgid as usize)?; + return Ok(alloc); + } + } + + // Find free bit in bitmap + let mut rel_blk_idx = 0; + if ext4_bmap_bit_find_clr(&bitmap_block.data, idx_in_bg, blk_in_bg, &mut rel_blk_idx) { + // Check if this is a system reserved block + let block_num = self.bg_idx_to_addr(rel_blk_idx, bgid); + if !self.is_system_reserved_block(block_num, bgid) { + ext4_bmap_bit_set(&mut bitmap_block.data, rel_blk_idx); + block_group.set_block_group_balloc_bitmap_csum(super_block, &bitmap_block.data); + self.block_device + .write_offset(bmp_blk_adr as usize * BLOCK_SIZE, &bitmap_block.data); + alloc = self.bg_idx_to_addr(rel_blk_idx, bgid); + self.update_free_block_counts(inode_ref, &mut block_group, bgid as usize)?; + return Ok(alloc); + } + } + + // No free block found in this group, try other block groups + bgid = (bgid + 1) % block_group_count; + count -= 1; + } + + return_errno_with_message!(Errno::ENOSPC, "No free blocks available in all block groups"); + } + + /// Allocate a new block start from a specific bgid + /// + /// Params: + /// `inode_ref` - Reference to the inode. + /// `start_bgid` - Start bgid of free block search + /// + /// Returns: + /// `Result` - The physical block number allocated. + pub fn balloc_alloc_block_from( + &self, + inode_ref: &mut Ext4InodeRef, + start_bgid: &mut u32, + ) -> Result { + let mut alloc: Ext4Fsblk = 0; + let super_block = &self.super_block; + let blocks_per_group = super_block.blocks_per_group(); + // Maximum number of blocks that can be represented by a bitmap block + let max_blocks_in_bitmap = BLOCK_SIZE * 8; + + let mut bgid = *start_bgid; + let mut idx_in_bg = 0; + + let block_group_count = super_block.block_group_count(); + let mut count = block_group_count; + + while count > 0 { + // Load block group reference + let mut block_group = + Ext4BlockGroup::load_new(self.block_device.clone(), super_block, bgid as usize); + + let free_blocks = block_group.get_free_blocks_count(); + if free_blocks == 0 { + // Try next block group + bgid = (bgid + 1) % block_group_count; + count -= 1; + + if count == 0 { + log::trace!("No free blocks available in all block groups"); + return_errno_with_message!(Errno::ENOSPC, "No free blocks available in all block groups"); + } + continue; + } + + // Compute indexes + let first_in_bg = self.get_block_of_bgid(bgid); + let first_in_bg_index = self.addr_to_idx_bg(first_in_bg); + + if idx_in_bg < first_in_bg_index { + idx_in_bg = first_in_bg_index; + } + + // Ensure idx_in_bg doesn't exceed bitmap size + if idx_in_bg >= max_blocks_in_bitmap as u32 { + // Try next block group if we've reached the end of this bitmap + bgid = (bgid + 1) % block_group_count; + count -= 1; + idx_in_bg = 0; + continue; + } + + // Load block with bitmap + let bmp_blk_adr = block_group.get_block_bitmap_block(super_block); + let mut bitmap_block = + Block::load(self.block_device.clone(), bmp_blk_adr as usize * BLOCK_SIZE); + + // Check if goal is free + if ext4_bmap_is_bit_clr(&bitmap_block.data, idx_in_bg) { + ext4_bmap_bit_set(&mut bitmap_block.data, idx_in_bg); + block_group.set_block_group_balloc_bitmap_csum(super_block, &bitmap_block.data); + self.block_device + .write_offset(bmp_blk_adr as usize * BLOCK_SIZE, &bitmap_block.data); + alloc = self.bg_idx_to_addr(idx_in_bg, bgid); + + /* Update free block counts */ + self.update_free_block_counts(inode_ref, &mut block_group, bgid as usize)?; + + *start_bgid = bgid; + return Ok(alloc); + } + + // Try to find free block near to goal + let end_idx = min((idx_in_bg + 63) & !63, max_blocks_in_bitmap as u32); + + for tmp_idx in (idx_in_bg + 1)..end_idx { + if ext4_bmap_is_bit_clr(&bitmap_block.data, tmp_idx) { + // Check if this is a system reserved block + let block_num = self.bg_idx_to_addr(tmp_idx, bgid); + if self.is_system_reserved_block(block_num, bgid) { + continue; + } + + ext4_bmap_bit_set(&mut bitmap_block.data, tmp_idx); + block_group.set_block_group_balloc_bitmap_csum(super_block, &bitmap_block.data); + self.block_device + .write_offset(bmp_blk_adr as usize * BLOCK_SIZE, &bitmap_block.data); + alloc = self.bg_idx_to_addr(tmp_idx, bgid); + self.update_free_block_counts(inode_ref, &mut block_group, bgid as usize)?; + + *start_bgid = bgid; + return Ok(alloc); + } + } + + // Find free bit in bitmap + let mut rel_blk_idx = 0; + if ext4_bmap_bit_find_clr(&bitmap_block.data, idx_in_bg, max_blocks_in_bitmap as u32, &mut rel_blk_idx) { + // Check if this is a system reserved block + let block_num = self.bg_idx_to_addr(rel_blk_idx, bgid); + if !self.is_system_reserved_block(block_num, bgid) { + ext4_bmap_bit_set(&mut bitmap_block.data, rel_blk_idx); + block_group.set_block_group_balloc_bitmap_csum(super_block, &bitmap_block.data); + self.block_device + .write_offset(bmp_blk_adr as usize * BLOCK_SIZE, &bitmap_block.data); + alloc = self.bg_idx_to_addr(rel_blk_idx, bgid); + self.update_free_block_counts(inode_ref, &mut block_group, bgid as usize)?; + + *start_bgid = bgid; + return Ok(alloc); + } + } + + // No free block found in this group, try other block groups + bgid = (bgid + 1) % block_group_count; + count -= 1; + idx_in_bg = 0; + } + + return_errno_with_message!(Errno::ENOSPC, "No free blocks available in all block groups"); + } + + fn update_free_block_counts( + &self, + inode_ref: &mut Ext4InodeRef, + block_group: &mut Ext4BlockGroup, + bgid: usize, + ) -> Result<()> { + let mut super_block = self.super_block; + let block_size = BLOCK_SIZE as u64; + + // Update superblock free blocks count + let mut super_blk_free_blocks = super_block.free_blocks_count(); + super_blk_free_blocks -= 1; + super_block.set_free_blocks_count(super_blk_free_blocks); + super_block.sync_to_disk_with_csum(self.block_device.clone()); + + // Update inode blocks (different block size!) count + let mut inode_blocks = inode_ref.inode.blocks_count(); + inode_blocks += block_size / EXT4_INODE_BLOCK_SIZE as u64; + inode_ref.inode.set_blocks_count(inode_blocks); + self.write_back_inode(inode_ref); + + // Update block group free blocks count + let mut fb_cnt = block_group.get_free_blocks_count(); + fb_cnt -= 1; + block_group.set_free_blocks_count(fb_cnt as u32); + block_group.sync_to_disk_with_csum(self.block_device.clone(), bgid, &super_block); + + Ok(()) + } + + #[allow(unused)] + pub fn balloc_free_blocks(&self, inode_ref: &mut Ext4InodeRef, start: Ext4Fsblk, count: u32) { + // log::trace!("balloc_free_blocks start {:x?} count {:x?}", start, count); + let mut count = count as usize; + let mut start = start; + + let mut super_block = self.super_block; + + let blocks_per_group = super_block.blocks_per_group(); + + let bgid = start / blocks_per_group as u64; + + let mut bg_first = start / blocks_per_group as u64; + let mut bg_last = (start + count as u64 - 1) / blocks_per_group as u64; + + while bg_first <= bg_last { + let idx_in_bg = start % blocks_per_group as u64; + + let mut bg = + Ext4BlockGroup::load_new(self.block_device.clone(), &super_block, bgid as usize); + + let block_bitmap_block = bg.get_block_bitmap_block(&super_block); + let mut raw_data = self + .block_device + .read_offset(block_bitmap_block as usize * BLOCK_SIZE); + let mut data: &mut Vec = &mut raw_data; + + let mut free_cnt = BLOCK_SIZE * 8 - idx_in_bg as usize; + + if count > free_cnt { + } else { + free_cnt = count; + } + + ext4_bmap_bits_free(data, idx_in_bg as u32, free_cnt as u32); + + count -= free_cnt; + start += free_cnt as u64; + + bg.set_block_group_balloc_bitmap_csum(&super_block, data); + self.block_device + .write_offset(block_bitmap_block as usize * BLOCK_SIZE, data); + + /* Update superblock free blocks count */ + let mut super_blk_free_blocks = super_block.free_blocks_count(); + + super_blk_free_blocks += free_cnt as u64; + super_block.set_free_blocks_count(super_blk_free_blocks); + super_block.sync_to_disk_with_csum(self.block_device.clone()); + + /* Update inode blocks (different block size!) count */ + let mut inode_blocks = inode_ref.inode.blocks_count(); + + inode_blocks -= (free_cnt * (BLOCK_SIZE / EXT4_INODE_BLOCK_SIZE)) as u64; + inode_ref.inode.set_blocks_count(inode_blocks); + self.write_back_inode(inode_ref); + + /* Update block group free blocks count */ + let mut fb_cnt = bg.get_free_blocks_count(); + fb_cnt += free_cnt as u64; + bg.set_free_blocks_count(fb_cnt as u32); + bg.sync_to_disk_with_csum(self.block_device.clone(), bgid as usize, &super_block); + + bg_first += 1; + } + } + + + pub fn is_system_reserved_block(&self, block_num: u64, _bgid: u32) -> bool { + + // 如果缓存未初始化,则不判断 + if self.system_zone_cache.is_none() { + return false; + } + // 查缓存 + if let Some(zones) = &self.system_zone_cache { + for zone in zones { + if block_num >= zone.start_blk && block_num <= zone.end_blk { + return true; + } + } + } + false + } + /// Optimized block allocation inspired by lwext4 + /// + /// Params: + /// `inode_ref` - Reference to the inode + /// `start_bgid` - Starting block group ID, will be updated to the last used block group + /// `count` - Number of blocks to allocate + /// + /// Returns: + /// `Result>` - Vector of allocated physical block numbers + pub fn balloc_alloc_block_batch( + &self, + inode_ref: &mut Ext4InodeRef, + start_bgid: &mut u32, + count: usize, + ) -> Result> { + if count == 0 { + return Ok(Vec::new()); + } + + log::debug!("[Block Alloc] Requesting {} blocks starting from bgid {}", count, *start_bgid); + + let super_block = &self.super_block; + let block_group_count = super_block.block_group_count(); + + // Validate inputs + if block_group_count == 0 { + log::error!("[Block Alloc] Invalid block group count: 0"); + return return_errno_with_message!(Errno::EINVAL, "Invalid block group count"); + } + + if *start_bgid >= block_group_count { + log::warn!("[Block Alloc] Invalid start_bgid {}, resetting to 0", *start_bgid); + *start_bgid = 0; + } + + let mut bgid = *start_bgid; + let mut result = Vec::with_capacity(count); + let mut remaining = count; + + // Search through all block groups + let mut groups_checked = 0; + + while remaining > 0 && groups_checked < block_group_count { + // Load block group reference + let mut block_group = + Ext4BlockGroup::load_new(self.block_device.clone(), super_block, bgid as usize); + + // Check if this group has free blocks + let free_blocks = block_group.get_free_blocks_count(); + if free_blocks == 0 { + log::debug!("[Block Alloc] Block group {} has no free blocks", bgid); + bgid = (bgid + 1) % block_group_count; + groups_checked += 1; + continue; + } + + // Get block bitmap for this group + let bmp_blk_adr = block_group.get_block_bitmap_block(super_block); + let mut bitmap_data = + self.block_device.read_offset(bmp_blk_adr as usize * BLOCK_SIZE); + + // Compute indexes and limits + let first_in_bg = self.get_block_of_bgid(bgid); + let first_in_bg_index = self.addr_to_idx_bg(first_in_bg); + let idx_in_bg = first_in_bg_index; // Start from the beginning of the group + let blocks_per_group = super_block.blocks_per_group(); + + // Find free blocks in bitmap + let mut found_blocks = 0; + let max_to_find = core::cmp::min(remaining, free_blocks as usize); + let mut rel_blk_idx = 0; + let mut current_idx = idx_in_bg; + + // First try to find blocks in a simple loop starting from current_idx + while found_blocks < max_to_find && current_idx < blocks_per_group { + // Ensure we don't go beyond bitmap size (BLOCK_SIZE * 8 bits) + if current_idx >= BLOCK_SIZE as u32 * 8 { + break; + } + + if ext4_bmap_is_bit_clr(&bitmap_data, current_idx) { + // Check if this is a system reserved block + let block_num = self.bg_idx_to_addr(current_idx, bgid); + if self.is_system_reserved_block(block_num, bgid) { + log::error!("[Block Alloc] System reserved block found at {:x?}", block_num); + current_idx += 1; + continue; + } + + // Found a free block + ext4_bmap_bit_set(&mut bitmap_data, current_idx); + + // Calculate physical block address + let block_num = self.bg_idx_to_addr(current_idx, bgid); + + // Add to result + result.push(block_num); + found_blocks += 1; + + // For debugging continuity issues + if result.len() > 1 { + let prev_block = result[result.len() - 2]; + if block_num != prev_block + 1 { + log::debug!("[Block Alloc] Non-contiguous blocks: prev={}, current={}, diff={}", + prev_block, block_num, block_num - prev_block); + } + } + } + + current_idx += 1; + } + + // If we didn't find enough blocks using sequential search, use bitmap search function + if found_blocks < max_to_find { + let mut start_idx = current_idx; + + while found_blocks < max_to_find { + // Make sure we don't exceed the bitmap size + let end_idx = core::cmp::min(blocks_per_group, BLOCK_SIZE as u32 * 8); + + // Find next clear bit + if !ext4_bmap_bit_find_clr(&bitmap_data, start_idx, end_idx, &mut rel_blk_idx) { + break; // No more free blocks in this group + } + + // Check if this is a system reserved block + let block_num = self.bg_idx_to_addr(rel_blk_idx, bgid); + if self.is_system_reserved_block(block_num, bgid) { + // Skip this block and continue search + log::error!("[Block Alloc] System reserved block found at {:x?} bgid {}", block_num, bgid); + start_idx = rel_blk_idx + 1; + continue; + } + + ext4_bmap_bit_set(&mut bitmap_data, rel_blk_idx); + + // Calculate physical block address + let block_num = self.bg_idx_to_addr(rel_blk_idx, bgid); + + // Add to result + result.push(block_num); + found_blocks += 1; + + // For debugging continuity issues + if result.len() > 1 { + let prev_block = result[result.len() - 2]; + if block_num != prev_block + 1 { + log::debug!("[Block Alloc] Non-contiguous blocks: prev={}, current={}, diff={}", + prev_block, block_num, block_num - prev_block); + } + } + } + } + + // If we found any blocks, update metadata + if found_blocks > 0 { + // Update bitmap on disk + block_group.set_block_group_balloc_bitmap_csum(super_block, &bitmap_data); + self.block_device.write_offset(bmp_blk_adr as usize * BLOCK_SIZE, &bitmap_data); + + // Update block group free blocks count + let new_free_count = free_blocks - found_blocks as u64; + block_group.set_free_blocks_count(new_free_count as u32); + block_group.sync_to_disk_with_csum(self.block_device.clone(), bgid as usize, super_block); + + // Update superblock free blocks count + let mut sb_copy = *super_block; + let sb_free_blocks = sb_copy.free_blocks_count(); + sb_copy.set_free_blocks_count(sb_free_blocks - found_blocks as u64); + sb_copy.sync_to_disk_with_csum(self.block_device.clone()); + + // Update inode blocks count + let blocks_per_fs_block = BLOCK_SIZE as u64 / EXT4_INODE_BLOCK_SIZE as u64; + let mut inode_blocks = inode_ref.inode.blocks_count(); + inode_blocks += found_blocks as u64 * blocks_per_fs_block; + inode_ref.inode.set_blocks_count(inode_blocks); + + // Decrement remaining blocks to allocate + remaining -= found_blocks; + + log::debug!("[Block Alloc] Allocated {} blocks from bg {}", found_blocks, bgid); + } + + // Try next block group + bgid = (bgid + 1) % block_group_count; + groups_checked += 1; + } + + // Log allocation results + let allocated_count = result.len(); + log::debug!("[Block Alloc] Allocated {}/{} blocks", allocated_count, count); + + // Even if we couldn't allocate all requested blocks, return what we got + if remaining > 0 { + log::warn!("[Block Alloc] Could only allocate {} out of {} blocks. Remaining: {}", + allocated_count, count, remaining); + } + + // Update start_bgid to continue from where we left off next time + *start_bgid = bgid; + + // Write back inode to save block count changes + if allocated_count > 0 { + self.write_back_inode(inode_ref); + } + + Ok(result) + } + + /// Returns the number of meta blocks for a given block group, like Linux ext4_num_base_meta_blocks. + pub fn num_base_meta_blocks(&self, bgid: u32) -> u32 { + let has_super = self.ext4_bg_has_super(bgid); + let gdt_blocks = self.ext4_bg_num_gdb(bgid); + let meta_blocks = if has_super { 1 + gdt_blocks } else { 0 }; + // log::info!( + // "[num_base_meta_blocks] group={} has_super={} gdt_blocks={} meta_blocks={}", + // bgid, has_super, gdt_blocks, meta_blocks + // ); + meta_blocks + } + + /// 判断group是否有superblock备份(与Linux ext4_bg_has_super一致) + pub fn ext4_bg_has_super(&self, group: u32) -> bool { + if group == 0 { + return true; + } + // Linux: group号为3/5/7的幂也有superblock备份 + fn is_power_of(mut n: u32, base: u32) -> bool { + if n < base { return false; } + while n % base == 0 { n /= base; } + n == 1 + } + is_power_of(group, 3) || is_power_of(group, 5) || is_power_of(group, 7) + } + + /// 判断是否有meta_bg特性(与Linux ext4_has_feature_meta_bg一致) + pub fn ext4_has_feature_meta_bg(&self) -> bool { + // EXT4_FEATURE_INCOMPAT_META_BG = 0x0010 + const EXT4_FEATURE_INCOMPAT_META_BG: u32 = 0x0010; + (self.super_block.incompat_features() & EXT4_FEATURE_INCOMPAT_META_BG) != 0 + } + + /// 返回该group的GDT blocks数(与Linux ext4_bg_num_gdb一致) + pub fn ext4_bg_num_gdb(&self, group: u32) -> u32 { + let sb = &self.super_block; + let group_count = sb.block_group_count(); + let block_size = sb.block_size(); + let desc_size = sb.desc_size() as u32; + let reserved_gdt_blocks = sb.reserved_gdt_blocks() as u32; + let desc_blocks = ((group_count as u64 * desc_size as u64 + block_size as u64 - 1) / block_size as u64) as u32; + + if !self.ext4_bg_has_super(group) { + return 0; + } + if group == 0 { + return desc_blocks + reserved_gdt_blocks; + } + if self.ext4_has_feature_meta_bg() { + 1 + } else { + desc_blocks + reserved_gdt_blocks + } + } +} diff --git a/vendor/ext4_rs/src/ext4_impls/dir.rs b/vendor/ext4_rs/src/ext4_impls/dir.rs new file mode 100644 index 00000000..6648c1a6 --- /dev/null +++ b/vendor/ext4_rs/src/ext4_impls/dir.rs @@ -0,0 +1,438 @@ +use crate::prelude::*; +use crate::return_errno_with_message; + +use crate::ext4_defs::*; + +impl Ext4 { + /// Find a directory entry in a directory + /// + /// Params: + /// parent_inode: u32 - inode number of the parent directory + /// name: &str - name of the entry to find + /// result: &mut Ext4DirSearchResult - result of the search + /// + /// Returns: + /// `Result` - status of the search + pub fn dir_find_entry( + &self, + parent_inode: u32, + name: &str, + result: &mut Ext4DirSearchResult, + ) -> Result { + // load parent inode + let parent = self.get_inode_ref(parent_inode); + assert!(parent.inode.is_dir()); + + // start from the first logical block + let mut iblock = 0; + // physical block id + let mut fblock: Ext4Fsblk = 0; + + // calculate total blocks + let inode_size: u64 = parent.inode.size(); + let total_blocks: u64 = inode_size / BLOCK_SIZE as u64; + + // iterate all blocks + while iblock < total_blocks { + let search_path = self.find_extent(&parent, iblock as u32); + + if let Ok(path) = search_path { + // get the last path + let path = path.path.last().unwrap(); + + // get physical block id + fblock = path.pblock; + + // load physical block + let mut ext4block = + Block::load(self.block_device.clone(), fblock as usize * BLOCK_SIZE); + + // find entry in block + let r = self.dir_find_in_block(&ext4block, name, result); + + if r.is_ok() { + result.pblock_id = fblock as usize; + return Ok(EOK); + } + } else { + return_errno_with_message!(Errno::ENOENT, "dir search fail") + } + // go to next block + iblock += 1 + } + + return_errno_with_message!(Errno::ENOENT, "dir search fail"); + } + + /// Find a directory entry in a block + /// + /// Params: + /// block: &mut Block - block to search in + /// name: &str - name of the entry to find + /// + /// Returns: + /// result: Ext4DirEntry - result of the search + pub fn dir_find_in_block( + &self, + block: &Block, + name: &str, + result: &mut Ext4DirSearchResult, + ) -> Result { + let mut offset = 0; + let mut prev_de_offset = 0; + + // start from the first entry + while offset < BLOCK_SIZE - core::mem::size_of::() { + let de: Ext4DirEntry = block.read_offset_as(offset); + if !de.unused() && de.compare_name(name) { + result.dentry = de; + result.offset = offset; + result.prev_offset = prev_de_offset; + return Ok(de); + } + + prev_de_offset = offset; + // go to next entry + offset += de.entry_len() as usize; + } + return_errno_with_message!(Errno::ENOENT, "dir find in block failed"); + } + + /// Get dir entries of a inode + /// + /// Params: + /// inode: u32 - inode number of the directory + /// + /// Returns: + /// `Vec` - list of directory entries + pub fn dir_get_entries(&self, inode: u32) -> Vec { + let mut entries = Vec::new(); + + // load inode + let inode_ref = self.get_inode_ref(inode); + assert!(inode_ref.inode.is_dir()); + + // calculate total blocks + let inode_size = inode_ref.inode.size(); + let total_blocks = inode_size / BLOCK_SIZE as u64; + + // start from the first logical block + let mut iblock = 0; + + // iterate all blocks + while iblock < total_blocks { + // get physical block id of a logical block id + let search_path = self.find_extent(&inode_ref, iblock as u32); + + if let Ok(path) = search_path { + // get the last path + let path = path.path.last().unwrap(); + + // get physical block id + let fblock = path.pblock; + + // load physical block + let ext4block = + Block::load(self.block_device.clone(), fblock as usize * BLOCK_SIZE); + let mut offset = 0; + + // iterate all entries in a block + while offset < BLOCK_SIZE - core::mem::size_of::() { + let de: Ext4DirEntry = ext4block.read_offset_as(offset); + if !de.unused() { + entries.push(de); + } + offset += de.entry_len() as usize; + } + } + + // go ot next block + iblock += 1; + } + entries + } + + pub fn dir_set_csum(&self, dst_blk: &mut Block, ino_gen: u32) { + let parent_de: Ext4DirEntry = dst_blk.read_offset_as(0); + + let tail_offset = BLOCK_SIZE - size_of::(); + let mut tail: Ext4DirEntryTail = dst_blk.read_offset_as(tail_offset); + + tail.tail_set_csum(&self.super_block, &parent_de, &dst_blk.data[..], ino_gen); + + tail.copy_to_slice(&mut dst_blk.data); + } + + /// Add a new entry to a directory + /// + /// Params: + /// parent: &mut Ext4InodeRef - parent directory inode reference + /// child: &mut Ext4InodeRef - child inode reference + /// path: &str - path of the new entry + /// + /// Returns: + /// `Result` - status of the operation + pub fn dir_add_entry( + &self, + parent: &mut Ext4InodeRef, + child: &Ext4InodeRef, + name: &str, + ) -> Result { + // calculate total blocks + let inode_size: u64 = parent.inode.size(); + let block_size = self.super_block.block_size(); + let total_blocks: u64 = inode_size / block_size as u64; + + // iterate all blocks + let mut iblock = 0; + while iblock < total_blocks { + // get physical block id of a logical block id + let pblock = self.get_pblock_idx(parent, iblock as u32)?; + + // load physical block + let mut ext4block = + Block::load(self.block_device.clone(), pblock as usize * BLOCK_SIZE); + + let result = self.try_insert_to_existing_block(&mut ext4block, name, child.inode_num); + + if result.is_ok() { + // set checksum + self.dir_set_csum(&mut ext4block, parent.inode.generation()); + ext4block.sync_blk_to_disk(self.block_device.clone()); + + return Ok(EOK); + } + + // go ot next block + iblock += 1; + } + + // no space in existing blocks, need to add new block + let new_block = self.append_inode_pblk(parent)?; + + // load new block + let mut new_ext4block = + Block::load(self.block_device.clone(), new_block as usize * BLOCK_SIZE); + + // write new entry to the new block + // must succeed, as we just allocated the block + let de_type = DirEntryType::EXT4_DE_DIR; + self.insert_to_new_block(&mut new_ext4block, child.inode_num, name, de_type); + + // set checksum + self.dir_set_csum(&mut new_ext4block, parent.inode.generation()); + new_ext4block.sync_blk_to_disk(self.block_device.clone()); + + Ok(EOK) + } + + /// Try to insert a new entry to an existing block + /// + /// Params: + /// block: &mut Block - block to insert the new entry + /// name: &str - name of the new entry + /// inode: u32 - inode number of the new entry + /// + /// Returns: + /// `Result` - status of the operation + pub fn try_insert_to_existing_block( + &self, + block: &mut Block, + name: &str, + child_inode: u32, + ) -> Result { + // required length aligned to 4 bytes + let required_len = { + let mut len = size_of::() + name.len(); + if len % 4 != 0 { + len += 4 - (len % 4); + } + len + }; + + let mut offset = 0; + + // Start from the first entry + while offset < BLOCK_SIZE - size_of::() { + let mut de = Ext4DirEntry::try_from(&block.data[offset..]).unwrap(); + + if de.unused() { + continue; + } + + let inode = de.inode; + let rec_len = de.entry_len; + + let used_len = de.name_len as usize; + let mut sz = core::mem::size_of::() + used_len; + if used_len % 4 != 0 { + sz += 4 - used_len % 4; + } + + let free_space = rec_len as usize - sz; + + // If there is enough free space + if free_space >= required_len { + // Create new directory entry + let mut new_entry = Ext4DirEntry::default(); + + // Update existing entry length and copy both entries back to block data + de.entry_len = sz as u16; + + let de_type = DirEntryType::EXT4_DE_DIR; + new_entry.write_entry(free_space as u16, child_inode, name, de_type); + + // update parent_de and new_de to blk_data + de.copy_to_slice(&mut block.data, offset); + new_entry.copy_to_slice(&mut block.data, offset + sz); + + // Sync to disk + block.sync_blk_to_disk(self.block_device.clone()); + + return Ok(EOK); + } + + // Move to the next entry + offset += de.entry_len() as usize; + } + + return_errno_with_message!(Errno::ENOSPC, "No space in block for new entry"); + } + + /// Insert a new entry to a new block + /// + /// Params: + /// block: &mut Block - block to insert the new entry + /// name: &str - name of the new entry + /// inode: u32 - inode number of the new entry + pub fn insert_to_new_block( + &self, + block: &mut Block, + inode: u32, + name: &str, + de_type: DirEntryType, + ) { + // write new entry + let mut new_entry = Ext4DirEntry::default(); + let el = BLOCK_SIZE - size_of::(); + new_entry.write_entry(el as u16, inode, name, de_type); + new_entry.copy_to_slice(&mut block.data, 0); + + copy_dir_entry_to_array(&new_entry, &mut block.data, 0); + + // init tail for new block + let tail = Ext4DirEntryTail::new(); + tail.copy_to_slice(&mut block.data); + } + + pub fn dir_remove_entry(&self, parent: &mut Ext4InodeRef, path: &str) -> Result { + // get remove_entry pos in parent and its prev entry + let mut result = Ext4DirSearchResult::new(Ext4DirEntry::default()); + + let r = self.dir_find_entry(parent.inode_num, path, &mut result)?; + + let mut ext4block = Block::load(self.block_device.clone(), result.pblock_id * BLOCK_SIZE); + + let de_del_entry_len = result.dentry.entry_len(); + + // prev entry + let pde: &mut Ext4DirEntry = ext4block.read_offset_as_mut(result.prev_offset); + + pde.entry_len += de_del_entry_len; + + let de_del: &mut Ext4DirEntry = ext4block.read_offset_as_mut(result.offset); + + de_del.inode = 0; + + self.dir_set_csum(&mut ext4block, parent.inode.generation()); + ext4block.sync_blk_to_disk(self.block_device.clone()); + + Ok(EOK) + } + + pub fn dir_has_entry(&self, dir_inode: u32) -> bool { + // load parent inode + let parent = self.get_inode_ref(dir_inode); + assert!(parent.inode.is_dir()); + + // start from the first logical block + let mut iblock = 0; + // physical block id + let mut fblock: Ext4Fsblk = 0; + + // calculate total blocks + let inode_size: u64 = parent.inode.size(); + let total_blocks: u64 = inode_size / BLOCK_SIZE as u64; + + // iterate all blocks + while iblock < total_blocks { + let search_path = self.find_extent(&parent, iblock as u32); + + if let Ok(path) = search_path { + // get the last path + let path = path.path.last().unwrap(); + + // get physical block id + fblock = path.pblock; + + // load physical block + let ext4block = + Block::load(self.block_device.clone(), fblock as usize * BLOCK_SIZE); + + // start from the first entry + let mut offset = 0; + while offset < BLOCK_SIZE - core::mem::size_of::() { + let de: Ext4DirEntry = ext4block.read_offset_as(offset); + offset += de.entry_len as usize; + if de.inode == 0 { + continue; + } + // skip . and .. + if de.get_name() == "." || de.get_name() == ".." { + continue; + } + return true; + } + } + // go to next block + iblock += 1 + } + + false + } + + pub fn dir_remove(&self, parent: u32, path: &str) -> Result { + let mut search_result = Ext4DirSearchResult::new(Ext4DirEntry::default()); + + let r = self.dir_find_entry(parent, path, &mut search_result)?; + + let mut parent_inode_ref = self.get_inode_ref(parent); + let mut child_inode_ref = self.get_inode_ref(search_result.dentry.inode); + + if self.dir_has_entry(child_inode_ref.inode_num){ + return_errno_with_message!(Errno::ENOTSUP, "rm dir with children not supported") + } + + self.truncate_inode(&mut child_inode_ref, 0)?; + + self.unlink(&mut parent_inode_ref, &mut child_inode_ref, path)?; + + self.write_back_inode(&mut parent_inode_ref); + + // to do + // ext4_inode_set_del_time + // ext4_inode_set_links_cnt + // ext4_fs_free_inode(&child) + + Ok(EOK) + } +} + +pub fn copy_dir_entry_to_array(header: &Ext4DirEntry, array: &mut [u8], offset: usize) { + unsafe { + let de_ptr = header as *const Ext4DirEntry as *const u8; + let array_ptr = array as *mut [u8] as *mut u8; + let count = core::mem::size_of::() / core::mem::size_of::(); + core::ptr::copy_nonoverlapping(de_ptr, array_ptr.add(offset), count); + } +} diff --git a/vendor/ext4_rs/src/ext4_impls/ext4.rs b/vendor/ext4_rs/src/ext4_impls/ext4.rs new file mode 100644 index 00000000..d8ce79e0 --- /dev/null +++ b/vendor/ext4_rs/src/ext4_impls/ext4.rs @@ -0,0 +1,178 @@ +use crate::prelude::*; +use crate::return_errno_with_message; +use crate::utils::*; + +use crate::ext4_defs::*; + +impl Ext4 { + /// 获取system zone缓存 + pub fn get_system_zone(&self) -> Vec { + let mut zones = Vec::new(); + let group_count = self.super_block.block_group_count(); + let inodes_per_group = self.super_block.inodes_per_group(); + let inode_size = self.super_block.inode_size() as u64; + let block_size = self.super_block.block_size() as u64; + for bgid in 0..group_count { + // meta blocks + let meta_blks = self.num_base_meta_blocks(bgid); + if meta_blks != 0 { + let start = self.get_block_of_bgid(bgid); + zones.push(SystemZone { + group: bgid, + start_blk: start, + end_blk: start + meta_blks as u64 - 1, + }); + } + // block group描述符 + let block_group = Ext4BlockGroup::load_new(self.block_device.clone(), &self.super_block, bgid as usize); + // block bitmap + let blk_bmp = block_group.get_block_bitmap_block(&self.super_block); + zones.push(SystemZone { + group: bgid, + start_blk: blk_bmp, + end_blk: blk_bmp, + }); + // inode bitmap + let ino_bmp = block_group.get_inode_bitmap_block(&self.super_block); + zones.push(SystemZone { + group: bgid, + start_blk: ino_bmp, + end_blk: ino_bmp, + }); + // inode table + let ino_tbl = block_group.get_inode_table_blk_num() as u64; + let itb_per_group = ((inodes_per_group as u64 * inode_size + block_size - 1) / block_size) as u64; + zones.push(SystemZone { + group: bgid, + start_blk: ino_tbl, + end_blk: ino_tbl + itb_per_group - 1, + }); + } + zones + } + /// Opens and loads an Ext4 from the `block_device`. + pub fn open(block_device: Arc) -> Self { + // Load the superblock + let block = Block::load(block_device.clone(), SUPERBLOCK_OFFSET); + let super_block: Ext4Superblock = block.read_as(); + + // drop(block); + + let ext4_tmp = Ext4 { + block_device, + super_block, + system_zone_cache: None, + }; + let zones = ext4_tmp.get_system_zone(); + + Ext4 { + system_zone_cache: Some(zones), + ..ext4_tmp + } + } + + // with dir result search path offset + pub fn generic_open( + &self, + path: &str, + parent_inode_num: &mut u32, + create: bool, + ftype: u16, + name_off: &mut u32, + ) -> Result { + let mut is_goal = false; + + let mut parent = parent_inode_num; + + let mut search_path = path; + + let mut dir_search_result = Ext4DirSearchResult::new(Ext4DirEntry::default()); + + loop { + while search_path.starts_with('/') { + *name_off += 1; // Skip the slash + search_path = &search_path[1..]; + } + + let len = path_check(search_path, &mut is_goal); + + let current_path = &search_path[..len]; + + if len == 0 || search_path.is_empty() { + break; + } + + search_path = &search_path[len..]; + + let r = self.dir_find_entry(*parent, current_path, &mut dir_search_result); + + // log::trace!("find in parent {:x?} r {:?} name {:?}", parent, r, current_path); + if let Err(e) = r { + if e.error() != Errno::ENOENT || !create { + return_errno_with_message!(Errno::ENOENT, "No such file or directory"); + } + + let mut inode_mode = 0; + if is_goal { + inode_mode = ftype; + } else { + inode_mode = InodeFileType::S_IFDIR.bits(); + } + + let new_inode_ref = self.create(*parent, current_path, inode_mode)?; + + // Update parent to the new inode + *parent = new_inode_ref.inode_num; + + // Now, update dir_search_result to reflect the new inode + dir_search_result.dentry.inode = new_inode_ref.inode_num; + + continue; + } + + if is_goal { + break; + } else { + // update parent + *parent = dir_search_result.dentry.inode; + } + *name_off += len as u32; + } + + if is_goal { + return Ok(dir_search_result.dentry.inode); + } + + Ok(dir_search_result.dentry.inode) + } + + #[allow(unused)] + pub fn dir_mk(&self, path: &str) -> Result { + let mut nameoff = 0; + + let filetype = InodeFileType::S_IFDIR; + + // todo get this path's parent + + // start from root + let mut parent = ROOT_INODE; + + let r = self.generic_open(path, &mut parent, true, filetype.bits(), &mut nameoff); + Ok(EOK) + } + + pub fn unlink( + &self, + parent: &mut Ext4InodeRef, + child: &mut Ext4InodeRef, + name: &str, + ) -> Result { + self.dir_remove_entry(parent, name)?; + + let is_dir = child.inode.is_dir(); + + self.ialloc_free_inode(child.inode_num, is_dir); + + Ok(EOK) + } +} diff --git a/vendor/ext4_rs/src/ext4_impls/extents.rs b/vendor/ext4_rs/src/ext4_impls/extents.rs new file mode 100644 index 00000000..cf7e7e6f --- /dev/null +++ b/vendor/ext4_rs/src/ext4_impls/extents.rs @@ -0,0 +1,1255 @@ +use crate::prelude::*; +use crate::return_errno_with_message; +use crate::ext4_defs::*; +use alloc::format; +use core::mem::size_of; +use crate::utils::crc::*; + + +impl Ext4 { + /// Find an extent in the extent tree. + /// + /// Params: + /// inode_ref: &Ext4InodeRef - inode reference + /// lblock: Ext4Lblk - logical block id + /// + /// Returns: + /// `Result` - search path + /// + /// If depth > 0, search for the extent_index that corresponds to the target lblock. + /// If depth = 0, directly search for the extent in the root node that corresponds to the target lblock. + pub fn find_extent(&self, inode_ref: &Ext4InodeRef, lblock: Ext4Lblk) -> Result { + let mut search_path = SearchPath::new(); + + // Load the root node + let root_data: &[u8; 60] = + unsafe { core::mem::transmute::<&[u32; 15], &[u8; 60]>(&inode_ref.inode.block) }; + let mut node = ExtentNode::load_from_data(root_data, true).unwrap(); + + let mut depth = node.header.depth; + + // Traverse down the tree if depth > 0 + let mut pblock_of_node = 0; + while depth > 0 { + let index_pos = node.binsearch_idx(lblock); + if let Some(pos) = index_pos { + let index = node.get_index(pos)?; + let next_block = index.leaf_lo; + + search_path.path.push(ExtentPathNode { + header: node.header, + index: Some(index), + extent: None, + position: pos, + pblock: next_block as u64, + pblock_of_node, + }); + + let next_block = search_path.path.last().unwrap().index.unwrap().leaf_lo; + let mut next_data = self + .block_device + .read_offset(next_block as usize * BLOCK_SIZE); + node = ExtentNode::load_from_data_mut(&mut next_data, false)?; + depth -= 1; + search_path.depth += 1; + pblock_of_node = next_block as usize; + } else { + return_errno_with_message!(Errno::ENOENT, "Extentindex not found"); + } + } + + // Handle the case where depth is 0 + if let Some((extent, pos)) = node.binsearch_extent(lblock) { + search_path.path.push(ExtentPathNode { + header: node.header, + index: None, + extent: Some(extent), + position: pos, + pblock: lblock as u64 - extent.get_first_block() as u64 + extent.get_pblock(), + pblock_of_node, + }); + search_path.maxdepth = node.header.depth; + + Ok(search_path) + } else { + search_path.path.push(ExtentPathNode { + header: node.header, + index: None, + extent: None, + position: 0, + pblock: 0, + pblock_of_node, + }); + Ok(search_path) + } + } + + /// Insert an extent into the extent tree. + pub fn insert_extent( + &self, + inode_ref: &mut Ext4InodeRef, + newex: &mut Ext4Extent, + ) -> Result<()> { + let newex_first_block = newex.first_block; + log::info!("[insert_extent] Starting - Inserting extent at block {}", newex_first_block); + log::info!("[insert_extent] Current tree state: magic={:x}, entries={}, max={}, depth={}", + inode_ref.inode.root_extent_header().magic, + inode_ref.inode.root_extent_header().entries_count, + inode_ref.inode.root_extent_header().max_entries_count, + inode_ref.inode.root_extent_header().depth); + + let mut search_path = self.find_extent(inode_ref, newex_first_block)?; + + let depth = search_path.depth as usize; + let node = &search_path.path[depth]; // Get the node at the current depth + + let at_root = node.pblock_of_node == 0; + let header = node.header; + + // Node is empty (no extents) + if header.entries_count == 0 { + log::info!("[insert_extent] Node is empty, inserting directly"); + self.insert_new_extent(inode_ref, &mut search_path, newex)?; + return Ok(()); + } + + // Insert to exsiting extent + if let Some(mut ex) = node.extent { + let pos = node.position; + let last_extent_pos = header.entries_count as usize - 1; + + // Try to Insert to found_ext + // found_ext: |<---found_ext--->| |<---ext2--->| + // 20 30 50 60 + // insert: |<---found_ext---><---newex--->| |<---ext2--->| + // 20 30 40 50 60 + // merge: |<---newex--->| |<---ext2--->| + // 20 40 50 60 + if self.can_merge(&ex, newex) { + self.merge_extent(&search_path, &mut ex, newex)?; + + if at_root { + // we are at root + *inode_ref.inode.root_extent_mut_at(node.position) = ex; + } + return Ok(()); + } + + // Insert right + // found_ext: |<---found_ext--->| |<---next_extent--->| + // 10 20 30 40 + // insert: |<---found_ext--->|<---newex---><---next_extent--->| + // 10 20 30 40 + // merge: |<---found_ext--->|<---newex--->| + // 10 20 40 + if pos < last_extent_pos + && ((ex.first_block + ex.block_count as u32) < newex.first_block) + { + if let Ok(next_extent) = self.get_extent_from_node(node, pos + 1) { + if self.can_merge(&next_extent, newex) { + self.merge_extent(&search_path, newex, &next_extent)?; + return Ok(()); + } + } + } + + // Insert left + // found_ext: |<---found_ext--->| |<---ext2--->| + // 20 30 40 50 + // insert: |<---prev_extent---><---newex--->|<---found_ext--->|....|<---ext2--->| + // 0 10 20 30 40 50 + // merge: |<---newex--->|<---found_ext--->|....|<---ext2--->| + // 0 20 30 40 50 + if pos > 0 && (newex.first_block + newex.block_count as u32) < ex.first_block { + if let Ok(mut prev_extent) = self.get_extent_from_node(node, pos - 1) { + if self.can_merge(&prev_extent, newex) { + self.merge_extent(&search_path, &mut prev_extent, newex)?; + return Ok(()); + } + } + } + } + + // Check if there's space to insert the new extent + // full full + // Before: |<---ext1--->|<---ext2--->| + // 10 20 30 + + // full full + // insert: |<---ext1--->|<---ext2--->|<---newex--->| + // 10 20 30 35 + if header.entries_count < header.max_entries_count { + log::info!("[insert_extent] Node has space, inserting new extent"); + self.insert_new_extent(inode_ref, &mut search_path, newex)?; + } else { + log::info!("[insert_extent] Node is full (entries={}, max={}), creating new leaf", + header.entries_count, header.max_entries_count); + self.create_new_leaf(inode_ref, &mut search_path, newex)?; + } + + log::info!("[insert_extent] Completed - Final tree state: magic={:x}, entries={}, max={}, depth={}", + inode_ref.inode.root_extent_header().magic, + inode_ref.inode.root_extent_header().entries_count, + inode_ref.inode.root_extent_header().max_entries_count, + inode_ref.inode.root_extent_header().depth); + + Ok(()) + } + + /// Get extent from the node at the given position. + fn get_extent_from_node(&self, node: &ExtentPathNode, pos: usize) -> Result { + let data = self + .block_device + .read_offset(node.pblock as usize * BLOCK_SIZE); + let extent_node = ExtentNode::load_from_data(&data, false).unwrap(); + + match extent_node.get_extent(pos) { + Some(extent) => Ok(extent), + None => return_errno_with_message!(Errno::EINVAL, "Failed to get extent from node"), + } + } + + /// Get index from the node at the given position. + fn get_index_from_node(&self, node: &ExtentPathNode, pos: usize) -> Result { + let data = self + .block_device + .read_offset(node.pblock as usize * BLOCK_SIZE); + let extent_node = ExtentNode::load_from_data(&data, false).unwrap(); + + extent_node.get_index(pos) + } + + + /// Check if two extents can be merged. + /// + /// This function determines whether two extents, `ex1` and `ex2`, can be merged + /// into a single extent. Extents are contiguous ranges of blocks in the ext4 + /// filesystem that map logical block numbers to physical block numbers. + /// + /// # Arguments + /// + /// * `ex1` - The first extent to check. + /// * `ex2` - The second extent to check. + /// + /// # Returns + /// + /// * `true` if the extents can be merged. + /// * `false` otherwise. + /// + /// # Merge Conditions + /// + /// 1. **Same Unwritten State**: + /// - The `is_unwritten` state of both extents must be the same. + /// - Unwritten extents are placeholders for blocks that are allocated but not initialized. + /// + /// 2. **Contiguous Block Ranges**: + /// - The logical block range of the first extent must immediately precede + /// the logical block range of the second extent. + /// + /// 3. **Maximum Length**: + /// - The total length of the merged extent must not exceed the maximum allowed + /// extent length (`EXT_INIT_MAX_LEN`). + /// - If the extents are unwritten, the total length must also not exceed + /// the maximum length for unwritten extents (`EXT_UNWRITTEN_MAX_LEN`). + /// + /// 4. **Contiguous Physical Blocks**: + /// - The physical block range of the first extent must immediately precede + /// the physical block range of the second extent. This ensures that the + /// physical storage is contiguous. + fn can_merge(&self, ex1: &Ext4Extent, ex2: &Ext4Extent) -> bool { + // Check if the extents have the same unwritten state + if ex1.is_unwritten() != ex2.is_unwritten() { + return false; + } + let ext1_ee_len = ex1.get_actual_len() as usize; + let ext2_ee_len = ex2.get_actual_len() as usize; + + // Check if the block ranges are contiguous + if ex1.first_block + ext1_ee_len as u32 != ex2.first_block { + return false; + } + + // Check if the merged length would exceed the maximum allowed length + if ext1_ee_len + ext2_ee_len > EXT_INIT_MAX_LEN as usize{ + return false; + } + + // Check if the physical blocks are contiguous + if ex1.get_pblock() + ext1_ee_len as u64 == ex2.get_pblock() { + return true; + } + false + } + + + fn merge_extent( + &self, + search_path: &SearchPath, + left_ext: &mut Ext4Extent, + right_ext: &Ext4Extent, + ) -> Result<()> { + let depth = search_path.depth as usize; + + log::info!("[merge_extent] Merging extents at depth {}", depth); + log::info!("[merge_extent] Left extent: logical block {}, physical block {}, length {}", + left_ext.first_block, left_ext.get_pblock(), left_ext.get_actual_len()); + log::info!("[merge_extent] Right extent: logical block {}, physical block {}, length {}", + right_ext.first_block, right_ext.get_pblock(), right_ext.get_actual_len()); + + let unwritten = left_ext.is_unwritten(); + let len = left_ext.get_actual_len() + right_ext.get_actual_len(); + left_ext.set_actual_len(len); + if unwritten { + left_ext.mark_unwritten(); + } + let header = search_path.path[depth].header; + + log::info!("[merge_extent] Merged extent: logical block {}, physical block {}, new length {}", + left_ext.first_block, left_ext.get_pblock(), left_ext.get_actual_len()); + + if header.max_entries_count > 4 { + let node = &search_path.path[depth]; + let block = node.pblock_of_node; + let new_ex_offset = core::mem::size_of::() + core::mem::size_of::() * (node.position); + let mut ext4block = Block::load(self.block_device.clone(), block * BLOCK_SIZE); + let left_ext:&mut Ext4Extent = ext4block.read_offset_as_mut(new_ex_offset); + + let unwritten = left_ext.is_unwritten(); + let len = left_ext.get_actual_len() + right_ext.get_actual_len(); + left_ext.set_actual_len(len); + if unwritten { + left_ext.mark_unwritten(); + } + + log::info!("[merge_extent] Updated on-disk extent: logical block {}, physical block {}, length {}", + left_ext.first_block, left_ext.get_pblock(), left_ext.get_actual_len()); + + ext4block.sync_blk_to_disk(self.block_device.clone()); + log::info!("[merge_extent] Synced merged extent to disk"); + } + + Ok(()) + } + + fn insert_new_extent( + &self, + inode_ref: &mut Ext4InodeRef, + search_path: &mut SearchPath, + new_extent: &mut Ext4Extent, + ) -> Result<()> { + let depth = search_path.depth as usize; + let node = &mut search_path.path[depth]; // Get the node at the current depth + let header = node.header; + + log::info!("[insert_new_extent] Inserting extent at depth {}: logical block {}, physical block {}, length {}", + depth, new_extent.first_block, new_extent.get_pblock(), new_extent.get_actual_len()); + log::info!("[insert_new_extent] Node info: entries={}, max={}, position={}", + header.entries_count, header.max_entries_count, node.position); + + log::debug!("[insert_new_extent] New extent details:"); + log::debug!(" - Logical start block: {}", new_extent.first_block); + log::debug!(" - Physical start block: {}", new_extent.get_pblock()); + log::debug!(" - Block count: {}", new_extent.block_count); + log::debug!(" - Actual length: {}", new_extent.get_actual_len()); + log::debug!(" - Unwritten: {}", new_extent.is_unwritten()); + log::debug!(" - Raw data: start_lo={}, start_hi={}, block_count={:#x}", + new_extent.start_lo, new_extent.start_hi, new_extent.block_count); + log::debug!(" - Tree position: depth={}, position={}, at_root={}", + depth, node.position, node.pblock_of_node == 0); + + // insert at root + if depth == 0 { + // Node is empty (no extents) + if header.entries_count == 0 { + log::info!("[insert_new_extent] Inserting first extent into empty root node"); + *inode_ref.inode.root_extent_mut_at(node.position) = *new_extent; + inode_ref.inode.root_extent_header_mut().entries_count += 1; + + self.write_back_inode(inode_ref); + + // Add debug logs after successful insertion at root node + log::debug!("[insert_new_extent] Successfully inserted at root:"); + log::debug!(" - Root header: magic={:x}, entries={}, max={}, depth={}", + inode_ref.inode.root_extent_header().magic, + inode_ref.inode.root_extent_header().entries_count, + inode_ref.inode.root_extent_header().max_entries_count, + inode_ref.inode.root_extent_header().depth); + + return Ok(()); + } + // Check if root node is full, need to grow in depth + if header.entries_count == header.max_entries_count { + log::info!("[insert_new_extent] Root node full, growing in depth"); + self.ext_grow_indepth(inode_ref)?; + // After growing, re-insert + return self.insert_extent(inode_ref, new_extent); + } + + + // Not empty, insert at search result pos + 1 + log::info!("[insert_new_extent] Inserting at root at position {} (entries: {})", + node.position + 1, header.entries_count); + *inode_ref.inode.root_extent_mut_at(node.position + 1) = *new_extent; + inode_ref.inode.root_extent_header_mut().entries_count += 1; + + log::debug!("[insert_new_extent] Successfully inserted at root:"); + log::debug!(" - Root header: magic={:x}, entries={}, max={}, depth={}", + inode_ref.inode.root_extent_header().magic, + inode_ref.inode.root_extent_header().entries_count, + inode_ref.inode.root_extent_header().max_entries_count, + inode_ref.inode.root_extent_header().depth); + + return Ok(()); + } else { + // insert at nonroot + log::info!("[insert_new_extent] Inserting at non-root node at depth {}, position {}", + depth, node.position + 1); + + // load block + let node_block = node.pblock_of_node; + let mut ext4block = + Block::load(self.block_device.clone(), node_block * BLOCK_SIZE); + let new_ex_offset = core::mem::size_of::() + core::mem::size_of::() * (node.position + 1); + + // insert new extent + let ex: &mut Ext4Extent = ext4block.read_offset_as_mut(new_ex_offset); + *ex = *new_extent; + let header: &mut Ext4ExtentHeader = ext4block.read_offset_as_mut(0); + + // update entry count + header.entries_count += 1; + log::info!("[insert_new_extent] Updated non-root node: entries={}, max={}", + header.entries_count, header.max_entries_count); + + // Complete block processing and sync to disk first + let node_header_entries = header.entries_count; + let node_header_max = header.max_entries_count; + ext4block.sync_blk_to_disk(self.block_device.clone()); + + // Set the checksum for the updated extent block + if let Err(e) = self.set_extent_block_checksum(inode_ref, node_block) { + log::warn!("[insert_new_extent] Failed to set extent block checksum: {:?}", e); + } else { + log::info!("[insert_new_extent] Set checksum for updated extent block"); + } + + log::info!("[insert_new_extent] Synced non-root node to disk"); + + log::debug!("[insert_new_extent] Successfully inserted at non-root node:"); + log::debug!(" - Node header: entries={}, max={}, depth={}", + node_header_entries, node_header_max, depth); + log::debug!(" - Block address: {}", node_block); + log::debug!(" - Extent position: {}", node.position + 1); + log::debug!(" - Extent: logical={}, physical={}, length={}", + new_extent.first_block, new_extent.get_pblock(), new_extent.get_actual_len()); + + return Ok(()); + } + + return_errno_with_message!(Errno::ENOTSUP, "Not supported insert extent at nonroot"); + } + + // finds empty index and adds new leaf. if no free index is found, then it requests in-depth growing. + fn create_new_leaf( + &self, + inode_ref: &mut Ext4InodeRef, + search_path: &mut SearchPath, + new_extent: &mut Ext4Extent, + ) -> Result<()> { + log::info!("[create_new_leaf] Starting - Current tree state:"); + log::info!("[create_new_leaf] Root header: magic={:x}, entries={}, max={}, depth={}", + inode_ref.inode.root_extent_header().magic, + inode_ref.inode.root_extent_header().entries_count, + inode_ref.inode.root_extent_header().max_entries_count, + inode_ref.inode.root_extent_header().depth); + log::info!("[create_new_leaf] New extent: logical block {}, physical block {}, length {}", + new_extent.first_block, new_extent.get_pblock(), new_extent.get_actual_len()); + + // tree is full, time to grow in depth + log::info!("[create_new_leaf] Tree is full, calling ext_grow_indepth"); + self.ext_grow_indepth(inode_ref)?; + + log::info!("[create_new_leaf] After ext_grow_indepth - New tree state:"); + log::info!("[create_new_leaf] Root header: magic={:x}, entries={}, max={}, depth={}", + inode_ref.inode.root_extent_header().magic, + inode_ref.inode.root_extent_header().entries_count, + inode_ref.inode.root_extent_header().max_entries_count, + inode_ref.inode.root_extent_header().depth); + + // insert again + log::info!("[create_new_leaf] Attempting to insert extent again"); + self.insert_extent(inode_ref, new_extent) + } + + + // allocates new block + // moves top-level data (index block or leaf) into the new block + // initializes new top-level, creating index that points to the + // just created block + fn ext_grow_indepth(&self, inode_ref: &mut Ext4InodeRef) -> Result<()>{ + log::info!("[ext_grow_indepth] Starting - Current tree state:"); + log::info!("[ext_grow_indepth] Root header: magic={:x}, entries={}, max={}, depth={}", + inode_ref.inode.root_extent_header().magic, + inode_ref.inode.root_extent_header().entries_count, + inode_ref.inode.root_extent_header().max_entries_count, + inode_ref.inode.root_extent_header().depth); + + // Allocate new block to store original root node content + let new_block = self.balloc_alloc_block(inode_ref, None)?; + log::info!("[ext_grow_indepth] Allocated new block: {}", new_block); + + // Load new block + let mut new_ext4block = + Block::load(self.block_device.clone(), new_block as usize * BLOCK_SIZE); + log::info!("[ext_grow_indepth] Loaded new block"); + + // Clear new block to ensure no garbage data + new_ext4block.data.fill(0); + + // Save original root node information + let old_root_header = inode_ref.inode.root_extent_header(); + let old_depth = old_root_header.depth; + let old_entries_count = old_root_header.entries_count; + + // Get logical block number of first extent (only when original was a leaf node) + let first_logical_block = if old_depth == 0 && old_entries_count > 0 { + inode_ref.inode.root_extent_at(0).first_block + } else { + 0 + }; + + // Copy root node extents data to new block + // extent start position in inode block is 12 bytes (after header) + // extent start position in new block is also 12 bytes (after header) + let header_size = EXT4_EXTENT_HEADER_SIZE; + + // Copy header first + let mut new_header = Ext4ExtentHeader::new( + EXT4_EXTENT_MAGIC, + old_entries_count, + ((BLOCK_SIZE - header_size) / EXT4_EXTENT_SIZE) as u16, // Maximum entries the new block can hold + 0, // New block becomes a leaf node, depth 0 + 0 // generation field, usually 0 + ); + + // Write header to new block + let header_bytes = unsafe { + core::slice::from_raw_parts( + &new_header as *const _ as *const u8, + header_size + ) + }; + new_ext4block.data[..header_size].copy_from_slice(header_bytes); + + // Copy extents data + if old_entries_count > 0 { + // Copy extents from root block to new block + // extent start position in inode block is 12 bytes (after header) + // extent start position in new block is also 12 bytes (after header) + let root_extents_size = old_entries_count as usize * EXT4_EXTENT_SIZE; + + // Use temporary variable to store block data to avoid mutable borrow conflicts + let block_data = unsafe { + let block_ptr = inode_ref.inode.block.as_ptr(); + core::slice::from_raw_parts(block_ptr as *const u8, 60) + }; + + let root_extents_bytes = &block_data[header_size..header_size + root_extents_size]; + new_ext4block.data[header_size..header_size + root_extents_size] + .copy_from_slice(root_extents_bytes); + } + + log::info!("[ext_grow_indepth] Copied root data to new block and set header: magic={:x}, entries={}, max_entries={}, depth={}", + new_header.magic, new_header.entries_count, new_header.max_entries_count, new_header.depth); + + // Set checksum for the new extent block + new_ext4block.sync_blk_to_disk(self.block_device.clone()); + // Set the checksum for the new extent block + if let Err(e) = self.set_extent_block_checksum(inode_ref, new_block as usize) { + log::warn!("[ext_grow_indepth] Failed to set extent block checksum: {:?}", e); + } else { + log::info!("[ext_grow_indepth] Set checksum for new extent block"); + } + + // First read the block number of the first extent (if any), then update root node + let first_logical_block_saved = first_logical_block; + + // Update root node to be an index node + { + let mut root_header = inode_ref.inode.root_extent_header_mut(); + root_header.set_magic(); // Set magic + root_header.set_entries_count(1); // Index node initially has one entry + root_header.set_max_entries_count(4); // Root index node typically has 4 entries + root_header.add_depth(); // Increase depth + + log::info!("[ext_grow_indepth] Updated root header: depth {} -> {}, entries={}, max={}", + old_depth, root_header.depth, root_header.entries_count, root_header.max_entries_count); + } + + // Clear extents data in original root node + unsafe { + let root_block_ptr = inode_ref.inode.block.as_mut_ptr() as *mut u8; + // Skip header part, only clear the extent data after it + let extents_ptr = root_block_ptr.add(header_size); + core::ptr::write_bytes(extents_ptr, 0, 60 - header_size); + } + + // Create first index entry in root node pointing to new block + { + let mut root_first_index = inode_ref.inode.root_first_index_mut(); + root_first_index.first_block = first_logical_block_saved; // Set starting logical block number + root_first_index.store_pblock(new_block); // Store physical address of new block + + log::info!("[ext_grow_indepth] Root became index block, first_block={}, pointing to block {}", + first_logical_block_saved, new_block); + } + + // Write updated inode back to disk + self.write_back_inode(inode_ref); + log::info!("[ext_grow_indepth] Wrote updated inode back to disk"); + + log::info!("[ext_grow_indepth] Completed - Final tree state:"); + log::info!("[ext_grow_indepth] Root header: magic={:x}, entries={}, max={}, depth={}", + inode_ref.inode.root_extent_header().magic, + inode_ref.inode.root_extent_header().entries_count, + inode_ref.inode.root_extent_header().max_entries_count, + inode_ref.inode.root_extent_header().depth); + + Ok(()) + } + +} + +impl Ext4 { + // Assuming init state + // depth 0 (root node) + // +--------+--------+--------+ + // | idx1 | idx2 | idx3 | + // +--------+--------+--------+ + // | | | + // v v v + // + // depth 1 (internal node) + // +--------+...+--------+ +--------+...+--------+ ...... + // | idx1 |...| idxn | | idx1 |...| idxn | ...... + // +--------+...+--------+ +--------+...+--------+ ...... + // | | | | + // v v v v + // + // depth 2 (leaf nodes) + // +--------+...+--------+ +--------+...+--------+ ...... + // | ext1 |...| extn | | ext1 |...| extn | ...... + // +--------+...+--------+ +--------+...+--------+ ...... + pub fn extent_remove_space( + &self, + inode_ref: &mut Ext4InodeRef, + from: u32, + to: u32, + ) -> Result { + // log::info!("Remove space from {:x?} to {:x?}", from, to); + let mut search_path = self.find_extent(inode_ref, from)?; + + // for i in search_path.path.iter() { + // log::info!("from Path: {:x?}", i); + // } + + let depth = search_path.depth as usize; + + /* If we do remove_space inside the range of an extent */ + let mut ex = search_path.path[depth].extent.unwrap(); + if ex.get_first_block() < from + && to < (ex.get_first_block() + ex.get_actual_len() as u32 - 1) + { + let mut newex = Ext4Extent::default(); + let unwritten = ex.is_unwritten(); + let ee_block = ex.first_block; + let block_count = ex.block_count; + let newblock = to + 1 - ee_block + ex.get_pblock() as u32; + ex.block_count = from as u16 - ee_block as u16; + + if unwritten { + ex.mark_unwritten(); + } + newex.first_block = to + 1; + newex.block_count = (ee_block + block_count as u32 - 1 - to) as u16; + newex.start_lo = newblock; + newex.start_hi = ((newblock as u64) >> 32) as u16; + + self.insert_extent(inode_ref, &mut newex)?; + + return Ok(EOK); + } + + // log::warn!("Remove space in depth: {:x?}", depth); + + let mut i = depth as isize; + + while i >= 0 { + // we are at the leaf node + // depth 0 (root node) + // +--------+--------+--------+ + // | idx1 | idx2 | idx3 | + // +--------+--------+--------+ + // |path + // v + // idx2 + // depth 1 (internal node) + // +--------+--------+--------+ ...... + // | idx1 | idx2 | idx3 | ...... + // +--------+--------+--------+ ...... + // |path + // v + // ext2 + // depth 2 (leaf nodes) + // +--------+--------+..+--------+ + // | ext1 | ext2 |..|last_ext| + // +--------+--------+..+--------+ + // ^ ^ + // | | + // from to(exceed last ext, rest of the extents will be removed) + if i as usize == depth { + let node_pblock = search_path.path[i as usize].pblock_of_node; + + let header = search_path.path[i as usize].header; + let entries_count = header.entries_count; + + // we are at root + if node_pblock == 0 { + let first_ex = inode_ref.inode.root_extent_at(0); + let last_ex = inode_ref.inode.root_extent_at(entries_count as usize - 1); + + let mut leaf_from = first_ex.first_block; + let mut leaf_to = last_ex.first_block + last_ex.get_actual_len() as u32 - 1; + if leaf_from < from { + leaf_from = from; + } + if leaf_to > to { + leaf_to = to; + } + // log::trace!("from {:x?} to {:x?} leaf_from {:x?} leaf_to {:x?}", from, to, leaf_from, leaf_to); + self.ext_remove_leaf(inode_ref, &mut search_path, leaf_from, leaf_to)?; + + i -= 1; + continue; + } + let ext4block = + Block::load(self.block_device.clone(), node_pblock * BLOCK_SIZE); + + let header = search_path.path[i as usize].header; + let entries_count = header.entries_count; + + let first_ex: Ext4Extent = ext4block.read_offset_as(size_of::()); + let last_ex: Ext4Extent = ext4block.read_offset_as( + size_of::() + + size_of::() * (entries_count - 1) as usize, + ); + + let mut leaf_from = first_ex.first_block; + let mut leaf_to = last_ex.first_block + last_ex.get_actual_len() as u32 - 1; + + if leaf_from < from { + leaf_from = from; + } + if leaf_to > to { + leaf_to = to; + } + // log::trace!( + // "from {:x?} to {:x?} leaf_from {:x?} leaf_to {:x?}", + // from, + // to, + // leaf_from, + // leaf_to + // ); + + self.ext_remove_leaf(inode_ref, &mut search_path, leaf_from, leaf_to)?; + + i -= 1; + continue; + } + + // log::trace!("---at level---{:?}\n", i); + + // we are at index + // example i=1, depth=2 + // depth 0 (root node) - Index node being processed + // +--------+--------+--------+ + // | idx1 | idx2 | idx3 | + // +--------+--------+--------+ + // |path | Next node to process (more_to_rm?) + // v v + // idx2 + // + // depth 1 (internal node) + // +--------++--------+...+--------+ + // | idx1 || idx2 |...| idxn | + // +--------++--------+...+--------+ + // |path + // v + // ext2 + // depth 2 (leaf nodes) + // +--------+--------+..+--------+ + // | ext1 | ext2 |..|last_ext| + // +--------+--------+..+--------+ + let header = search_path.path[i as usize].header; + if self.more_to_rm(&search_path.path[i as usize], to) { + // todo + // load next idx + + // go to this node's child + i += 1; + } else { + if i > 0 { + // empty + if header.entries_count == 0 { + self.ext_remove_idx(inode_ref, &mut search_path, i as u16 - 1)?; + } + } + + let idx = i; + if idx - 1 < 0 { + break; + } + i -= 1; + } + } + + Ok(EOK) + } + + pub fn ext_remove_leaf( + &self, + inode_ref: &mut Ext4InodeRef, + path: &mut SearchPath, + from: u32, + to: u32, + ) -> Result { + // log::trace!("Remove leaf from {:x?} to {:x?}", from, to); + + // depth 0 (root node) + // +--------+--------+--------+ + // | idx1 | idx2 | idx3 | + // +--------+--------+--------+ + // | | | + // v v v + // ^ + // Current position + let depth = inode_ref.inode.root_header_depth(); + let mut header = path.path[depth as usize].header; + + let mut new_entry_count = header.entries_count; + let mut ex2 = Ext4Extent::default(); + + /* find where to start removing */ + let pos = path.path[depth as usize].position; + let entry_count = header.entries_count; + + // depth 1 (internal node) + // +--------+...+--------+ +--------+...+--------+ ...... + // | idx1 |...| idxn | | idx1 |...| idxn | ...... + // +--------+...+--------+ +--------+...+--------+ ...... + // | | | | + // v v v v + // ^ + // Current loaded node + + // load node data + let node_disk_pos = path.path[depth as usize].pblock_of_node * BLOCK_SIZE; + + let mut ext4block = if node_disk_pos == 0 { + // we are at root + Block::load_inode_root_block(&inode_ref.inode.block) + } else { + Block::load(self.block_device.clone(), node_disk_pos) + }; + + // depth 2 (leaf nodes) + // +--------+...+--------+ +--------+...+--------+ ...... + // | ext1 |...| extn | | ext1 |...| extn | ...... + // +--------+...+--------+ +--------+...+--------+ ...... + // ^ + // Current start extent + + // start from pos + for i in pos..entry_count as usize { + let ex: &mut Ext4Extent = ext4block + .read_offset_as_mut(size_of::() + i * size_of::()); + + if ex.first_block > to { + break; + } + + let mut new_len = 0; + let mut start = ex.first_block; + let mut new_start = ex.first_block; + + let mut len = ex.get_actual_len(); + let mut newblock = ex.get_pblock(); + + // Initial state: + // +--------+...+--------+ +--------+...+--------+ ...... + // | ext1 |...| ext2 | | ext3 |...| extn | ...... + // +--------+...+--------+ +--------+...+--------+ ...... + // ^ ^ + // from to + + // Case 1: Remove a portion within the extent + if start < from { + len -= from as u16 - start as u16; + new_len = from - start; + start = from; + } else { + // Case 2: Adjust extent that partially overlaps the 'to' boundary + if start + len as u32 - 1 > to { + new_len = start + len as u32 - 1 - to; + len -= new_len as u16; + new_start = to + 1; + newblock += (to + 1 - start) as u64; + ex2 = *ex; + } + } + + // After removing range from `from` to `to`: + // +--------+...+--------+ +--------+...+--------+ ...... + // | ext1 |...[removed]| |[removed]|...| extn | ...... + // +--------+...+--------+ +--------+...+--------+ ...... + // ^ ^ + // from to + // new_start + + // Remove blocks within the extent + self.ext_remove_blocks(inode_ref, ex, start, start + len as u32 - 1); + + ex.first_block = new_start; + // log::trace!("after remove leaf ex first_block {:x?}", ex.first_block); + + if new_len == 0 { + new_entry_count -= 1; + } else { + let unwritten = ex.is_unwritten(); + ex.store_pblock(newblock as u64); + ex.block_count = new_len as u16; + + if unwritten { + ex.mark_unwritten(); + } + } + } + + // Move remaining extents to the start: + // Before: + // +--------+--------+...+--------+ + // | ext3 | ext4 |...| extn | + // +--------+--------+...+--------+ + // ^ ^ + // rm rm + // After: + // +--------+.+--------+--------+... + // | ext1 |.| extn | [empty]|... + // +--------+.+--------+--------+... + + // Move any remaining extents to the starting position of the node. + if ex2.first_block > 0 { + let start_index = size_of::() + pos * size_of::(); + let end_index = + size_of::() + entry_count as usize * size_of::(); + let remaining_extents: Vec = ext4block.data[start_index..end_index].to_vec(); + ext4block.data[size_of::() + ..size_of::() + remaining_extents.len()] + .copy_from_slice(&remaining_extents); + } + + // Update the entries count in the header + header.entries_count = new_entry_count; + + /* + * If the extent pointer is pointed to the first extent of the node, and + * there's still extents presenting, we may need to correct the indexes + * of the paths. + */ + if pos == 0 && new_entry_count > 0 { + self.ext_correct_indexes(path)?; + } + + /* if this leaf is free, then we should + * remove it from index block above */ + if new_entry_count == 0 { + // if we are at root? + if path.path[depth as usize].pblock_of_node == 0 { + return Ok(EOK); + } + self.ext_remove_idx(inode_ref, path, depth - 1)?; + } else if depth > 0 { + // go to next index + path.path[depth as usize - 1].position += 1; + } + + Ok(EOK) + } + + fn ext_remove_index_block(&self, inode_ref: &mut Ext4InodeRef, index: &mut Ext4ExtentIndex) { + let block_to_free = index.get_pblock(); + + // log::trace!("remove index's block {:x?}", block_to_free); + self.balloc_free_blocks(inode_ref, block_to_free as _, 1); + } + + fn ext_remove_idx( + &self, + inode_ref: &mut Ext4InodeRef, + path: &mut SearchPath, + depth: u16, + ) -> Result { + // log::trace!("Remove index at depth {:x?}", depth); + + // Initial state: + // +--------+--------+--------+ + // | idx1 | idx2 | idx3 | + // +--------+--------+--------+ + // ^ + // Current index to remove (pos=1) + + // Removing index: + // +--------+--------+--------+ + // | idx1 |[empty] | idx3 | + // +--------+--------+--------+ + // ^ + + let i = depth as usize; + let mut header = path.path[i].header; + + // Get the index block to delete + let leaf_block = path.path[i].index.unwrap().get_pblock(); + + // If current index is not the last one, move subsequent indexes forward + if path.path[i].position != header.entries_count as usize - 1 { + let start_pos = size_of::() + + path.path[i].position * size_of::(); + let end_pos = size_of::() + + (header.entries_count as usize) * size_of::(); + + let node_disk_pos = path.path[i].pblock_of_node * BLOCK_SIZE; + let mut ext4block = Block::load(self.block_device.clone(), node_disk_pos); + + let remaining_indexes: Vec = + ext4block.data[start_pos + size_of::()..end_pos].to_vec(); + ext4block.data[start_pos..start_pos + remaining_indexes.len()] + .copy_from_slice(&remaining_indexes); + let remaining_size = remaining_indexes.len(); + + // Clear the remaining positions + let empty_start = start_pos + remaining_size; + let empty_end = end_pos; + ext4block.data[empty_start..empty_end].fill(0); + } + + // Update the entries_count in the header + header.entries_count -= 1; + + // Free the index block + self.ext_remove_index_block(inode_ref, &mut path.path[i].index.unwrap()); + + // If we're not at the root, check if we need to update the parent node index + let mut idx = i; + while idx > 0 { + if path.path[idx].position != 0 { + break; + } + + let parent_idx = idx - 1; + let parent_index = &mut path.path[parent_idx].index.unwrap(); + let current_index = &path.path[idx].index.unwrap(); + + parent_index.first_block = current_index.first_block; + self.write_back_inode(inode_ref); + + idx -= 1; + } + + Ok(EOK) + } + + /// Correct the first block of the parent index. + fn ext_correct_indexes(&self, path: &mut SearchPath) -> Result { + // If child gets removed from parent, we need to update the parent's first_block + let mut depth = path.depth as usize; + + // depth 2: + // +--------+--------+--------+ + // |[empty] | ext2 | ext3 | + // +--------+--------+--------+ + // ^ + // pos=0, ext1_first_block=0(removed) parent index first block=0 + + // depth 2: + // +--------+--------+--------+ + // | ext2 | ext3 |[empty] | + // +--------+--------+--------+ + // ^ + // pos=0, now first_block=ext2_first_block + + // Update parent node index: + // depth 1: + // +-----------------------+ + // | idx1_2 |...| idx1_n | + // +-----------------------+ + // ^ + // Update parent node index (first_block) + + // depth 0: + // +--------+--------+--------+ + // | idx1 | idx2 | idx3 | + // +--------+--------+--------+ + // | + // Update root node index (first_block) + + while depth > 0 { + let parent_idx = depth - 1; + + // Get the extent at the current level + if let Some(child_extent) = path.path[depth].extent { + // Get the parent node + let parent_node = &mut path.path[parent_idx]; + // Get parent node's index and update first_block + if let Some(ref mut parent_index) = parent_node.index { + parent_index.first_block = child_extent.first_block; + } + } + + depth -= 1; + } + + Ok(EOK) + } + + fn ext_remove_blocks( + &self, + inode_ref: &mut Ext4InodeRef, + ex: &mut Ext4Extent, + from: u32, + to: u32, + ) { + let len = to - from + 1; + let num = from - ex.first_block; + let start: u32 = ex.get_pblock() as u32 + num; + self.balloc_free_blocks(inode_ref, start as _, len); + } + + pub fn more_to_rm(&self, path: &ExtentPathNode, to: u32) -> bool { + let header = path.header; + + // No Sibling exists + if header.entries_count == 1 { + return false; + } + + let pos = path.position; + if pos > header.entries_count as usize - 1 { + return false; + } + + // Check if index is out of bounds + if let Some(index) = path.index { + let last_index_pos = header.entries_count as usize - 1; + let node_disk_pos = path.pblock_of_node * BLOCK_SIZE; + let ext4block = Block::load(self.block_device.clone(), node_disk_pos); + let last_index: Ext4ExtentIndex = + ext4block.read_offset_as(size_of::() * last_index_pos); + + if path.position > last_index_pos || index.first_block > last_index.first_block { + return false; + } + + // Check if index's first_block is greater than 'to' + if index.first_block > to { + return false; + } + } + + true + } +} + +impl Ext4 { + /// Calculate and set the extent block checksum in the extent tail + fn set_extent_block_checksum(&self, inode_ref: &Ext4InodeRef, block_addr: usize) -> Result<()> { + // Check if metadata checksums are enabled in the filesystem + let features_ro_compat = self.super_block.features_read_only; + // EXT4_FEATURE_RO_COMPAT_METADATA_CSUM is typically 0x400 + let has_metadata_checksums = (features_ro_compat & 0x400) != 0; + + if !has_metadata_checksums { + return Ok(()); + } + + // Load the extent block + let mut ext4block = Block::load(self.block_device.clone(), block_addr * BLOCK_SIZE); + + // Get the extent header + let header = ext4block.read_offset_as::(0); + + // Check for valid magic + if header.magic != EXT4_EXTENT_MAGIC { + return_errno_with_message!(Errno::EINVAL, "Invalid extent magic"); + } + + // Calculate position of the extent tail + let tail_offset = ext4_extent_tail_offset(&header); + + // Create a copy of the data for checksum calculation to avoid borrow conflicts + let data_for_checksum = ext4block.data[..tail_offset].to_vec(); + + // Calculate checksum + let checksum = self.calculate_extent_block_checksum(inode_ref, &data_for_checksum, block_addr); + + // Get a mutable reference to the tail + let tail: &mut Ext4ExtentTail = ext4block.read_offset_as_mut(tail_offset); + + // Set checksum in tail + tail.et_checksum = checksum; + + // Write back the block + ext4block.sync_blk_to_disk(self.block_device.clone()); + + Ok(()) + } + + /// Calculate the checksum for an extent block + fn calculate_extent_block_checksum(&self, inode_ref: &Ext4InodeRef, data: &[u8], block_addr: usize) -> u32 { + let mut checksum = 0; + + // If metadata checksums are not enabled, return 0 + let features_ro_compat = self.super_block.features_read_only; + // EXT4_FEATURE_RO_COMPAT_METADATA_CSUM is typically 0x400 + let has_metadata_checksums = (features_ro_compat & 0x400) != 0; + + if !has_metadata_checksums { + return 0; + } + + // Get UUID from superblock + let uuid = &self.super_block.uuid; + + // Calculate checksum - first using UUID + checksum = ext4_crc32c(EXT4_CRC32_INIT, uuid, uuid.len() as u32); + + // Add inode number to checksum + let ino_index = inode_ref.inode_num; + checksum = ext4_crc32c(checksum, &ino_index.to_le_bytes(), 4); + + // Add inode generation to checksum + let ino_gen = inode_ref.inode.generation; + checksum = ext4_crc32c(checksum, &ino_gen.to_le_bytes(), 4); + + // Finally add the extent block data + checksum = ext4_crc32c(checksum, data, data.len() as u32); + + checksum + } + +} + +/// Calculate the offset of the extent tail +pub fn ext4_extent_tail_offset(header: &Ext4ExtentHeader) -> usize { + size_of::() + + (header.max_entries_count as usize * size_of::()) +} \ No newline at end of file diff --git a/vendor/ext4_rs/src/ext4_impls/file.rs b/vendor/ext4_rs/src/ext4_impls/file.rs new file mode 100644 index 00000000..484daf4d --- /dev/null +++ b/vendor/ext4_rs/src/ext4_impls/file.rs @@ -0,0 +1,756 @@ +use crate::prelude::*; +use crate::return_errno_with_message; +use crate::utils::path_check; +use crate::ext4_defs::*; +use core::cmp::max; +// use std::time::{Duration, Instant}; + +impl Ext4 { + /// Link a child inode to a parent directory + /// + /// Params: + /// parent: &mut Ext4InodeRef - parent directory inode reference + /// child: &mut Ext4InodeRef - child inode reference + /// name: &str - name of the child inode + /// + /// Returns: + /// `Result` - status of the operation + pub fn link( + &self, + parent: &mut Ext4InodeRef, + child: &mut Ext4InodeRef, + name: &str, + ) -> Result { + // Add a directory entry in the parent directory pointing to the child inode + + // at this point should insert to existing block + self.dir_add_entry(parent, child, name)?; + self.write_back_inode_without_csum(parent); + + // If this is the first link. add '.' and '..' entries + if child.inode.is_dir() { + // let child_ref = child.clone(); + let new_child_ref = Ext4InodeRef { + inode_num: child.inode_num, + inode: child.inode, + }; + + // at this point child need a new block + // Create "." entry pointing to the child directory itself + self.dir_add_entry(child, &new_child_ref, ".")?; + + // at this point should insert to existing block + // Create ".." entry pointing to the parent directory + self.dir_add_entry(child, parent, "..")?; + + child.inode.set_links_count(2); + let link_cnt = parent.inode.links_count() + 1; + parent.inode.set_links_count(link_cnt); + + return Ok(EOK); + } + + // Increment the link count of the child inode + let link_cnt = child.inode.links_count() + 1; + child.inode.set_links_count(link_cnt); + + Ok(EOK) + } + + /// create a new inode and link it to the parent directory + /// + /// Params: + /// parent: u32 - inode number of the parent directory + /// name: &str - name of the new file + /// mode: u16 - file mode + /// + /// Returns: + pub fn create(&self, parent: u32, name: &str, inode_mode: u16) -> Result { + let mut parent_inode_ref = self.get_inode_ref(parent); + + // let mut child_inode_ref = self.create_inode(inode_mode)?; + let init_child_ref = self.create_inode(inode_mode)?; + + self.write_back_inode_without_csum(&init_child_ref); + // load new + let mut child_inode_ref = self.get_inode_ref(init_child_ref.inode_num); + + self.link(&mut parent_inode_ref, &mut child_inode_ref, name)?; + + self.write_back_inode(&mut parent_inode_ref); + self.write_back_inode(&mut child_inode_ref); + + Ok(child_inode_ref) + } + + pub fn create_inode(&self, inode_mode: u16) -> Result { + + let inode_file_type = match InodeFileType::from_bits(inode_mode) { + Some(file_type) => file_type, + None => InodeFileType::S_IFREG, + }; + + let is_dir = inode_file_type == InodeFileType::S_IFDIR; + + // allocate inode + let inode_num = self.alloc_inode(is_dir)?; + + // initialize inode + let mut inode = Ext4Inode::default(); + + // set mode + inode.set_mode(inode_mode | 0o777); + + // set extra size + let inode_size = self.super_block.inode_size(); + let extra_size = self.super_block.extra_size(); + if inode_size > EXT4_GOOD_OLD_INODE_SIZE { + inode.set_i_extra_isize(extra_size); + } + + // set extent + inode.set_flags(EXT4_INODE_FLAG_EXTENTS as u32); + inode.extent_tree_init(); + + let inode_ref = Ext4InodeRef { + inode_num, + inode, + }; + + Ok(inode_ref) + } + + + /// create a new inode and link it to the parent directory + /// + /// Params: + /// parent: u32 - inode number of the parent directory + /// name: &str - name of the new file + /// mode: u16 - file mode + /// uid: u32 - user id + /// gid: u32 - group id + /// + /// Returns: + pub fn create_with_attr(&self, parent: u32, name: &str, inode_mode: u16, uid:u16, gid: u16) -> Result { + let mut parent_inode_ref = self.get_inode_ref(parent); + + // let mut child_inode_ref = self.create_inode(inode_mode)?; + let mut init_child_ref = self.create_inode(inode_mode)?; + + init_child_ref.inode.set_uid(uid); + init_child_ref.inode.set_gid(gid); + + self.write_back_inode_without_csum(&init_child_ref); + // load new + let mut child_inode_ref = self.get_inode_ref(init_child_ref.inode_num); + + self.link(&mut parent_inode_ref, &mut child_inode_ref, name)?; + + self.write_back_inode(&mut parent_inode_ref); + self.write_back_inode(&mut child_inode_ref); + + Ok(child_inode_ref) + } + + /// Read data from a file at a given offset + /// + /// Params: + /// inode: u32 - inode number of the file + /// offset: usize - offset from where to read + /// read_buf: &mut [u8] - buffer to read the data into + /// + /// Returns: + /// `Result` - number of bytes read + pub fn read_at(&self, inode: u32, offset: usize, read_buf: &mut [u8]) -> Result { + // read buf is empty, return 0 + let mut read_buf_len = read_buf.len(); + if read_buf_len == 0 { + log::error!("[Read] Empty read buffer, returning 0"); + return Ok(0); + } + + // get the inode reference + let inode_ref = self.get_inode_ref(inode); + let file_size = inode_ref.inode.size(); + let total_blocks = (file_size + BLOCK_SIZE as u64 - 1) / BLOCK_SIZE as u64; + + // if the offset is greater than the file size, return 0 + if offset >= file_size as usize { + return Ok(0); + } + + // adjust the read buffer size if the read buffer size is greater than the file size + if offset + read_buf_len > file_size as usize { + read_buf_len = file_size as usize - offset; + log::trace!("[Read] Adjusted read size to {} bytes to not exceed file size (offset: {}, file_size: {})", + read_buf_len, offset, file_size); + } + + // calculate the start block and unaligned size + let iblock_start = offset / BLOCK_SIZE; + let iblock_last = (offset + read_buf_len + BLOCK_SIZE - 1) / BLOCK_SIZE; // round up to include the last partial block + let unaligned_start_offset = offset % BLOCK_SIZE; + + // Ensure we don't read beyond the last block + let iblock_last = min(iblock_last, total_blocks as usize); + + + // Buffer to keep track of read bytes + let mut cursor = 0; + let mut total_bytes_read = 0; + let mut iblock = iblock_start; + + // Unaligned read at the beginning + if unaligned_start_offset > 0 { + let adjust_read_size = min(BLOCK_SIZE - unaligned_start_offset, read_buf_len); + + // get iblock physical block id + let pblock_idx = match self.get_pblock_idx(&inode_ref, iblock as u32) { + Ok(idx) => { + idx + }, + Err(e) => { + return_errno_with_message!(Errno::EIO, "Failed to get physical block for logical block"); + } + }; + + // read data + let data = self.block_device.read_offset(pblock_idx as usize * BLOCK_SIZE); + + // copy data to read buffer + read_buf[cursor..cursor + adjust_read_size].copy_from_slice( + &data[unaligned_start_offset..unaligned_start_offset + adjust_read_size], + ); + + // update cursor and total bytes read + cursor += adjust_read_size; + total_bytes_read += adjust_read_size; + iblock += 1; + } + + // Continue with full block reads + while total_bytes_read < read_buf_len && iblock < iblock_last { + let mut read_length = min(BLOCK_SIZE, read_buf_len - total_bytes_read); + + // Check if this is the last block of the file + if iblock as u64 >= total_blocks - 1 { + let remaining_bytes = file_size as usize - (iblock * BLOCK_SIZE); + let actual_read_length = min(read_length, remaining_bytes); + + if actual_read_length < read_length { + read_length = actual_read_length; + } + } + + + // get iblock physical block id + let pblock_idx = match self.get_pblock_idx(&inode_ref, iblock as u32) { + Ok(idx) => { + idx + }, + Err(e) => { + return_errno_with_message!(Errno::EIO, "Failed to get physical block for logical block"); + } + }; + + // read data + let data = self.block_device.read_offset(pblock_idx as usize * BLOCK_SIZE); + // log::trace!("[Read] Read block data - physical_block: {}, data_len: {}", pblock_idx, data.len()); + + // copy data to read buffer + read_buf[cursor..cursor + read_length].copy_from_slice(&data[..read_length]); + + // update cursor and total bytes read + cursor += read_length; + total_bytes_read += read_length; + iblock += 1; + } + + Ok(total_bytes_read) + } + + /// Write data to a file at a given offset + /// + /// Params: + /// inode: u32 - inode number of the file + /// offset: usize - offset from where to write + /// write_buf: &[u8] - buffer to write the data from + /// + /// Returns: + /// `Result` - number of bytes written + pub fn write_at(&self, inode: u32, offset: usize, write_buf: &[u8]) -> Result { + // write buf is empty, return 0 + let mut write_buf_len = write_buf.len(); + if write_buf_len == 0 { + log::info!("[Write] Empty write buffer, returning 0"); + return Ok(0); + } + + // get the inode reference + let mut inode_ref = self.get_inode_ref(inode); + + // Get the file size + let file_size = inode_ref.inode.size(); + log::trace!("[Write] Starting write - inode: {}, offset: {}, size: {}, current file size: {}", + inode, offset, write_buf_len, file_size); + + // Calculate the start and end block index + let iblock_start = offset / BLOCK_SIZE; + let iblock_last = (offset + write_buf_len + BLOCK_SIZE - 1) / BLOCK_SIZE; + let total_blocks_needed = iblock_last - iblock_start; + + // start block index + let mut iblk_idx = iblock_start; + let ifile_blocks = (file_size + BLOCK_SIZE as u64 - 1) / BLOCK_SIZE as u64; + + // Calculate the unaligned size + let unaligned = offset % BLOCK_SIZE; + if unaligned > 0 { + log::trace!("[Alignment] Unaligned start: {} bytes", unaligned); + } + + // Buffer to keep track of written bytes + let mut written = 0; + let mut total_blocks = 0; + let mut new_blocks = 0; + + // Start bgid for block allocation + let mut start_bgid = 1; + + // Pre-allocate blocks if needed + let blocks_to_allocate = if iblk_idx >= ifile_blocks as usize { + total_blocks_needed + } else { + max(0, total_blocks_needed - (ifile_blocks as usize - iblk_idx)) + }; + + if blocks_to_allocate > 0 { + log::trace!("[Pre-allocation] Allocating {} blocks", blocks_to_allocate); + + // 使用append_inode_pblk_batch进行批量块分配 + let allocated_blocks = self.append_inode_pblk_batch(&mut inode_ref, &mut start_bgid, blocks_to_allocate)?; + + // If we couldn't allocate all blocks, adjust the write size + if allocated_blocks.len() < blocks_to_allocate { + log::trace!("[Write] Could only allocate {} out of {} blocks", allocated_blocks.len(), blocks_to_allocate); + + // Calculate new write size based on allocated blocks + let max_write_size = allocated_blocks.len() * BLOCK_SIZE; + let adjusted_write_size = if unaligned > 0 { + // For unaligned writes, we need to account for the unaligned portion + if allocated_blocks.len() > 0 { + let first_block_available = BLOCK_SIZE - unaligned; + let remaining_blocks_available = (allocated_blocks.len() - 1) * BLOCK_SIZE; + first_block_available + remaining_blocks_available + } else { + 0 + } + } else { + max_write_size + }; + + if adjusted_write_size == 0 { + log::error!("[Write] No space available for write after block allocation"); + return return_errno_with_message!(Errno::ENOSPC, "No blocks available for write"); + } + + // Update write size + write_buf_len = min(write_buf_len, adjusted_write_size); + log::trace!("[Write] Adjusted write size from {} to {} bytes", write_buf.len(), write_buf_len); + } + + new_blocks += allocated_blocks.len(); + } + + // Verify we have enough blocks for the write + let required_blocks = (write_buf_len + BLOCK_SIZE - 1) / BLOCK_SIZE; + let available_blocks = if iblk_idx >= ifile_blocks as usize { + new_blocks + } else { + (ifile_blocks as usize - iblk_idx) + new_blocks + }; + + if available_blocks < required_blocks { + log::error!("[Write] Not enough blocks available: required {}, available {}", + required_blocks, available_blocks); + return return_errno_with_message!(Errno::ENOSPC, "Not enough blocks available for write"); + } + + // Unaligned write + if unaligned > 0 && written < write_buf_len { + let len = min(write_buf_len, BLOCK_SIZE - unaligned); + log::trace!("[Unaligned Write] Writing {} bytes", len); + + // Get the physical block id + let pblock_idx = match self.get_pblock_idx(&inode_ref, iblk_idx as u32) { + Ok(idx) => idx, + Err(e) => { + log::error!("[Write] Failed to get physical block for logical block {}: {:?}", iblk_idx, e); + return Err(e); + } + }; + total_blocks += 1; + + let mut block = Block::load(self.block_device.clone(), pblock_idx as usize * BLOCK_SIZE); + + // Read existing data if needed + if unaligned > 0 || len < BLOCK_SIZE { + let existing_data = self.block_device.read_offset(pblock_idx as usize * BLOCK_SIZE); + block.data.copy_from_slice(&existing_data); + } + + block.write_offset(unaligned, &write_buf[..len], len); + + // Verify write + block.sync_blk_to_disk(self.block_device.clone()); + let verify_block = Block::load(self.block_device.clone(), pblock_idx as usize * BLOCK_SIZE); + if verify_block.data[unaligned..unaligned + len] != write_buf[..len] { + log::error!("[Write] Verification failed for unaligned write at block {}", pblock_idx); + return return_errno_with_message!(Errno::EIO, "Write verification failed"); + } + drop(block); + drop(verify_block); + + written += len; + iblk_idx += 1; + } + + // Aligned write + let mut aligned_blocks = 0; + log::info!("[Aligned Write] Starting aligned writes for {} blocks", (write_buf_len - written + BLOCK_SIZE - 1) / BLOCK_SIZE); + + while written < write_buf_len { + aligned_blocks += 1; + + // Get the physical block id + let pblock_idx = match self.get_pblock_idx(&inode_ref, iblk_idx as u32) { + Ok(idx) => idx, + Err(e) => { + log::error!("[Write] Failed to get physical block for logical block {}: {:?}", iblk_idx, e); + return Err(e); + } + }; + total_blocks += 1; + + let block_offset = pblock_idx as usize * BLOCK_SIZE; + let mut block = Block::load(self.block_device.clone(), block_offset); + let write_size = min(BLOCK_SIZE, write_buf_len - written); + + // For partial block writes, read existing data first + if write_size < BLOCK_SIZE { + let existing_data = self.block_device.read_offset(block_offset); + block.data.copy_from_slice(&existing_data); + } + + block.write_offset(0, &write_buf[written..written + write_size], write_size); + + // Verify write + block.sync_blk_to_disk(self.block_device.clone()); + let verify_block = Block::load(self.block_device.clone(), block_offset); + if verify_block.data[..write_size] != write_buf[written..written + write_size] { + log::error!("[Write] Verification failed for aligned write at block {}", pblock_idx); + return return_errno_with_message!(Errno::EIO, "Write verification failed"); + } + drop(block); + drop(verify_block); + + written += write_size; + iblk_idx += 1; + + if aligned_blocks % 1000 == 0 { + log::trace!("[Progress] Written {} blocks, {} bytes", aligned_blocks, written); + } + } + + // Update file size if necessary + let new_size = offset + written; + if new_size > file_size as usize { + log::trace!("[Write] Updating file size from {} to {}", file_size, new_size); + + // Verify the new size is valid + if new_size > EXT4_MAX_FILE_SIZE as usize { + log::error!("[Write] New file size {} exceeds maximum allowed size", new_size); + return return_errno_with_message!(Errno::EFBIG, "File size too large"); + } + + inode_ref.inode.set_size(new_size as u64); + self.write_back_inode(&mut inode_ref); + + // Verify file size update + let verify_inode = self.get_inode_ref(inode); + if verify_inode.inode.size() != new_size as u64 { + log::error!("[Write] File size update verification failed: expected {}, got {}", + new_size, verify_inode.inode.size()); + return return_errno_with_message!(Errno::EIO, "File size update verification failed"); + } + } + + log::info!("=== Write Performance Summary ==="); + log::info!("[Blocks] Total blocks: {}, New blocks: {}, Aligned blocks: {}", + total_blocks, new_blocks, aligned_blocks); + log::info!("[Bytes] Total written: {}", written); + log::info!("[File] Final size: {}", inode_ref.inode.size()); + log::info!("=== End of Write Analysis ==="); + + Ok(written) + } + + /// File remove + /// + /// Params: + /// path: file path start from root + /// + /// Returns: + /// `Result` - status of the operation + pub fn file_remove(&self, path: &str) -> Result { + // start from root + let mut parent_inode_num = ROOT_INODE; + + let mut nameoff = 0; + let child_inode = self.generic_open(path, &mut parent_inode_num, false, 0, &mut nameoff)?; + + let mut child_inode_ref = self.get_inode_ref(child_inode); + let child_link_cnt = child_inode_ref.inode.links_count(); + if child_link_cnt == 1 { + self.truncate_inode(&mut child_inode_ref, 0)?; + } + + // get child name + let mut is_goal = false; + let p = &path[nameoff as usize..]; + let len = path_check(p, &mut is_goal); + + // load parent + let mut parent_inode_ref = self.get_inode_ref(parent_inode_num); + + let r = self.unlink( + &mut parent_inode_ref, + &mut child_inode_ref, + &p[..len], + )?; + + + Ok(EOK) + } + + /// File truncate + /// + /// Params: + /// inode_ref: &mut Ext4InodeRef - inode reference + /// new_size: u64 - new size of the file + /// + /// Returns: + /// `Result` - status of the operation + pub fn truncate_inode(&self, inode_ref: &mut Ext4InodeRef, new_size: u64) -> Result { + let old_size = inode_ref.inode.size(); + + assert!(old_size > new_size); + + if old_size == new_size { + return Ok(EOK); + } + + let block_size = BLOCK_SIZE as u64; + let new_blocks_cnt = ((new_size + block_size - 1) / block_size) as u32; + let old_blocks_cnt = ((old_size + block_size - 1) / block_size) as u32; + let diff_blocks_cnt = old_blocks_cnt - new_blocks_cnt; + + if diff_blocks_cnt > 0{ + self.extent_remove_space(inode_ref, new_blocks_cnt, EXT_MAX_BLOCKS)?; + } + + inode_ref.inode.set_size(new_size); + self.write_back_inode(inode_ref); + + Ok(EOK) + } +} + +//// Write Performance Analysis +// impl Ext4 { + // /// Write data to a file at a given offset + // /// + // /// Params: + // /// inode: u32 - inode number of the file + // /// offset: usize - offset from where to write + // /// write_buf: &[u8] - buffer to write the data from + // /// + // /// Returns: + // /// `Result` - number of bytes written + // pub fn write_at(&self, inode: u32, offset: usize, write_buf: &[u8]) -> Result { + // let total_start = Instant::now(); + // log::info!("=== Write Performance Analysis ==="); + // log::info!("Write size: {} bytes", write_buf.len()); + + // // write buf is empty, return 0 + // let write_buf_len = write_buf.len(); + // if write_buf_len == 0 { + // return Ok(0); + // } + + // // get the inode reference + // let inode_start = Instant::now(); + // let mut inode_ref = self.get_inode_ref(inode); + // let inode_time = inode_start.elapsed(); + // log::info!("[Time] Get inode: {:.3}ms", inode_time.as_secs_f64() * 1000.0); + + // // Get the file size + // let file_size = inode_ref.inode.size(); + + // // Calculate the start and end block index + // let iblock_start = offset / BLOCK_SIZE; + // let iblock_last = (offset + write_buf_len + BLOCK_SIZE - 1) / BLOCK_SIZE; + // let total_blocks_needed = iblock_last - iblock_start; + // log::info!("[Blocks] Start block: {}, Last block: {}, Total blocks needed: {}", + // iblock_start, iblock_last, total_blocks_needed); + + // // start block index + // let mut iblk_idx = iblock_start; + // let ifile_blocks = (file_size + BLOCK_SIZE as u64 - 1) / BLOCK_SIZE as u64; + + // // Calculate the unaligned size + // let unaligned = offset % BLOCK_SIZE; + // if unaligned > 0 { + // log::info!("[Alignment] Unaligned start: {} bytes", unaligned); + // } + + // // Buffer to keep track of written bytes + // let mut written = 0; + // let mut total_blocks = 0; + // let mut new_blocks = 0; + // let mut total_alloc_time = Duration::new(0, 0); + // let mut total_write_time = Duration::new(0, 0); + // let mut total_sync_time = Duration::new(0, 0); + + // // Start bgid for block allocation + // let mut start_bgid = 1; + + // // Pre-allocate blocks if needed + // let blocks_to_allocate = if iblk_idx >= ifile_blocks as usize { + // total_blocks_needed + // } else { + // max(0, total_blocks_needed - (ifile_blocks as usize - iblk_idx)) + // }; + + // if blocks_to_allocate > 0 { + // let prealloc_start = Instant::now(); + // log::info!("[Pre-allocation] Allocating {} blocks", blocks_to_allocate); + + // // Use the new batch allocation function + // let allocated_blocks = self.balloc_alloc_block_new(&mut inode_ref, &mut start_bgid, blocks_to_allocate)?; + + // // Create a single extent for all allocated blocks + // if !allocated_blocks.is_empty() { + // let mut newex = Ext4Extent::default(); + // newex.first_block = iblk_idx as u32; + // newex.store_pblock(allocated_blocks[0]); + // newex.block_count = allocated_blocks.len() as u16; + // self.insert_extent(&mut inode_ref, &mut newex)?; + // } + + // let prealloc_time = prealloc_start.elapsed(); + // log::info!("[Time] Pre-allocation: {:.3}ms", prealloc_time.as_secs_f64() * 1000.0); + // new_blocks += blocks_to_allocate; + // } + + // // Unaligned write + // if unaligned > 0 { + // let unaligned_start = Instant::now(); + // let len = min(write_buf_len, BLOCK_SIZE - unaligned); + // log::info!("[Unaligned Write] Writing {} bytes", len); + + // // Get the physical block id + // let pblock_start = Instant::now(); + // let pblock_idx = self.get_pblock_idx(&inode_ref, iblk_idx as u32)?; + // let alloc_time = pblock_start.elapsed(); + // total_alloc_time += alloc_time; + // total_blocks += 1; + + // let write_start = Instant::now(); + // let mut block = Block::load(self.block_device.clone(), pblock_idx as usize * BLOCK_SIZE); + // block.write_offset(unaligned, &write_buf[..len], len); + // let write_time = write_start.elapsed(); + // total_write_time += write_time; + + // let sync_start = Instant::now(); + // block.sync_blk_to_disk(self.block_device.clone()); + // let sync_time = sync_start.elapsed(); + // total_sync_time += sync_time; + // drop(block); + + // written += len; + // iblk_idx += 1; + + // let unaligned_time = unaligned_start.elapsed(); + // log::info!("[Time] Total unaligned write: {:.3}ms", unaligned_time.as_secs_f64() * 1000.0); + // } + + // // Aligned write + // let aligned_start = Instant::now(); + // let mut aligned_blocks = 0; + // log::info!("[Aligned Write] Starting aligned writes for {} blocks", (write_buf_len - written + BLOCK_SIZE - 1) / BLOCK_SIZE); + + // while written < write_buf_len { + // aligned_blocks += 1; + + // // Get the physical block id + // let pblock_start = Instant::now(); + // let pblock_idx = self.get_pblock_idx(&inode_ref, iblk_idx as u32)?; + // let alloc_time = pblock_start.elapsed(); + // total_alloc_time += alloc_time; + // total_blocks += 1; + + // let write_start = Instant::now(); + // let block_offset = pblock_idx as usize * BLOCK_SIZE; + // let mut block = Block::load(self.block_device.clone(), block_offset); + // let write_size = min(BLOCK_SIZE, write_buf_len - written); + // block.write_offset(0, &write_buf[written..written + write_size], write_size); + // let write_time = write_start.elapsed(); + // total_write_time += write_time; + + // let sync_start = Instant::now(); + // block.sync_blk_to_disk(self.block_device.clone()); + // let sync_time = sync_start.elapsed(); + // total_sync_time += sync_time; + // drop(block); + + // written += write_size; + // iblk_idx += 1; + + // if aligned_blocks % 1000 == 0 { + // log::info!("[Progress] Written {} blocks, {} bytes", aligned_blocks, written); + // } + // } + + // let aligned_time = aligned_start.elapsed(); + // log::info!("[Time] Total aligned write: {:.3}ms", aligned_time.as_secs_f64() * 1000.0); + + // // Update file size if necessary + // let update_start = Instant::now(); + // if offset + written > file_size as usize { + // inode_ref.inode.set_size((offset + write_buf_len) as u64); + // self.write_back_inode(&mut inode_ref); + // } + // let update_time = update_start.elapsed(); + // log::info!("[Time] Inode update: {:.3}ms", update_time.as_secs_f64() * 1000.0); + + // let total_time = total_start.elapsed(); + // log::info!("=== Write Performance Summary ==="); + // log::info!("[Blocks] Total blocks: {}, New blocks: {}, Aligned blocks: {}", + // total_blocks, new_blocks, aligned_blocks); + // log::info!("[Time] Average block allocation: {:.3}ms", + // (total_alloc_time.as_secs_f64() * 1000.0) / total_blocks as f64); + // log::info!("[Time] Average block write: {:.3}ms", + // (total_write_time.as_secs_f64() * 1000.0) / total_blocks as f64); + // log::info!("[Time] Average block sync: {:.3}ms", + // (total_sync_time.as_secs_f64() * 1000.0) / total_blocks as f64); + // log::info!("[Time] Total write time: {:.3}ms", total_time.as_secs_f64() * 1000.0); + // log::info!("[Speed] Write speed: {:.2} MB/s", + // (write_buf_len as f64 / 1024.0 / 1024.0) / total_time.as_secs_f64()); + // log::info!("[Efficiency] Write efficiency: {:.2}%", + // (written as f64 / (total_blocks * BLOCK_SIZE) as f64) * 100.0); + // log::info!("=== End of Write Analysis ==="); + + // Ok(written) + // } +// } \ No newline at end of file diff --git a/vendor/ext4_rs/src/ext4_impls/ialloc.rs b/vendor/ext4_rs/src/ext4_impls/ialloc.rs new file mode 100644 index 00000000..9a613334 --- /dev/null +++ b/vendor/ext4_rs/src/ext4_impls/ialloc.rs @@ -0,0 +1,122 @@ +use crate::ext4_defs::*; +use crate::prelude::*; +use crate::return_errno_with_message; +use crate::utils::bitmap::*; + +impl Ext4 { + pub fn ialloc_alloc_inode(&self, is_dir: bool) -> Result { + let mut bgid = 0; + let bg_count = self.super_block.block_group_count(); + let mut super_block = self.super_block; + + while bgid <= bg_count { + if bgid == bg_count { + bgid = 0; + continue; + } + + let mut bg = + Ext4BlockGroup::load_new(self.block_device.clone(), &super_block, bgid as usize); + + let mut free_inodes = bg.get_free_inodes_count(); + + if free_inodes > 0 { + let inode_bitmap_block = bg.get_inode_bitmap_block(&super_block); + + let mut raw_data = self + .block_device + .read_offset(inode_bitmap_block as usize * BLOCK_SIZE); + + let inodes_in_bg = super_block.get_inodes_in_group_cnt(bgid); + + let mut bitmap_data = &mut raw_data[..]; + + let mut idx_in_bg = 0; + + ext4_bmap_bit_find_clr(bitmap_data, 0, inodes_in_bg, &mut idx_in_bg); + ext4_bmap_bit_set(bitmap_data, idx_in_bg); + + // update bitmap in disk + self.block_device + .write_offset(inode_bitmap_block as usize * BLOCK_SIZE, bitmap_data); + + bg.set_block_group_ialloc_bitmap_csum(&super_block, bitmap_data); + + /* Modify filesystem counters */ + free_inodes -= 1; + bg.set_free_inodes_count(&super_block, free_inodes); + + /* Increment used directories counter */ + if is_dir { + let used_dirs = bg.get_used_dirs_count(&super_block) + 1; + bg.set_used_dirs_count(&super_block, used_dirs); + } + + /* Decrease unused inodes count */ + let mut unused = bg.get_itable_unused(&super_block); + let free = inodes_in_bg - unused; + if idx_in_bg >= free { + unused = inodes_in_bg - (idx_in_bg + 1); + bg.set_itable_unused(&super_block, unused); + } + + bg.sync_to_disk_with_csum(self.block_device.clone(), bgid as usize, &super_block); + + /* Update superblock */ + super_block.decrease_free_inodes_count(); + super_block.sync_to_disk_with_csum(self.block_device.clone()); + + /* Compute the absolute i-nodex number */ + let inodes_per_group = super_block.inodes_per_group(); + let inode_num = bgid * inodes_per_group + (idx_in_bg + 1); + + return Ok(inode_num); + } + + bgid += 1; + } + + return_errno_with_message!(Errno::ENOSPC, "alloc inode fail"); + } + + pub fn ialloc_free_inode(&self, index: u32, is_dir: bool) { + // Compute index of block group + let bgid = self.get_bgid_of_inode(index); + let block_device = self.block_device.clone(); + + let mut super_block = self.super_block; + let mut bg = + Ext4BlockGroup::load_new(self.block_device.clone(), &super_block, bgid as usize); + + // Load inode bitmap block + let inode_bitmap_block = bg.get_inode_bitmap_block(&self.super_block); + let mut bitmap_data = self + .block_device + .read_offset(inode_bitmap_block as usize * BLOCK_SIZE); + + // Find index within group and clear bit + let index_in_group = self.inode_to_bgidx(index); + ext4_bmap_bit_clr(&mut bitmap_data, index_in_group); + + // Set new checksum after modification + // update bitmap in disk + self.block_device + .write_offset(inode_bitmap_block as usize * BLOCK_SIZE, &bitmap_data); + bg.set_block_group_ialloc_bitmap_csum(&super_block, &bitmap_data); + + // Update free inodes count in block group + let free_inodes = bg.get_free_inodes_count() + 1; + bg.set_free_inodes_count(&self.super_block, free_inodes); + + // If inode was a directory, decrement the used directories count + if is_dir { + let used_dirs = bg.get_used_dirs_count(&self.super_block) - 1; + bg.set_used_dirs_count(&self.super_block, used_dirs); + } + + bg.sync_to_disk_with_csum(block_device.clone(), bgid as usize, &super_block); + + super_block.decrease_free_inodes_count(); + super_block.sync_to_disk_with_csum(self.block_device.clone()); + } +} diff --git a/vendor/ext4_rs/src/ext4_impls/inode.rs b/vendor/ext4_rs/src/ext4_impls/inode.rs new file mode 100644 index 00000000..c25231c3 --- /dev/null +++ b/vendor/ext4_rs/src/ext4_impls/inode.rs @@ -0,0 +1,511 @@ +use bitflags::Flags; + +use crate::ext4_defs::*; +use crate::prelude::*; +use crate::return_errno_with_message; +use crate::utils::bitmap::*; + +impl Ext4 { + pub fn get_bgid_of_inode(&self, inode_num: u32) -> u32 { + inode_num / self.super_block.inodes_per_group() + } + + pub fn inode_to_bgidx(&self, inode_num: u32) -> u32 { + inode_num % self.super_block.inodes_per_group() + } + + /// Get inode disk position. + pub fn inode_disk_pos(&self, inode_num: u32) -> usize { + let super_block = self.super_block; + let inodes_per_group = super_block.inodes_per_group; + let inode_size = super_block.inode_size as u64; + let group = (inode_num - 1) / inodes_per_group; + let index = (inode_num - 1) % inodes_per_group; + let block_group = + Ext4BlockGroup::load_new(self.block_device.clone(), &super_block, group as usize); + let inode_table_blk_num = block_group.get_inode_table_blk_num(); + + inode_table_blk_num as usize * BLOCK_SIZE + index as usize * inode_size as usize + } + + /// Load the inode reference from the disk. + pub fn get_inode_ref(&self, inode_num: u32) -> Ext4InodeRef { + let offset = self.inode_disk_pos(inode_num); + + let mut ext4block = Block::load(self.block_device.clone(), offset); + + let inode: &mut Ext4Inode = ext4block.read_as_mut(); + + Ext4InodeRef { + inode_num, + inode: *inode, + } + } + + /// write back inode with checksum + pub fn write_back_inode(&self, inode_ref: &mut Ext4InodeRef) { + let inode_pos = self.inode_disk_pos(inode_ref.inode_num); + + // make sure self.super_block is up-to-date + inode_ref + .inode + .set_inode_checksum(&self.super_block, inode_ref.inode_num); + inode_ref + .inode + .sync_inode_to_disk(self.block_device.clone(), inode_pos); + } + + /// write back inode with checksum + pub fn write_back_inode_without_csum(&self, inode_ref: &Ext4InodeRef) { + let inode_pos = self.inode_disk_pos(inode_ref.inode_num); + + inode_ref + .inode + .sync_inode_to_disk(self.block_device.clone(), inode_pos); + } + + /// Get physical block id of a logical block. + /// + /// Params: + /// inode_ref: &Ext4InodeRef - inode reference + /// lblock: Ext4Lblk - logical block id + /// + /// Returns: + /// `Result` - physical block id + pub fn get_pblock_idx(&self, inode_ref: &Ext4InodeRef, lblock: Ext4Lblk) -> Result { + let search_path = self.find_extent(inode_ref, lblock); + if let Ok(path) = search_path { + // get the last path + let path = path.path.last().unwrap(); + + // get physical block id + let fblock = path.pblock; + + return Ok(fblock); + } + + return_errno_with_message!(Errno::EIO, "search extent fail"); + } + + /// Allocate a new block + pub fn allocate_new_block(&self, inode_ref: &mut Ext4InodeRef) -> Result { + let mut super_block = self.super_block; + let inodes_per_group = super_block.inodes_per_group(); + let bgid = (inode_ref.inode_num - 1) / inodes_per_group; + let index = (inode_ref.inode_num - 1) % inodes_per_group; + + // load block group + let mut block_group = + Ext4BlockGroup::load_new(self.block_device.clone(), &super_block, bgid as usize); + + let block_bitmap_block = block_group.get_block_bitmap_block(&super_block); + + let mut block_bmap_raw_data = self + .block_device + .read_offset(block_bitmap_block as usize * BLOCK_SIZE); + let mut data: &mut Vec = &mut block_bmap_raw_data; + let mut rel_blk_idx = 0; + + ext4_bmap_bit_find_clr(data, index, 0x8000, &mut rel_blk_idx); + ext4_bmap_bit_set(data, rel_blk_idx); + + block_group.set_block_group_balloc_bitmap_csum(&super_block, data); + self.block_device + .write_offset(block_bitmap_block as usize * BLOCK_SIZE, data); + + /* Update superblock free blocks count */ + let mut super_blk_free_blocks = super_block.free_blocks_count(); + super_blk_free_blocks -= 1; + super_block.set_free_blocks_count(super_blk_free_blocks); + super_block.sync_to_disk_with_csum(self.block_device.clone()); + + /* Update inode blocks (different block size!) count */ + let mut inode_blocks = inode_ref.inode.blocks_count(); + inode_blocks += (BLOCK_SIZE / EXT4_INODE_BLOCK_SIZE) as u64; + inode_ref.inode.set_blocks_count(inode_blocks); + self.write_back_inode(inode_ref); + + /* Update block group free blocks count */ + let mut fb_cnt = block_group.get_free_blocks_count(); + fb_cnt -= 1; + block_group.set_free_blocks_count(fb_cnt as u32); + block_group.sync_to_disk_with_csum(self.block_device.clone(), bgid as usize, &super_block); + + Ok(rel_blk_idx as Ext4Fsblk) + } + + /// Append a new block to the inode and update the extent tree. + /// + /// Params: + /// inode_ref: &mut Ext4InodeRef - inode reference + /// iblock: Ext4Lblk - logical block id + /// + /// Returns: + /// `Result` - physical block id of the new block + pub fn append_inode_pblk(&self, inode_ref: &mut Ext4InodeRef) -> Result { + let inode_size = inode_ref.inode.size(); + let iblock = ((inode_size as usize + BLOCK_SIZE - 1) / BLOCK_SIZE) as u32; + + let mut newex: Ext4Extent = Ext4Extent::default(); + + let new_block = self.balloc_alloc_block(inode_ref, None)?; + + newex.first_block = iblock; + newex.store_pblock(new_block); + newex.block_count = min(1, EXT_MAX_BLOCKS - iblock) as u16; + + self.insert_extent(inode_ref, &mut newex)?; + + // Update the inode size + let mut inode_size = inode_ref.inode.size(); + inode_size += BLOCK_SIZE as u64; + inode_ref.inode.set_size(inode_size); + self.write_back_inode(inode_ref); + + Ok(new_block) + } + + /// Append a new block to the inode and update the extent tree.From a specific bgid + /// + /// Params: + /// inode_ref: &mut Ext4InodeRef - inode reference + /// bgid: Start bgid of free block search + /// + /// Returns: + /// `Result` - physical block id of the new block + pub fn append_inode_pblk_from( + &self, + inode_ref: &mut Ext4InodeRef, + start_bgid: &mut u32, + ) -> Result { + let inode_size = inode_ref.inode.size(); + let iblock = ((inode_size as usize + BLOCK_SIZE - 1) / BLOCK_SIZE) as u32; + + let mut newex: Ext4Extent = Ext4Extent::default(); + + let new_block = self.balloc_alloc_block_from(inode_ref, start_bgid)?; + + newex.first_block = iblock; + newex.store_pblock(new_block); + newex.block_count = min(1, EXT_MAX_BLOCKS - iblock) as u16; + + self.insert_extent(inode_ref, &mut newex)?; + + // Update the inode size + let mut inode_size = inode_ref.inode.size(); + inode_size += BLOCK_SIZE as u64; + inode_ref.inode.set_size(inode_size); + self.write_back_inode(inode_ref); + + Ok(new_block) + } + + /// Allocate a new inode + /// + /// Params: + /// inode_mode: u16 - inode mode + /// + /// Returns: + /// `Result` - inode number + pub fn alloc_inode(&self, is_dir: bool) -> Result { + // Allocate inode + let inode_num = self.ialloc_alloc_inode(is_dir)?; + + Ok(inode_num) + } + + pub fn correspond_inode_mode(&self, filetype: u8) -> u16 { + let file_type = DirEntryType::from_bits(filetype).unwrap(); + match file_type { + DirEntryType::EXT4_DE_REG_FILE => InodeFileType::S_IFREG.bits(), + DirEntryType::EXT4_DE_DIR => InodeFileType::S_IFDIR.bits(), + DirEntryType::EXT4_DE_SYMLINK => InodeFileType::S_IFLNK.bits(), + DirEntryType::EXT4_DE_CHRDEV => InodeFileType::S_IFCHR.bits(), + DirEntryType::EXT4_DE_BLKDEV => InodeFileType::S_IFBLK.bits(), + DirEntryType::EXT4_DE_FIFO => InodeFileType::S_IFIFO.bits(), + DirEntryType::EXT4_DE_SOCK => InodeFileType::S_IFSOCK.bits(), + _ => { + // FIXME: unsupported filetype + InodeFileType::S_IFREG.bits() + } + } + } + + /// Append multiple blocks to the inode and update the extent tree. + /// + /// Params: + /// inode_ref: &mut Ext4InodeRef - inode reference + /// start_bgid: &mut u32 - start block group id for allocation + /// block_count: usize - number of blocks to allocate + /// + /// Returns: + /// `Result>` - vector of physical block ids of the new blocks + pub fn append_inode_pblk_batch( + &self, + inode_ref: &mut Ext4InodeRef, + start_bgid: &mut u32, + block_count: usize, + ) -> Result> { + let inode_size = inode_ref.inode.size(); + let iblock = ((inode_size as usize + BLOCK_SIZE - 1) / BLOCK_SIZE) as u32; + + // Use new optimized block allocation function + let allocated_blocks = self.balloc_alloc_block_batch(inode_ref, start_bgid, block_count)?; + + if allocated_blocks.is_empty() { + log::warn!("[Batch Append] No blocks could be allocated"); + return Ok(Vec::new()); + } + + // Record the actual number of allocated blocks + let actual_allocated = allocated_blocks.len(); + if actual_allocated < block_count { + log::warn!( + "[Batch Append] Partial allocation: {}/{} blocks", + actual_allocated, + block_count + ); + } + + // Check the current state of the extent tree + let root_header = inode_ref.inode.root_extent_header(); + log::info!( + "[Batch Append] Current extent tree state: magic={:x}, entries={}, max={}, depth={}", + root_header.magic, + root_header.entries_count, + root_header.max_entries_count, + root_header.depth + ); + + // Find the starting logical block position + let mut current_iblk = iblock; + let mut last_extent_end = if root_header.entries_count > 0 { + // Get the end position of the last extent + let last_extent = match self.get_last_extent(inode_ref) { + Ok(extent) => extent.first_block + extent.block_count as u32, + Err(_) => { + log::warn!( + "[Batch Append] Could not get last extent, starting at block {}", + iblock + ); + iblock + } + }; + last_extent + } else { + 0 + }; + + // Ensure new extents start after the end of the last extent + if current_iblk < last_extent_end { + current_iblk = last_extent_end; + } + + // Group allocated physical blocks into contiguous segments + let mut contiguous_segments = Vec::new(); + let mut current_segment = Vec::new(); + + // Add the first block to the current segment + if !allocated_blocks.is_empty() { + current_segment.push(allocated_blocks[0]); + } + + // Check for continuity starting from the second block + for i in 1..allocated_blocks.len() { + let prev_block = allocated_blocks[i - 1]; + let curr_block = allocated_blocks[i]; + + // If the current block is contiguous with the previous block + if curr_block == prev_block + 1 { + current_segment.push(curr_block); + } else { + // If not contiguous, end the current segment and start a new one + if !current_segment.is_empty() { + contiguous_segments.push(current_segment); + current_segment = Vec::new(); + } + current_segment.push(curr_block); + } + } + + // Add the last segment + if !current_segment.is_empty() { + contiguous_segments.push(current_segment); + } + + log::info!( + "[Batch Append] Split {} allocated blocks into {} contiguous segments", + allocated_blocks.len(), + contiguous_segments.len() + ); + + // Define maximum extent length + const MAX_EXTENT_LENGTH: usize = EXT_INIT_MAX_LEN as usize; + + // Create extents for each contiguous segment + for segment in contiguous_segments { + if segment.is_empty() { + continue; + } + + // If segment length exceeds maximum extent length, split + let mut segment_start = 0; + while segment_start < segment.len() { + // Calculate current segment length, ensuring it doesn't exceed MAX_EXTENT_LENGTH + let sub_segment_length = + core::cmp::min(MAX_EXTENT_LENGTH, segment.len() - segment_start); + let first_physical_block = segment[segment_start]; + + // Create new extent + let mut newex = Ext4Extent::default(); + newex.first_block = current_iblk; + newex.store_pblock(first_physical_block); + newex.block_count = sub_segment_length as u16; + + log::info!("[Batch Append] Inserting extent: first_block={}, block_count={}, physical_block={}", + current_iblk, sub_segment_length, first_physical_block); + + // Validate extent validity + if !self.is_valid_extent(&newex, inode_ref) { + log::error!( + "[Batch Append] Invalid extent detected: first_block={}, block_count={}", + newex.first_block, + newex.block_count + ); + return return_errno_with_message!(Errno::EINVAL, "Invalid extent detected"); + } + + // Insert extent + self.insert_extent(inode_ref, &mut newex)?; + + // Update next logical block position + current_iblk = match current_iblk.checked_add(sub_segment_length as u32) { + Some(v) => v, + None => { + return return_errno_with_message!( + Errno::EINVAL, + "Logical block number overflow" + ) + } + }; + + // Move to next segment + segment_start += sub_segment_length; + } + + // Update end position of last extent + last_extent_end = current_iblk; + + // Validate extent tree state + let root_header = inode_ref.inode.root_extent_header(); + log::info!("[Batch Append] Updated extent tree state: magic={:x}, entries={}, max={}, depth={}", + root_header.magic, + root_header.entries_count, + root_header.max_entries_count, + root_header.depth); + } + + // Update inode size, ensuring it doesn't overflow + let new_size = match inode_size.checked_add((allocated_blocks.len() * BLOCK_SIZE) as u64) { + Some(v) => v, + None => return return_errno_with_message!(Errno::EINVAL, "File size overflow"), + }; + inode_ref.inode.set_size(new_size); + self.write_back_inode(inode_ref); + + Ok(allocated_blocks) + } + + /// Get the last extent in the extent tree + fn get_last_extent(&self, inode_ref: &Ext4InodeRef) -> Result { + let root_header = inode_ref.inode.root_extent_header(); + if root_header.entries_count == 0 { + return return_errno_with_message!(Errno::ENOENT, "No extents found"); + } + + let mut current_header = root_header; + let mut current_block = inode_ref.inode.root_extent_block(); + let mut depth = root_header.depth; + + while depth > 0 { + let index_block = Block::load( + self.block_device.clone(), + current_block as usize * BLOCK_SIZE, + ); + let index_header = Ext4ExtentHeader::load_from_u8(&index_block.data[..]); + if index_header.entries_count == 0 { + return return_errno_with_message!(Errno::ENOENT, "Invalid extent tree"); + } + + // Get the last index entry + let last_idx = Ext4ExtentIndex::load_from_u8( + &index_block.data[EXT4_EXTENT_HEADER_SIZE + + (index_header.entries_count - 1) as usize * EXT4_EXTENT_INDEX_SIZE..], + ); + current_block = last_idx.leaf_lo as u64 | ((last_idx.leaf_hi as u64) << 32); + current_header = index_header; + depth -= 1; + } + + // Get the last extent entry + let extent_block = Block::load( + self.block_device.clone(), + current_block as usize * BLOCK_SIZE, + ); + let extent_header = Ext4ExtentHeader::load_from_u8(&extent_block.data[..]); + if extent_header.entries_count == 0 { + return return_errno_with_message!(Errno::ENOENT, "No extent entries found"); + } + + let last_extent = Ext4Extent::load_from_u8( + &extent_block.data[EXT4_EXTENT_HEADER_SIZE + + (extent_header.entries_count - 1) as usize * EXT4_EXTENT_SIZE..], + ); + + Ok(last_extent) + } + + /// Validate an extent + fn is_valid_extent(&self, extent: &Ext4Extent, inode_ref: &Ext4InodeRef) -> bool { + // Check if the extent is within valid range + if extent.first_block >= EXT_MAX_BLOCKS { + log::error!( + "[Extent Validation] Extent first block {} exceeds maximum", + extent.first_block + ); + return false; + } + + // Check if the extent length is valid + if extent.block_count == 0 || extent.block_count > EXT_INIT_MAX_LEN { + log::error!( + "[Extent Validation] Invalid extent length {}", + extent.block_count + ); + return false; + } + + // Check if the extent would cause overflow + if let Some(end_block) = extent.first_block.checked_add(extent.block_count as u32) { + if end_block > EXT_MAX_BLOCKS { + log::error!( + "[Extent Validation] Extent end block {} exceeds maximum", + end_block + ); + return false; + } + } else { + log::error!("[Extent Validation] Extent block range overflow"); + return false; + } + + // Check if the physical block is valid + let pblock = extent.get_pblock(); + if pblock == 0 { + log::error!("[Extent Validation] Invalid physical block number"); + return false; + } + + true + } +} diff --git a/vendor/ext4_rs/src/ext4_impls/mod.rs b/vendor/ext4_rs/src/ext4_impls/mod.rs new file mode 100644 index 00000000..8c64124b --- /dev/null +++ b/vendor/ext4_rs/src/ext4_impls/mod.rs @@ -0,0 +1,15 @@ +pub mod extents; +pub mod ext4; +pub mod inode; +pub mod dir; +pub mod file; +pub mod ialloc; +pub mod balloc; + +pub use extents::*; +pub use ext4::*; +pub use inode::*; +pub use dir::*; +pub use file::*; +pub use ialloc::*; +pub use balloc::*; \ No newline at end of file diff --git a/vendor/ext4_rs/src/fuse_interface/mod.rs b/vendor/ext4_rs/src/fuse_interface/mod.rs new file mode 100644 index 00000000..ceceeb36 --- /dev/null +++ b/vendor/ext4_rs/src/fuse_interface/mod.rs @@ -0,0 +1,683 @@ +use crate::prelude::*; + +use crate::ext4_defs::*; +use crate::return_errno; +use crate::return_errno_with_message; +use crate::utils::path_check; + +// export some definitions +pub use crate::ext4_defs::Ext4; +pub use crate::ext4_defs::BLOCK_SIZE; +pub use crate::ext4_defs::BlockDevice; +pub use crate::ext4_defs::InodeFileType; + +/// fuser interface for ext4 +impl Ext4 { + /// Look up a directory entry by name and get its attributes. + pub fn fuse_lookup(&self, parent: u64, name: &str) -> Result { + let mut search_result = Ext4DirSearchResult::new(Ext4DirEntry::default()); + + self.dir_find_entry(parent as u32, name, &mut search_result)?; + + let inode_num = search_result.dentry.inode; + + let inode_ref = self.get_inode_ref(inode_num); + let file_attr = FileAttr::from_inode_ref(&inode_ref); + + Ok(file_attr) + } + + /// Get file attributes. + pub fn fuse_getattr(&self, ino: u64) -> Result { + let inode_ref = self.get_inode_ref(ino as u32); + let file_attr = FileAttr::from_inode_ref(&inode_ref); + Ok(file_attr) + } + + /// Set file attributes. + pub fn fuse_setattr( + &self, + ino: u64, + mode: Option, + uid: Option, + gid: Option, + size: Option, + atime: Option, + mtime: Option, + ctime: Option, + fh: Option, + crtime: Option, + chgtime: Option, + bkuptime: Option, + flags: Option, + ) { + let mut inode_ref = self.get_inode_ref(ino as u32); + + let mut attr = FileAttr::default(); + + if let Some(mode) = mode { + let inode_file_type = + InodeFileType::from_bits(mode as u16 & EXT4_INODE_MODE_TYPE_MASK).unwrap(); + attr.kind = inode_file_type; + let inode_perm = InodePerm::from_bits(mode as u16 & EXT4_INODE_MODE_PERM_MASK).unwrap(); + attr.perm = inode_perm; + } + + if let Some(uid) = uid { + attr.uid = uid + } + + if let Some(gid) = gid { + attr.gid = gid + } + + if let Some(size) = size { + attr.size = size + } + + if let Some(atime) = atime { + attr.atime = atime + } + + if let Some(mtime) = mtime { + attr.mtime = mtime + } + + if let Some(ctime) = ctime { + attr.ctime = ctime + } + + if let Some(crtime) = crtime { + attr.crtime = crtime + } + + if let Some(chgtime) = chgtime { + attr.chgtime = chgtime + } + + if let Some(bkuptime) = bkuptime { + attr.bkuptime = bkuptime + } + + if let Some(flags) = flags { + attr.flags = flags + } + + inode_ref.set_attr(&attr); + + self.write_back_inode(&mut inode_ref); + } + + /// Read symbolic link. + fn fuse_readlink(&mut self, ino: u64) -> Result> { + let inode_ref = self.get_inode_ref(ino as u32); + let file_size = inode_ref.inode.size(); + let mut read_buf = vec![0; file_size as usize]; + let read_size = self.read_at(ino as u32, 0, &mut read_buf)?; + Ok(read_buf) + } + + + /// Create a regular file, character device, block device, fifo or socket node. + pub fn fuse_mknod( + &self, + parent: u64, + name: &str, + mode: u32, + umask: u32, + rdev: u32, + ) -> Result { + let mut search_result = Ext4DirSearchResult::new(Ext4DirEntry::default()); + let r = self.dir_find_entry(parent as u32, name, &mut search_result); + if r.is_ok() { + return_errno!(Errno::EEXIST); + } + let inode_ref = self.create(parent as u32, name, mode as u16)?; + Ok(inode_ref) + } + + + /// Create a regular file, character device, block device, fifo or socket node. + pub fn fuse_mknod_with_attr( + &self, + parent: u64, + name: &str, + mode: u32, + umask: u32, + rdev: u32, + uid: u32, + gid: u32, + ) -> Result { + let mut search_result = Ext4DirSearchResult::new(Ext4DirEntry::default()); + let r = self.dir_find_entry(parent as u32, name, &mut search_result); + if r.is_ok() { + return_errno!(Errno::EEXIST); + } + let inode_ref = self.create_with_attr(parent as u32, name, mode as u16, uid as u16, gid as u16)?; + Ok(inode_ref) + } + + /// Create a directory. + pub fn fuse_mkdir(&mut self, parent: u64, name: &str, mode: u32, umask: u32) -> Result { + let mut search_result = Ext4DirSearchResult::new(Ext4DirEntry::default()); + let r = self.dir_find_entry(parent as u32, name, &mut search_result); + if r.is_ok() { + return_errno!(Errno::EEXIST); + } + let file_type = InodeFileType::from_bits(mode as u16).unwrap(); + if file_type != InodeFileType::S_IFDIR { + // The mode is not a directory + return_errno_with_message!(Errno::EINVAL, "Invalid mode for directory creation"); + } + let inode_ref = self.create(parent as u32, name, mode as u16)?; + Ok(EOK) + } + + /// Create a directory. + pub fn fuse_mkdir_with_attr(&mut self, parent: u64, name: &str, mode: u32, umask: u32, uid:u32, gid:u32) -> Result { + + let mut search_result = Ext4DirSearchResult::new(Ext4DirEntry::default()); + let r = self.dir_find_entry(parent as u32, name, &mut search_result); + if r.is_ok() { + return_errno!(Errno::EEXIST); + } + + // mkdir via fuse passes a mode of 0. so we need to set default mode + let file_type = match InodeFileType::from_bits(mode as u16) { + Some(file_type) => file_type, + None => InodeFileType::S_IFDIR, + }; + let mode = file_type.bits(); + let inode_ref = self.create_with_attr(parent as u32, name, mode, uid as u16, gid as u16)?; + + Ok(inode_ref) + } + + /// Remove a file. + pub fn fuse_unlink(&self, parent: u64, name: &str) -> Result { + // unlink actual remove a file + + // get child inode num + let mut parent_inode = parent as u32; + let mut nameoff = 0; + let child_inode = self.generic_open(name, &mut parent_inode, false, 0, &mut nameoff)?; + + let mut child_inode_ref = self.get_inode_ref(child_inode); + let child_link_cnt = child_inode_ref.inode.links_count(); + if child_link_cnt == 1 { + self.truncate_inode(&mut child_inode_ref, 0)?; + } + + // get child name + let mut is_goal = false; + let p = &name[nameoff as usize..]; + let len = path_check(p, &mut is_goal); + + // load parent + let mut parent_inode_ref = self.get_inode_ref(parent_inode); + + let r = self.unlink( + &mut parent_inode_ref, + &mut child_inode_ref, + &p[..len], + )?; + + Ok(EOK) + } + /// Remove a directory. + pub fn fuse_rmdir(&mut self, parent: u64, name: &str) -> Result { + let mut search_result = Ext4DirSearchResult::new(Ext4DirEntry::default()); + + let r = self.dir_find_entry(parent as u32, name, &mut search_result)?; + + let mut parent_inode_ref = self.get_inode_ref(parent as u32); + let mut child_inode_ref = self.get_inode_ref(search_result.dentry.inode); + + self.truncate_inode(&mut child_inode_ref, 0)?; + + self.unlink(&mut parent_inode_ref, &mut child_inode_ref, name)?; + + self.write_back_inode(&mut parent_inode_ref); + + // to do + // ext4_inode_set_del_time + // ext4_inode_set_links_cnt + // ext4_fs_free_inode(&child) + + Ok(EOK) + } + /// Create a symbolic link. + pub fn fuse_symlink(&mut self, parent: u64, link_name: &str, target: &str) -> Result { + let mut search_result = Ext4DirSearchResult::new(Ext4DirEntry::default()); + let r = self.dir_find_entry(parent as u32, link_name, &mut search_result); + if r.is_ok() { + return_errno!(Errno::EEXIST); + } + + let mut mode = 0o777; + let file_type = InodeFileType::S_IFLNK; + mode |= file_type.bits(); + + let inode_ref = self.create(parent as u32, link_name, mode)?; + Ok(EOK) + } + /// Create a hard link. + /// Params: + /// ino: the inode number of the source file + /// newparent: the inode number of the new parent directory + /// newname: the name of the new file + /// + /// + pub fn fuse_link(&mut self, ino: u64, newparent: u64, newname: &str) -> Result { + let mut parent_inode_ref = self.get_inode_ref(newparent as u32); + let mut child_inode_ref = self.get_inode_ref(ino as u32); + + // to do if child already exists we should not add . and .. in child directory + self.link(&mut parent_inode_ref, &mut child_inode_ref, newname)?; + + Ok(EOK) + } + + /// Open a file. + /// Open flags (with the exception of O_CREAT, O_EXCL, O_NOCTTY and O_TRUNC) are + /// available in flags. Filesystem may store an arbitrary file handle (pointer, index, + /// etc) in fh, and use this in other all other file operations (read, write, flush, + /// release, fsync). Filesystem may also implement stateless file I/O and not store + /// anything in fh. There are also some flags (direct_io, keep_cache) which the + /// filesystem may set, to change the way the file is opened. See fuse_file_info + /// structure in for more details. + pub fn fuse_open(&mut self, ino: u64, flags: i32) -> Result { + let inode_ref = self.get_inode_ref(ino as u32); + + // check permission + let file_type = inode_ref.inode.file_type(); + let file_perm = inode_ref.inode.file_perm(); + + let can_read = file_perm.contains(InodePerm::S_IREAD); + let can_write = file_perm.contains(InodePerm::S_IWRITE); + let can_execute = file_perm.contains(InodePerm::S_IEXEC); + + // If trying to open the file in write mode, check for write permissions + if ((flags & O_WRONLY != 0) || (flags & O_RDWR != 0)) && !can_write { + return_errno_with_message!(Errno::EACCES, "Permission denied can not write"); + } + // If trying to open the file in read mode, check for read permissions + if ((flags & O_RDONLY != 0) || (flags & O_RDWR != 0)) && !can_read { + return_errno_with_message!(Errno::EACCES, "Permission denied can not read"); + } + + // If trying to open the file in read mode, check for read permissions + if ((flags & O_EXCL != 0) || (flags & O_RDWR != 0)) && !can_execute { + return_errno_with_message!(Errno::EACCES, "Permission denied can not exec"); + } + + Ok(EOK) + } + + /// Read data. + /// Read should send exactly the number of bytes requested except on EOF or error, + /// otherwise the rest of the data will be substituted with zeroes. An exception to + /// this is when the file has been opened in 'direct_io' mode, in which case the + /// return value of the read system call will reflect the return value of this + /// operation. fh will contain the value set by the open method, or will be undefined + /// if the open method didn't set any value. + /// + /// flags: these are the file flags, such as O_SYNC. Only supported with ABI >= 7.9 + /// lock_owner: only supported with ABI >= 7.9 + pub fn fuse_read( + &self, + ino: u64, + fh: u64, + offset: i64, + size: u32, + flags: i32, + lock_owner: Option, + ) -> Result> { + let mut data = vec![0u8; size as usize]; + let read_size = self.read_at(ino as u32, offset as usize, &mut data)?; + let r = data[..read_size].to_vec(); + Ok(r) + } + + /// Write data. + /// Write should return exactly the number of bytes requested except on error. An + /// exception to this is when the file has been opened in 'direct_io' mode, in + /// which case the return value of the write system call will reflect the return + /// value of this operation. fh will contain the value set by the open method, or + /// will be undefined if the open method didn't set any value. + /// + /// write_flags: will contain FUSE_WRITE_CACHE, if this write is from the page cache. If set, + /// the pid, uid, gid, and fh may not match the value that would have been sent if write cachin + /// is disabled + /// flags: these are the file flags, such as O_SYNC. Only supported with ABI >= 7.9 + /// lock_owner: only supported with ABI >= 7.9 + pub fn fuse_write( + &self, + ino: u64, + fh: u64, + offset: i64, + data: &[u8], + write_flags: u32, + flags: i32, + lock_owner: Option, + ) -> Result { + let write_size = self.write_at(ino as u32, offset as usize, data)?; + Ok(write_size) + } + + /// Open a directory. + /// Filesystem may store an arbitrary file handle (pointer, index, etc) in fh, and + /// use this in other all other directory stream operations (readdir, releasedir, + /// fsyncdir). Filesystem may also implement stateless directory I/O and not store + /// anything in fh, though that makes it impossible to implement standard conforming + /// directory stream operations in case the contents of the directory can change + /// between opendir and releasedir. + pub fn fuse_opendir(&mut self, ino: u64, flags: i32) -> Result { + let inode_ref = self.get_inode_ref(ino as u32); + + // 检查是否为目录 + if !inode_ref.inode.is_dir() { + return_errno_with_message!(Errno::ENOTDIR, "Not a directory"); + } + + // // 检查权限(例如,只允许读取目录) + // let file_perm = inode_ref.inode.file_perm(); + // if !file_perm.contains(InodePerm::S_IREAD) { + // return_errno_with_message!(Errno::EACCES, "Permission denied"); + // } + + // 成功打开目录,返回文件句柄(这里假设返回 inode 编号作为文件句柄) + Ok(ino as usize) + } + + /// Read directory. + /// Send a buffer filled using buffer.fill(), with size not exceeding the + /// requested size. Send an empty buffer on end of stream. fh will contain the + /// value set by the opendir method, or will be undefined if the opendir method + /// didn't set any value. + pub fn fuse_readdir(&self, ino: u64, fh: u64, offset: i64) -> Result> { + let mut entries = self.dir_get_entries(ino as u32); + entries = entries[offset as usize..].to_vec(); + Ok(entries) + } + + /// Create and open a file. + /// If the file does not exist, first create it with the specified mode, and then + /// open it. Open flags (with the exception of O_NOCTTY) are available in flags. + /// Filesystem may store an arbitrary file handle (pointer, index, etc) in fh, + /// and use this in other all other file operations (read, write, flush, release, + /// fsync). There are also some flags (direct_io, keep_cache) which the + /// filesystem may set, to change the way the file is opened. See fuse_file_info + /// structure in for more details. If this method is not + /// implemented or under Linux kernel versions earlier than 2.6.15, the mknod() + /// and open() methods will be called instead. + pub fn fuse_create( + &mut self, + parent: u64, + name: &str, + mode: u32, + umask: u32, + flags: i32, + ) -> Result { + // check file exist + let mut search_result = Ext4DirSearchResult::new(Ext4DirEntry::default()); + let r = self.dir_find_entry(parent as u32, name, &mut search_result); + if r.is_ok() { + let inode_ref = self.get_inode_ref(search_result.dentry.inode); + + // check permission + let file_perm = inode_ref.inode.file_perm(); + + let can_read = file_perm.contains(InodePerm::S_IREAD); + let can_write = file_perm.contains(InodePerm::S_IWRITE); + let can_execute = file_perm.contains(InodePerm::S_IEXEC); + + // If trying to open the file in write mode, check for write permissions + if ((flags & O_WRONLY != 0) || (flags & O_RDWR != 0)) && !can_write { + return_errno_with_message!(Errno::EACCES, "Permission denied can not write"); + } + + // If trying to open the file in read mode, check for read permissions + if (flags & O_RDONLY != 0) || (flags & O_RDWR != 0) && !can_read{ + return_errno_with_message!(Errno::EACCES, "Permission denied can not read"); + } + + // If trying to open the file in read mode, check for read permissions + if (flags & O_EXCL != 0) || (flags & O_RDWR != 0) && !can_execute { + return_errno_with_message!(Errno::EACCES, "Permission denied can not exec"); + } + + return Ok(EOK); + } else { + //create file + let inode_ref = self.create(parent as u32, name, mode as u16)?; + } + + Ok(EOK) + } + + /// Check file access permissions. + /// This will be called for the access() system call. If the 'default_permissions' + /// mount option is given, this method is not called. This method is not called + /// under Linux kernel versions 2.4.x + /// int access(const char *pathname, int mode); + /// int faccessat(int dirfd, const char *pathname, int mode, int flags); + /// + /// uid and gid come from request + pub fn fuse_access(&mut self, ino: u64, uid: u16, gid: u16, mode: u16, mask: i32) -> bool { + let inode_ref = self.get_inode_ref(ino as u32); + + inode_ref.inode.check_access(uid, gid, mode, mask as u16) + } + + /// Get file system statistics. + /// Linux stat syscall defines: + /// int stat(const char *restrict pathname, struct stat *restrict statbuf); + /// int fstatat(int dirfd, const char *restrict pathname, struct stat *restrict statbuf, int flags); + pub fn fuse_statfs(&mut self, ino: u64) -> Result { + let inode_ref = self.get_inode_ref(ino as u32); + let linux_stat = LinuxStat::from_inode_ref(&inode_ref); + Ok(linux_stat) + } + + /// Initialize filesystem. + /// Called before any other filesystem method. + /// The kernel module connection can be configured using the KernelConfig object + pub fn fuse_init(&mut self) -> Result { + Ok(EOK) + } + + /// Clean up filesystem. + /// Called on filesystem exit. + pub fn fuse_destroy(&mut self) -> Result { + Ok(EOK) + } + + /// Rename a file. + fn fuse_rename(&mut self, parent: u64, name: &str, newparent: u64, newname: &str, flags: u32) { + unimplemented!(); + } + + /// Flush method. + /// This is called on each close() of the opened file. Since file descriptors can + /// be duplicated (dup, dup2, fork), for one open call there may be many flush + /// calls. Filesystems shouldn't assume that flush will always be called after some + /// writes, or that if will be called at all. fh will contain the value set by the + /// open method, or will be undefined if the open method didn't set any value. + /// NOTE: the name of the method is misleading, since (unlike fsync) the filesystem + /// is not forced to flush pending writes. One reason to flush data, is if the + /// filesystem wants to return write errors. If the filesystem supports file locking + /// operations (setlk, getlk) it should remove all locks belonging to 'lock_owner'. + fn fuse_flush(&mut self, ino: u64, fh: u64, lock_owner: u64) { + unimplemented!(); + } + + /// Release an open file. + /// Release is called when there are no more references to an open file: all file + /// descriptors are closed and all memory mappings are unmapped. For every open + /// call there will be exactly one release call. The filesystem may reply with an + /// error, but error values are not returned to close() or munmap() which triggered + /// the release. fh will contain the value set by the open method, or will be undefined + /// if the open method didn't set any value. flags will contain the same flags as for + /// open. + fn fuse_release( + &mut self, + _ino: u64, + _fh: u64, + _flags: i32, + _lock_owner: Option, + _flush: bool, + ) { + unimplemented!(); + } + + /// Synchronize file contents. + /// If the datasync parameter is non-zero, then only the user data should be flushed, + /// not the meta data. + fn fuse_fsync(&mut self, ino: u64, fh: u64, datasync: bool) { + unimplemented!(); + } + + /// Read directory. + /// Send a buffer filled using buffer.fill(), with size not exceeding the + /// requested size. Send an empty buffer on end of stream. fh will contain the + /// value set by the opendir method, or will be undefined if the opendir method + /// didn't set any value. + fn fuse_readdirplus(&mut self, ino: u64, fh: u64, offset: i64) { + unimplemented!(); + } + + /// Release an open directory. + /// For every opendir call there will be exactly one releasedir call. fh will + /// contain the value set by the opendir method, or will be undefined if the + /// opendir method didn't set any value. + fn fuse_releasedir(&mut self, _ino: u64, _fh: u64, _flags: i32) { + unimplemented!(); + } + + /// Synchronize directory contents. + /// If the datasync parameter is set, then only the directory contents should + /// be flushed, not the meta data. fh will contain the value set by the opendir + /// method, or will be undefined if the opendir method didn't set any value. + fn fuse_fsyncdir(&mut self, ino: u64, fh: u64, datasync: bool) { + unimplemented!(); + } + + /// Set an extended attribute. + fn fuse_setxattr(&mut self, ino: u64, name: &str, _value: &[u8], flags: i32, position: u32) { + unimplemented!(); + } + + /// Get an extended attribute. + /// If `size` is 0, the size of the value should be sent with `reply.size()`. + /// If `size` is not 0, and the value fits, send it with `reply.data()`, or + /// `reply.error(ERANGE)` if it doesn't. + fn fuse_getxattr(&mut self, ino: u64, name: &str, size: u32) { + unimplemented!(); + } + + /// List extended attribute names. + /// If `size` is 0, the size of the value should be sent with `reply.size()`. + /// If `size` is not 0, and the value fits, send it with `reply.data()`, or + /// `reply.error(ERANGE)` if it doesn't. + fn fuse_listxattr(&mut self, ino: u64, size: u32) { + unimplemented!(); + } + + /// Remove an extended attribute. + fn fuse_removexattr(&mut self, ino: u64, name: &str) { + unimplemented!(); + } + + /// Test for a POSIX file lock. + fn fuse_getlk( + &mut self, + ino: u64, + fh: u64, + lock_owner: u64, + start: u64, + end: u64, + typ: i32, + pid: u32, + ) { + unimplemented!(); + } + + /// Acquire, modify or release a POSIX file lock. + /// For POSIX threads (NPTL) there's a 1-1 relation between pid and owner, but + /// otherwise this is not always the case. For checking lock ownership, + /// 'fi->owner' must be used. The l_pid field in 'struct flock' should only be + /// used to fill in this field in getlk(). Note: if the locking methods are not + /// implemented, the kernel will still allow file locking to work locally. + /// Hence these are only interesting for network filesystems and similar. + fn fuse_setlk( + &mut self, + ino: u64, + fh: u64, + lock_owner: u64, + start: u64, + end: u64, + typ: i32, + pid: u32, + sleep: bool, + ) { + unimplemented!(); + } + + /// Map block index within file to block index within device. + /// Note: This makes sense only for block device backed filesystems mounted + /// with the 'blkdev' option + fn fuse_bmap(&mut self, ino: u64, blocksize: u32, idx: u64) { + unimplemented!(); + } + + /// control device + fn fuse_ioctl( + &mut self, + ino: u64, + fh: u64, + flags: u32, + cmd: u32, + in_data: &[u8], + out_size: u32, + ) { + unimplemented!(); + } + + /// Poll for events + // #[cfg(feature = "abi-7-11")] + // fn fuse_poll( + // &mut self, + // ino: u64, + // fh: u64, + // kh: u64, + // events: u32, + // flags: u32, + // ) { + // } + + /// Preallocate or deallocate space to a file + fn fuse_fallocate(&mut self, ino: u64, fh: u64, offset: i64, length: i64, mode: i32) { + unimplemented!(); + } + + /// Reposition read/write file offset + fn fuse_lseek(&mut self, ino: u64, fh: u64, offset: i64, whence: i32) { + unimplemented!(); + } + + /// Copy the specified range from the source inode to the destination inode + fn fuse_copy_file_range( + &mut self, + ino_in: u64, + fh_in: u64, + offset_in: i64, + ino_out: u64, + fh_out: u64, + offset_out: i64, + len: u64, + flags: u32, + ) { + unimplemented!(); + } +} diff --git a/vendor/ext4_rs/src/lib.rs b/vendor/ext4_rs/src/lib.rs new file mode 100644 index 00000000..f311b7b2 --- /dev/null +++ b/vendor/ext4_rs/src/lib.rs @@ -0,0 +1,23 @@ +#![feature(error_in_core)] +#![no_std] +#![allow(unused)] + +extern crate alloc; + +pub mod utils; +pub mod prelude; + +pub use utils::*; +pub use prelude::*; + + +mod ext4_defs; +mod ext4_impls; + + +pub mod simple_interface; +pub mod fuse_interface; + + +pub use simple_interface::*; +pub use fuse_interface::*; diff --git a/vendor/ext4_rs/src/main.rs b/vendor/ext4_rs/src/main.rs new file mode 100644 index 00000000..6ce7939f --- /dev/null +++ b/vendor/ext4_rs/src/main.rs @@ -0,0 +1,230 @@ +#![feature(error_in_core)] +#![allow(unused)] + +extern crate alloc; + +mod prelude; +mod utils; + +use prelude::*; +use utils::*; + +mod ext4_defs; +mod ext4_impls; + +mod fuse_interface; +mod simple_interface; + +use ext4_defs::*; +use fuse_interface::*; +use simple_interface::*; + +use log::{Level, LevelFilter, Metadata, Record}; + +macro_rules! with_color { + ($color_code:expr, $($arg:tt)*) => {{ + format_args!("\u{1B}[{}m{}\u{1B}[m", $color_code as u8, format_args!($($arg)*)) + }}; +} + +struct SimpleLogger; + +impl log::Log for SimpleLogger { + fn enabled(&self, metadata: &Metadata) -> bool { + metadata.level() <= Level::Trace + } + + fn log(&self, record: &Record) { + let level = record.level(); + let args_color = match level { + Level::Error => ColorCode::Red, + Level::Warn => ColorCode::Yellow, + Level::Info => ColorCode::Green, + Level::Debug => ColorCode::Cyan, + Level::Trace => ColorCode::BrightBlack, + }; + + if self.enabled(record.metadata()) { + println!( + "{} - {}", + record.level(), + with_color!(args_color, "{}", record.args()) + ); + } + } + + fn flush(&self) {} +} + +#[repr(u8)] +enum ColorCode { + Red = 31, + Green = 32, + Yellow = 33, + Cyan = 36, + BrightBlack = 90, +} + +#[derive(Debug)] +pub struct Disk {} + +impl BlockDevice for Disk { + fn read_offset(&self, offset: usize) -> Vec { + // log::info!("read_offset: {:x?}", offset); + use std::fs::OpenOptions; + use std::io::{Read, Seek}; + let mut file = OpenOptions::new() + .read(true) + .write(true) + .open("ex4.img") + .unwrap(); + let mut buf = vec![0u8; BLOCK_SIZE as usize]; + let _r = file.seek(std::io::SeekFrom::Start(offset as u64)); + let _r = file.read_exact(&mut buf); + + buf + } + + fn write_offset(&self, offset: usize, data: &[u8]) { + use std::fs::OpenOptions; + use std::io::{Seek, Write}; + let mut file = OpenOptions::new() + .read(true) + .write(true) + .open("ex4.img") + .unwrap(); + + let _r = file.seek(std::io::SeekFrom::Start(offset as u64)); + let _r = file.write_all(&data); + } +} + +fn test_raw_block_device_write(block_device: Arc, size_mb: usize) { + let write_size = size_mb * 1024 * 1024; + let mut buffer = vec![0x41u8; write_size]; + + // Start from block 1000 to avoid overwriting important data + let start_block = 1000; + let start_offset = start_block * BLOCK_SIZE; + + log::info!("Starting raw BlockDevice write test: {} MB", size_mb); + let start_time = std::time::Instant::now(); + + // Write in BLOCK_SIZE chunks + let mut written = 0; + while written < write_size { + let write_size = std::cmp::min(BLOCK_SIZE, write_size - written); + let offset = start_offset + written; + block_device.write_offset(offset, &buffer[written..written + write_size]); + written += write_size; + } + + let end_time = start_time.elapsed(); + let speed_mb_per_sec = (write_size as f64 / 1024.0 / 1024.0) / end_time.as_secs_f64(); + + log::info!("Raw BlockDevice write speed: {:.2} MB/s", speed_mb_per_sec); + log::info!("Total time: {:.2} seconds", end_time.as_secs_f64()); +} + +fn main() { + log::set_logger(&SimpleLogger).unwrap(); + log::set_max_level(LevelFilter::Trace); + let disk = Arc::new(Disk {}); + let block_device = disk.clone(); // Clone before using + let ext4 = Ext4::open(disk); + + // file read + let path = "test_files/0.txt"; + // 1G + const READ_SIZE: usize = (0x100000 * 1024); + let mut read_buf = vec![0u8; READ_SIZE as usize]; + let child_inode = ext4.generic_open(path, &mut 2, false, 0, &mut 0).unwrap(); + let mut data = vec![0u8; READ_SIZE as usize]; + let read_data = ext4.read_at(child_inode, 0 as usize, &mut data); + log::info!("read data {:?}", &data[..10]); + + + + let path = "test_files/linktest"; + let mut read_buf = vec![0u8; READ_SIZE as usize]; + // 2 is root inode + let child_inode = ext4.generic_open(path, &mut 2, false, 0, &mut 0).unwrap(); + let mut data = vec![0u8; READ_SIZE as usize]; + let read_data = ext4.read_at(child_inode, 0 as usize, &mut data); + log::info!("read data {:?}", &data[..10]); + + // dir make + log::info!("----mkdir----"); + for i in 0..10 { + let path = format!("dirtest{}", i); + let path = path.as_str(); + log::info!("mkdir making {:?}", path); + let r = ext4.dir_mk(&path); + assert!(r.is_ok(), "dir make error {:?}", r.err()); + } + let path = "dir1/dir2/dir3/dir4/dir5/dir6"; + log::info!("mkdir making {:?}", path); + let r = ext4.dir_mk(&path); + assert!(r.is_ok(), "dir make error {:?}", r.err()); + + // dir ls + let entries = ext4.dir_get_entries(ROOT_INODE); + log::info!("dir ls root"); + for entry in entries { + log::info!("{:?}", entry.get_name()); + } + + // file remove + let path = "test_files/file_to_remove"; + let r = ext4.file_remove(&path); + + // dir remove + let path = "dir_to_remove"; + let r = ext4.dir_remove(ROOT_INODE, &path); + + // file create/write + log::info!("----create file----"); + let inode_mode = InodeFileType::S_IFREG.bits(); + let inode_perm = (InodePerm::S_IREAD | InodePerm::S_IWRITE).bits(); + let inode_ref = ext4.create(ROOT_INODE, "4G.txt", inode_mode | inode_perm).unwrap(); + log::info!("----write file----"); + const WRITE_SIZE: usize = (1024 * 1024 * 1024 * 4); + let write_buf = vec![0x41 as u8; WRITE_SIZE]; + + // Record start time + let start_time = std::time::Instant::now(); + let r = ext4.write_at(inode_ref.inode_num, 0, &write_buf); + let end_time = start_time.elapsed(); + + // Calculate and display write speed + let write_speed = (WRITE_SIZE as f64 / 1024.0 / 1024.0) / (end_time.as_secs_f64()); + log::info!("Write speed: {:.2} MB/s", write_speed); + log::info!("Total time: {:.2} seconds", end_time.as_secs_f64()); + + log::info!("----write done verifying----"); + const BLOCKS_PER_128MB: usize = 32768; // 128MB / 4KB = 32768 blocks + let mut last_progress = 0; + for i in 0..WRITE_SIZE/ BLOCK_SIZE { + let offset = (i * BLOCK_SIZE) as i64; + let write_data = vec![0x41 as u8; BLOCK_SIZE]; + let read_data = ext4 + .ext4_file_read(inode_ref.inode_num as u64, BLOCK_SIZE as u32, offset) + .unwrap(); + if read_data != write_data { + log::info!("Data mismatch at block {:x}", i); + panic!("Data mismatch at block {:x}", i); + } + + // 每128MB打印一次进度 + let current_progress = i / BLOCKS_PER_128MB; + if current_progress > last_progress { + last_progress = current_progress; + let progress_mb = current_progress * 128; + log::info!( + "Progress: {} MB / {} MB verified", + progress_mb, + WRITE_SIZE / (1024 * 1024) + ); + } + } +} diff --git a/vendor/ext4_rs/src/prelude.rs b/vendor/ext4_rs/src/prelude.rs new file mode 100644 index 00000000..8a2e150a --- /dev/null +++ b/vendor/ext4_rs/src/prelude.rs @@ -0,0 +1,29 @@ +#![allow(unused)] +#![feature(error_in_core)] + +extern crate alloc; + +pub(crate) use alloc::boxed::Box; +pub(crate) use alloc::collections::BTreeMap; +pub(crate) use alloc::collections::BTreeSet; +pub(crate) use alloc::collections::LinkedList; +pub(crate) use alloc::collections::VecDeque; +pub(crate) use alloc::ffi::CString; +pub(crate) use alloc::string::String; +pub(crate) use alloc::string::ToString; +pub(crate) use alloc::sync::Arc; +pub(crate) use alloc::sync::Weak; +pub(crate) use alloc::vec; +pub(crate) use alloc::vec::Vec; +pub(crate) use core::any::Any; +pub(crate) use core::ffi::CStr; +pub(crate) use core::fmt::Debug; +pub(crate) use core::mem::size_of; +pub(crate) use core::cmp::min; + + +pub(crate) use bitflags::bitflags; +pub(crate) use log::{debug, info, trace, warn}; + +pub(crate) use crate::utils::errors::*; +pub(crate) type Result = core::result::Result; diff --git a/vendor/ext4_rs/src/simple_interface/mod.rs b/vendor/ext4_rs/src/simple_interface/mod.rs new file mode 100644 index 00000000..b69f9907 --- /dev/null +++ b/vendor/ext4_rs/src/simple_interface/mod.rs @@ -0,0 +1,171 @@ +use core::panic::RefUnwindSafe; + +use crate::prelude::*; + +use crate::ext4_defs::*; +use crate::return_errno; +use crate::return_errno_with_message; +use crate::utils::path_check; + +// export some definitions +pub use crate::ext4_defs::Ext4; +pub use crate::ext4_defs::BLOCK_SIZE; +pub use crate::ext4_defs::BlockDevice; +pub use crate::ext4_defs::InodeFileType; + + +/// simple interface for ext4 +impl Ext4 { + + /// Parse the file access flags (such as "r", "w", "a", etc.) and convert them to system constants. + /// + /// This method parses common file access flags into their corresponding bitwise constants defined in `libc`. + /// + /// # Arguments + /// * `flags` - The string representation of the file access flags (e.g., "r", "w", "a", "r+", etc.). + /// + /// # Returns + /// * `Result` - The corresponding bitwise flag constants (e.g., `O_RDONLY`, `O_WRONLY`, etc.), or an error if the flags are invalid. + fn ext4_parse_flags(&self, flags: &str) -> Result { + match flags { + "r" | "rb" => Ok(O_RDONLY), + "w" | "wb" => Ok(O_WRONLY | O_CREAT | O_TRUNC), + "a" | "ab" => Ok(O_WRONLY | O_CREAT | O_APPEND), + "r+" | "rb+" | "r+b" => Ok(O_RDWR), + "w+" | "wb+" | "w+b" => Ok(O_RDWR | O_CREAT | O_TRUNC), + "a+" | "ab+" | "a+b" => Ok(O_RDWR | O_CREAT | O_APPEND), + _ => Err(Ext4Error::new(Errno::EINVAL)), + } + } + + /// Open a file at the specified path and return the corresponding inode number. + /// + /// Open a file by searching for the given path starting from the root directory (`ROOT_INODE`). + /// If the file does not exist and the `O_CREAT` flag is specified, the file will be created. + /// + /// # Arguments + /// * `path` - The path of the file to open. + /// * `flags` - The access flags (e.g., "r", "w", "a", etc.). + /// + /// # Returns + /// * `Result` - Returns the inode number of the opened file if successful. + pub fn ext4_file_open( + &self, + path: &str, + flags: &str, + ) -> Result { + let mut parent_inode_num = ROOT_INODE; + let filetype = InodeFileType::S_IFREG; + + let iflags = self.ext4_parse_flags(flags).unwrap(); + + let filetype = InodeFileType::S_IFDIR; + + let mut create = false; + if iflags & O_CREAT != 0 { + create = true; + } + + self.generic_open(path, &mut parent_inode_num, create, filetype.bits(), &mut 0) + } + + /// Create a new directory at the specified path. + /// + /// Checks if the directory already exists by searching from the root directory (`ROOT_INODE`). + /// If the directory does not exist, it creates the directory under the root directory and returns its inode number. + /// + /// # Arguments + /// * `path` - The path where the directory will be created. + /// + /// # Returns + /// * `Result` - The inode number of the newly created directory if successful, + /// or an error (`Errno::EEXIST`) if the directory already exists. + pub fn ext4_dir_mk(&self, path: &str) -> Result { + let mut search_result = Ext4DirSearchResult::new(Ext4DirEntry::default()); + let r = self.dir_find_entry(ROOT_INODE, path, &mut search_result); + if r.is_ok() { + return_errno!(Errno::EEXIST); + } + let mut parent_inode_num = ROOT_INODE; + let filetype = InodeFileType::S_IFDIR; + + self.generic_open(path, &mut parent_inode_num, true, filetype.bits(), &mut 0) + } + + + /// Open a directory at the specified path and return the corresponding inode number. + /// + /// Opens a directory by searching for the given path starting from the root directory (`ROOT_INODE`). + /// + /// # Arguments + /// * `path` - The path of the directory to open. + /// + /// # Returns + /// * `Result` - Returns the inode number of the opened directory if successful. + pub fn ext4_dir_open( + &self, + path: &str, + ) -> Result { + let mut parent_inode_num = ROOT_INODE; + let filetype = InodeFileType::S_IFDIR; + self.generic_open(path, &mut parent_inode_num, false, filetype.bits(), &mut 0) + } + + /// Get dir entries of a inode + /// + /// Params: + /// inode: u32 - inode number of the directory + /// assert!(inode.is_dir()); + /// + /// Returns: + /// `Vec` - list of directory entries + pub fn ext4_dir_get_entries(&self, inode: u32) -> Vec { + let mut entries = self.dir_get_entries(inode); + entries + } + + /// Read data from a file starting from a given offset. + /// + /// Reads data from the file starting at the specified inode (`ino`), with a given offset and size. + /// + /// # Arguments + /// * `ino` - The inode number of the file to read from. + /// * `size` - The number of bytes to read. + /// * `offset` - The offset from where to start reading. + /// + /// # Returns + /// * `Result>` - The data read from the file. + pub fn ext4_file_read( + &self, + ino: u64, + size: u32, + offset: i64, + ) -> Result> { + let mut data = vec![0u8; size as usize]; + let read_size = self.read_at(ino as u32, offset as usize, &mut data)?; + let r = data[..read_size].to_vec(); + Ok(r) + } + + /// Write data to a file starting at a given offset. + /// + /// Writes data to the file starting at the specified inode (`ino`) and offset. + /// + /// # Arguments + /// * `ino` - The inode number of the file to write to. + /// * `offset` - The offset in the file where the data will be written. + /// * `data` - The data to write to the file. + /// + /// # Returns + /// * `Result` - The number of bytes written to the file. + pub fn ext4_file_write( + &self, + ino: u64, + offset: i64, + data: &[u8], + ) -> Result { + let write_size = self.write_at(ino as u32, offset as usize, data)?; + Ok(write_size) + } + +} \ No newline at end of file diff --git a/vendor/ext4_rs/src/utils/bitmap.rs b/vendor/ext4_rs/src/utils/bitmap.rs new file mode 100644 index 00000000..95341cdf --- /dev/null +++ b/vendor/ext4_rs/src/utils/bitmap.rs @@ -0,0 +1,103 @@ +/// Check if a bit is set in the bitmap +/// Parameter bmap: Bitmap array +/// Parameter bit: Bit index in the bitmap +pub fn ext4_bmap_is_bit_set(bmap: &[u8], bit: u32) -> bool { + bmap[(bit >> 3) as usize] & (1 << (bit & 7)) != 0 +} + +/// Check if a bit is cleared in the bitmap +/// Parameter bmap: Bitmap array +/// Parameter bit: Bit index in the bitmap +pub fn ext4_bmap_is_bit_clr(bmap: &[u8], bit: u32) -> bool { + !ext4_bmap_is_bit_set(bmap, bit) +} + +/// Set a bit in the bitmap +/// Parameter bmap: Bitmap array +/// Parameter bit: Bit index in the bitmap +pub fn ext4_bmap_bit_set(bmap: &mut [u8], bit: u32) { + bmap[(bit >> 3) as usize] |= 1 << (bit & 7); +} + +/// Clear a bit in the bitmap +/// Parameter bmap: Bitmap array +/// Parameter bit: Bit index in the bitmap +pub fn ext4_bmap_bit_clr(bmap: &mut [u8], bit: u32) { + bmap[(bit >> 3) as usize] &= !(1 << (bit & 7)); +} + +/// Find a free bit in the bitmap +/// Parameter bmap: Bitmap array +/// Parameter sbit: Start bit index +/// Parameter ebit: End bit index +/// Parameter bit_id: Reference to store the free bit index +pub fn ext4_bmap_bit_find_clr(bmap: &[u8], sbit: u32, ebit: u32, bit_id: &mut u32) -> bool { + let mut i: u32; + let mut bcnt = ebit - sbit; + + i = sbit; + + while i & 7 != 0 { + if bcnt == 0 { + return false; + } + + if ext4_bmap_is_bit_clr(bmap, i) { + *bit_id = i; + return true; + } + + i += 1; + bcnt -= 1; + } + + let mut byte_idx = (i >> 3) as usize; + let mut bit_pos = i; + + while bcnt >= 8 { + // 检查边界条件 + if byte_idx >= bmap.len() { + return false; + } + + if bmap[byte_idx] != 0xFF { + for j in 0..8 { + let bit_idx = bit_pos + j; + if ext4_bmap_is_bit_clr(bmap, bit_idx) { + *bit_id = bit_idx; + return true; + } + } + } + + byte_idx += 1; + bcnt -= 8; + bit_pos += 8; + } + + while bcnt > 0 { + if bit_pos >= ebit { + return false; + } + + if ext4_bmap_is_bit_clr(bmap, bit_pos) { + *bit_id = bit_pos; + return true; + } + + bit_pos += 1; + bcnt -= 1; + } + + false +} + +/// Clear a range of bits in the bitmap +/// Parameter bmap: Mutable reference to the bitmap array +/// Parameter start_bit: The start index of the bit range to clear +/// Parameter end_bit: The end index of the bit range to clear +pub fn ext4_bmap_bits_free(bmap: &mut [u8], start_bit: u32, end_bit: u32) { + for bit in start_bit..=end_bit { + ext4_bmap_bit_clr(bmap, bit); + } +} \ No newline at end of file diff --git a/vendor/ext4_rs/src/utils/crc.rs b/vendor/ext4_rs/src/utils/crc.rs new file mode 100644 index 00000000..702ac09a --- /dev/null +++ b/vendor/ext4_rs/src/utils/crc.rs @@ -0,0 +1,78 @@ +/* */ +/* CRC LOOKUP TABLE */ +/* ================ */ +/* The following CRC lookup table was generated automagically */ +/* by the Rocksoft^tm Model CRC Algorithm Table Generation */ +/* Program V1.0 using the following model parameters: */ +/* */ +/* Width : 4 bytes. */ +/* Poly : 0x1EDC6F41L */ +/* Reverse : TRUE. */ +/* */ +/* For more information on the Rocksoft^tm Model CRC Algorithm, */ +/* see the document titled "A Painless Guide to CRC Error */ +/* Detection Algorithms" by Ross Williams */ +/* (ross@guest.adelaide.edu.au.). This document is likely to be */ +/* in the FTP archive "ftp.adelaide.edu.au/pub/rocksoft". */ +/* */ +pub const CRC32C_TAB: [u32; 256] = [ + 0x00000000, 0xF26B8303, 0xE13B70F7, 0x1350F3F4, 0xC79A971F, 0x35F1141C, 0x26A1E7E8, 0xD4CA64EB, + 0x8AD958CF, 0x78B2DBCC, 0x6BE22838, 0x9989AB3B, 0x4D43CFD0, 0xBF284CD3, 0xAC78BF27, 0x5E133C24, + 0x105EC76F, 0xE235446C, 0xF165B798, 0x030E349B, 0xD7C45070, 0x25AFD373, 0x36FF2087, 0xC494A384, + 0x9A879FA0, 0x68EC1CA3, 0x7BBCEF57, 0x89D76C54, 0x5D1D08BF, 0xAF768BBC, 0xBC267848, 0x4E4DFB4B, + 0x20BD8EDE, 0xD2D60DDD, 0xC186FE29, 0x33ED7D2A, 0xE72719C1, 0x154C9AC2, 0x061C6936, 0xF477EA35, + 0xAA64D611, 0x580F5512, 0x4B5FA6E6, 0xB93425E5, 0x6DFE410E, 0x9F95C20D, 0x8CC531F9, 0x7EAEB2FA, + 0x30E349B1, 0xC288CAB2, 0xD1D83946, 0x23B3BA45, 0xF779DEAE, 0x05125DAD, 0x1642AE59, 0xE4292D5A, + 0xBA3A117E, 0x4851927D, 0x5B016189, 0xA96AE28A, 0x7DA08661, 0x8FCB0562, 0x9C9BF696, 0x6EF07595, + 0x417B1DBC, 0xB3109EBF, 0xA0406D4B, 0x522BEE48, 0x86E18AA3, 0x748A09A0, 0x67DAFA54, 0x95B17957, + 0xCBA24573, 0x39C9C670, 0x2A993584, 0xD8F2B687, 0x0C38D26C, 0xFE53516F, 0xED03A29B, 0x1F682198, + 0x5125DAD3, 0xA34E59D0, 0xB01EAA24, 0x42752927, 0x96BF4DCC, 0x64D4CECF, 0x77843D3B, 0x85EFBE38, + 0xDBFC821C, 0x2997011F, 0x3AC7F2EB, 0xC8AC71E8, 0x1C661503, 0xEE0D9600, 0xFD5D65F4, 0x0F36E6F7, + 0x61C69362, 0x93AD1061, 0x80FDE395, 0x72966096, 0xA65C047D, 0x5437877E, 0x4767748A, 0xB50CF789, + 0xEB1FCBAD, 0x197448AE, 0x0A24BB5A, 0xF84F3859, 0x2C855CB2, 0xDEEEDFB1, 0xCDBE2C45, 0x3FD5AF46, + 0x7198540D, 0x83F3D70E, 0x90A324FA, 0x62C8A7F9, 0xB602C312, 0x44694011, 0x5739B3E5, 0xA55230E6, + 0xFB410CC2, 0x092A8FC1, 0x1A7A7C35, 0xE811FF36, 0x3CDB9BDD, 0xCEB018DE, 0xDDE0EB2A, 0x2F8B6829, + 0x82F63B78, 0x709DB87B, 0x63CD4B8F, 0x91A6C88C, 0x456CAC67, 0xB7072F64, 0xA457DC90, 0x563C5F93, + 0x082F63B7, 0xFA44E0B4, 0xE9141340, 0x1B7F9043, 0xCFB5F4A8, 0x3DDE77AB, 0x2E8E845F, 0xDCE5075C, + 0x92A8FC17, 0x60C37F14, 0x73938CE0, 0x81F80FE3, 0x55326B08, 0xA759E80B, 0xB4091BFF, 0x466298FC, + 0x1871A4D8, 0xEA1A27DB, 0xF94AD42F, 0x0B21572C, 0xDFEB33C7, 0x2D80B0C4, 0x3ED04330, 0xCCBBC033, + 0xA24BB5A6, 0x502036A5, 0x4370C551, 0xB11B4652, 0x65D122B9, 0x97BAA1BA, 0x84EA524E, 0x7681D14D, + 0x2892ED69, 0xDAF96E6A, 0xC9A99D9E, 0x3BC21E9D, 0xEF087A76, 0x1D63F975, 0x0E330A81, 0xFC588982, + 0xB21572C9, 0x407EF1CA, 0x532E023E, 0xA145813D, 0x758FE5D6, 0x87E466D5, 0x94B49521, 0x66DF1622, + 0x38CC2A06, 0xCAA7A905, 0xD9F75AF1, 0x2B9CD9F2, 0xFF56BD19, 0x0D3D3E1A, 0x1E6DCDEE, 0xEC064EED, + 0xC38D26C4, 0x31E6A5C7, 0x22B65633, 0xD0DDD530, 0x0417B1DB, 0xF67C32D8, 0xE52CC12C, 0x1747422F, + 0x49547E0B, 0xBB3FFD08, 0xA86F0EFC, 0x5A048DFF, 0x8ECEE914, 0x7CA56A17, 0x6FF599E3, 0x9D9E1AE0, + 0xD3D3E1AB, 0x21B862A8, 0x32E8915C, 0xC083125F, 0x144976B4, 0xE622F5B7, 0xF5720643, 0x07198540, + 0x590AB964, 0xAB613A67, 0xB831C993, 0x4A5A4A90, 0x9E902E7B, 0x6CFBAD78, 0x7FAB5E8C, 0x8DC0DD8F, + 0xE330A81A, 0x115B2B19, 0x020BD8ED, 0xF0605BEE, 0x24AA3F05, 0xD6C1BC06, 0xC5914FF2, 0x37FACCF1, + 0x69E9F0D5, 0x9B8273D6, 0x88D28022, 0x7AB90321, 0xAE7367CA, 0x5C18E4C9, 0x4F48173D, 0xBD23943E, + 0xF36E6F75, 0x0105EC76, 0x12551F82, 0xE03E9C81, 0x34F4F86A, 0xC69F7B69, 0xD5CF889D, 0x27A40B9E, + 0x79B737BA, 0x8BDCB4B9, 0x988C474D, 0x6AE7C44E, 0xBE2DA0A5, 0x4C4623A6, 0x5F16D052, 0xAD7D5351, +]; + + +pub const EXT4_CRC32_INIT: u32 = 0xFFFFFFFF; + +/// Calculate CRC32 checksum +/// Parameter crc: Initial value +/// Parameter buf: Buffer +/// Parameter size: Buffer size +/// Parameter tab: Lookup table +pub fn crc32(crc: u32, buf: &[u8], size: u32, tab: &[u32]) -> u32 { + let mut crc = crc; + let mut p = buf; + let mut size = size as usize; + + // Loop to update crc value + while size > 0 { + crc = tab[(crc as u8 ^ p[0]) as usize] ^ (crc >> 8); + p = &p[1..]; + size -= 1; + } + + crc +} + +pub fn ext4_crc32c(crc: u32, buf: &[u8], size: u32) -> u32 { + crc32(crc, buf, size, &CRC32C_TAB) +} \ No newline at end of file diff --git a/vendor/ext4_rs/src/utils/errors.rs b/vendor/ext4_rs/src/utils/errors.rs new file mode 100644 index 00000000..0e82459f --- /dev/null +++ b/vendor/ext4_rs/src/utils/errors.rs @@ -0,0 +1,94 @@ + + +/// Ext4Error number. +#[repr(i32)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum Errno { + EPERM = 1, /* Operation not permitted */ + ENOENT = 2, /* No such file or directory */ + EINTR = 4, /* Interrupted system call */ + EIO = 5, /* I/O error */ + ENXIO = 6, /* No such device or address */ + E2BIG = 7, /* Argument list too long */ + EBADF = 9, /* Bad file number */ + EAGAIN = 11, /* Try again */ + ENOMEM = 12, /* Out of memory */ + EACCES = 13, /* Permission denied */ + EFAULT = 14, /* Bad address */ + ENOTBLK = 15, /* Block device required */ + EBUSY = 16, /* Device or resource busy */ + EEXIST = 17, /* File exists */ + EXDEV = 18, /* Cross-device link */ + ENODEV = 19, /* No such device */ + ENOTDIR = 20, /* Not a directory */ + EISDIR = 21, /* Is a directory */ + EINVAL = 22, /* Invalid argument */ + ENFILE = 23, /* File table overflow */ + EMFILE = 24, /* Too many open files */ + ENOTTY = 25, /* Not a typewriter */ + ETXTBSY = 26, /* Text file busy */ + EFBIG = 27, /* File too large */ + ENOSPC = 28, /* No space left on device */ + ESPIPE = 29, /* Illegal seek */ + EROFS = 30, /* Read-only file system */ + EMLINK = 31, /* Too many links */ + EPIPE = 32, /* Broken pipe */ + ENAMETOOLONG = 36, /* File name too long */ + ENOTSUP = 95, /* Not supported */ +} + +#[derive(Debug, Clone, Copy, PartialEq)] +#[allow(unused)] +pub struct Ext4Error { + errno: Errno, + msg: Option<&'static str>, +} + +impl Ext4Error { + pub const fn new(errno: Errno) -> Self { + Ext4Error { errno, msg: None } + } + + pub const fn with_message(errno: Errno, msg: &'static str) -> Self { + Ext4Error { + errno, + msg: Some(msg), + } + } + + pub const fn error(&self) -> Errno { + self.errno + } +} + +impl From for Ext4Error { + fn from(errno: Errno) -> Self { + Ext4Error::new(errno) + } +} + +impl From for Ext4Error { + fn from(_: core::str::Utf8Error) -> Self { + Ext4Error::with_message(Errno::EINVAL, "Invalid utf-8 string") + } +} + +impl From for Ext4Error { + fn from(_: alloc::string::FromUtf8Error) -> Self { + Ext4Error::with_message(Errno::EINVAL, "Invalid utf-8 string") + } +} + +#[macro_export] +macro_rules! return_errno { + ($errno: expr) => { + return Err(Ext4Error::new($errno)) + }; +} + +#[macro_export] +macro_rules! return_errno_with_message { + ($errno: expr, $message: expr) => { + return Err(Ext4Error::with_message($errno, $message)) + }; +} diff --git a/vendor/ext4_rs/src/utils/mod.rs b/vendor/ext4_rs/src/utils/mod.rs new file mode 100644 index 00000000..5ff44ba9 --- /dev/null +++ b/vendor/ext4_rs/src/utils/mod.rs @@ -0,0 +1,11 @@ +pub mod bitmap; +pub mod crc; +pub mod path; +pub mod errors; + + + +pub use bitmap::*; +pub use crc::*; +pub use path::*; +pub use errors::*; \ No newline at end of file diff --git a/vendor/ext4_rs/src/utils/path.rs b/vendor/ext4_rs/src/utils/path.rs new file mode 100644 index 00000000..c3be374f --- /dev/null +++ b/vendor/ext4_rs/src/utils/path.rs @@ -0,0 +1,63 @@ +use alloc::string::String; +use alloc::vec::Vec; + +pub fn path_check(path: &str, is_goal: &mut bool) -> usize { + // Iterate through each character and its index in the string + for (i, c) in path.chars().enumerate() { + // Check if maximum filename length limit is reached + if i >= 255 { + break; + } + + // Check if character is a path separator + if c == '/' { + *is_goal = false; + return i; + } + + // Check if end of string is reached + if c == '\0' { + *is_goal = true; + return i; + } + } + + // If neither '/' nor '\0' was found, and length is less than maximum filename length + *is_goal = true; + path.len() +} + +#[cfg(test)] +mod path_tests { + use super::*; + #[test] + fn test_ext4_path_check() { + let mut is_goal = false; + + // Test root path + assert_eq!(path_check("/", &mut is_goal), 0); + assert!(!is_goal, "Root path should not set is_goal to true"); + + // Test normal path + assert_eq!(path_check("/home/user/file.txt", &mut is_goal), 0); + assert!(!is_goal, "Normal path should not set is_goal to true"); + + // Test path without slashes + let path = "file.txt"; + assert_eq!(path_check(path, &mut is_goal), path.len()); + assert!(is_goal, "Path without slashes should set is_goal to true"); + + // Test null character at end of path + let path = "home\0"; + assert_eq!(path_check(path, &mut is_goal), 4); + assert!( + is_goal, + "Path with null character should set is_goal to true" + ); + + // // Test too long filename + // let long_path = "a".repeat(EXT4_DIRECTORY_FILENAME_LEN + 10); + // assert_eq!(ext4_path_check(&long_path, &mut is_goal), EXT4_DIRECTORY_FILENAME_LEN); + // assert!(!is_goal, "Long filename should not set is_goal to true and should be truncated"); + } +} From db5054d6be38de823e05a5fe11a83dcd554c2b45 Mon Sep 17 00:00:00 2001 From: LittleSand <1840309785@qq.com> Date: Wed, 24 Jun 2026 02:27:56 +0800 Subject: [PATCH 09/19] test: use external oscomp images directly --- Makefile | 20 ++++++++++++++++---- README.md | 2 +- README_gitlab.md | 2 +- os/qemu-loongarch-run.sh | 14 +++++++++++--- os/qemu-run.sh | 14 +++++++++++--- 5 files changed, 40 insertions(+), 12 deletions(-) diff --git a/Makefile b/Makefile index 137afb6e..694c96ff 100644 --- a/Makefile +++ b/Makefile @@ -30,8 +30,12 @@ VFAT_IMG := $(OS_DIR)/vfat.img MKFS_VFAT ?= mkfs.vfat ASSEMBLE_DISK := scripts/assemble_partitioned_disk.sh COMMA := , -TESTIMG_RV ?= sdcard-rv.img -TESTIMG_LA ?= sdcard-la.img +# Local test images can be large (LTP in particular), so keep them outside this +# worktree and attach them directly as writable QEMU block devices. Override with +# `make run-la TESTIMG_LA=/path/to/sdcard-la.img` when needed. +LOCAL_TESTIMG_DIR ?= ../CCYOS +TESTIMG_RV ?= $(firstword $(wildcard $(LOCAL_TESTIMG_DIR)/sdcard-rv.img sdcard-rv.img)) +TESTIMG_LA ?= $(firstword $(wildcard $(LOCAL_TESTIMG_DIR)/sdcard-la.img sdcard-la.img)) RV_TEST_DRIVE := -drive file=$(TESTIMG_RV)$(COMMA)if=none$(COMMA)format=raw$(COMMA)id=test0 -device virtio-blk-device$(COMMA)drive=test0$(COMMA)bus=virtio-mmio-bus.0 LA_TEST_DRIVE := -drive file=$(TESTIMG_LA)$(COMMA)if=none$(COMMA)format=raw$(COMMA)id=test0 -device virtio-blk-pci$(COMMA)drive=test0 @@ -143,7 +147,11 @@ disk-la.img: kernel-la $(VFAT_IMG) $(ASSEMBLE_DISK) # 官方测试盘放 bus.0,注册出来才是 vda=disk、vdb=sdcard。 # 设备型号对齐 os/qemu-run.sh(riscv: virtio-mmio)与 os/qemu-loongarch-run.sh(loongarch: pci)。 # ------------------------------------------------------------ -run-rv: kernel-rv disk.img $(TESTIMG_RV) +run-rv: kernel-rv disk.img + @if [ -z "$(TESTIMG_RV)" ]; then \ + echo "Error: no RISC-V test image found. Set TESTIMG_RV=/path/to/sdcard-rv.img" >&2; \ + exit 1; \ + fi @echo "[Run] 运行 RISC-V QEMU(内核盘:vda1 rootfs,vda2 VFAT;测试盘:vdb)" qemu-system-riscv64 -machine virt -kernel kernel-rv -m $(RV_MEM) -nographic \ -smp $(RV_SMP) -bios default -no-reboot -rtc base=utc \ @@ -152,7 +160,11 @@ run-rv: kernel-rv disk.img $(TESTIMG_RV) $(RV_TEST_DRIVE) \ -device virtio-net-device,netdev=net -netdev user,id=net -run-la: kernel-la disk-la.img $(TESTIMG_LA) +run-la: kernel-la disk-la.img + @if [ -z "$(TESTIMG_LA)" ]; then \ + echo "Error: no LoongArch test image found. Set TESTIMG_LA=/path/to/sdcard-la.img" >&2; \ + exit 1; \ + fi @echo "[Run] 运行 LoongArch QEMU(内核盘:vda1 rootfs,vda2 VFAT;测试盘:vdb)" qemu-system-loongarch64 -machine virt -kernel kernel-la -m $(LA_MEM) -nographic \ -smp $(LA_SMP) -no-reboot -rtc base=utc \ diff --git a/README.md b/README.md index 2d5cd6a2..7f62e073 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ cd os && make test - 第一分区:ext4 rootfs,内容来自对应的 `os/fs-*.img`。 - 第二分区:64 MiB FAT32/VFAT 空分区,用于 OSComp `basic/mount` 与 `basic/umount` 测试。 -官方测试镜像 `sdcard-rv.img` / `sdcard-la.img` 不写入 rootfs;本地运行时它们需要位于仓库根目录。`make run-rv` / `make run-la` 会将我们的 MBR 分区盘作为第一个 VirtIO 块设备 `vda`,将官方测试镜像作为第二个块设备 `vdb`。内核从 `vda1` 启动 rootfs,`rcS` 会把 `vdb` 这个裸 ext4 测试镜像挂到 `/tests` 后执行白名单 musl 测试。 +官方测试镜像 `sdcard-rv.img` / `sdcard-la.img` 不写入 rootfs,也不需要复制到本仓库。`make run-rv` / `make run-la` 默认优先使用相邻 `../CCYOS/` 目录下的测试镜像,也可通过 `TESTIMG_RV=/path/to/sdcard-rv.img` 或 `TESTIMG_LA=/path/to/sdcard-la.img` 指定任意本地路径。运行时我们的 MBR 分区盘作为第一个 VirtIO 块设备 `vda`,官方测试镜像作为第二个块设备 `vdb`。内核从 `vda1` 启动 rootfs,`rcS` 会把 `vdb` 这个裸 ext4 测试镜像挂到 `/tests` 后原地执行白名单 musl 测试。 内核默认会从发现到的块设备(整盘和分区)中探测 ext4 rootfs,优先尝试分区设备,并选择含 `/bin/sh` 或 `/bin/ash` 的设备挂载为 `/`,因此评测运行形态会从我们的 `vda1` 启动。`oscomp` feature 已弃用并保留为空兼容项,不再改变启动行为。 diff --git a/README_gitlab.md b/README_gitlab.md index 44df577e..ed914023 100644 --- a/README_gitlab.md +++ b/README_gitlab.md @@ -30,7 +30,7 @@ - 第一分区:ext4 rootfs。 - 第二分区:64 MiB FAT32/VFAT 空分区,用于 `basic/mount`、`basic/umount`。 -我们的 MBR 分区盘作为第一个块设备 `vda`,内核从 `vda1` 启动 rootfs。官方测试镜像 `sdcard-rv.img` / `sdcard-la.img` 作为第二个块设备 `vdb` 提供测试内容,镜像根目录包含 `musl/` 与 `glibc/`。启动脚本会把 `vdb` 这个裸 ext4 测试镜像挂载到 `/tests`,再自动运行白名单 musl 测试。 +我们的 MBR 分区盘作为第一个块设备 `vda`,内核从 `vda1` 启动 rootfs。官方测试镜像 `sdcard-rv.img` / `sdcard-la.img` 作为第二个块设备 `vdb` 提供测试内容,镜像根目录包含 `musl/` 与 `glibc/`。本地运行时测试镜像可放在仓库外,并通过 `TESTIMG_RV` / `TESTIMG_LA` 指定路径;启动脚本会把 `vdb` 这个裸 ext4 测试镜像挂载到 `/tests`,再原地运行白名单 musl 测试。 内核默认会从发现到的整盘与分区块设备中探测 ext4 rootfs,优先尝试分区设备,选择含 `/bin/sh` 或 `/bin/ash` 的分区作为 `/`。`oscomp` feature 已弃用并保留为空兼容项,不再改变启动行为。 diff --git a/os/qemu-loongarch-run.sh b/os/qemu-loongarch-run.sh index f5ebdf5d..7dde89d9 100755 --- a/os/qemu-loongarch-run.sh +++ b/os/qemu-loongarch-run.sh @@ -15,7 +15,15 @@ arch="${ARCH:-loongarch}" fs="fs-${arch}.img" disk="disk-la.img" vfat="vfat.img" -test_img="../sdcard-la.img" +test_img="${TESTIMG_LA:-}" +if [ -z "$test_img" ]; then + for candidate in "../../CCYOS/sdcard-la.img" "../sdcard-la.img"; do + if [ -f "$candidate" ]; then + test_img="$candidate" + break + fi + done +fi QEMU_ARGS=( -machine virt @@ -53,12 +61,12 @@ else QEMU_ARGS+=(-drive file="$disk",if=none,format=raw,id=x0) QEMU_ARGS+=(-device virtio-blk-pci,drive=x0) - if [ -f "$test_img" ]; then + if [ -n "$test_img" ] && [ -f "$test_img" ]; then echo "Attaching official test image ${test_img} as vdb" QEMU_ARGS+=(-drive file="$test_img",if=none,format=raw,id=test0) QEMU_ARGS+=(-device virtio-blk-pci,drive=test0) else - echo "Warning: official test image ${test_img} not found; skipping vdb attachment" >&2 + echo "Warning: official test image not found; set TESTIMG_LA=/path/to/sdcard-la.img to attach one" >&2 fi fi diff --git a/os/qemu-run.sh b/os/qemu-run.sh index 6e3b36e6..e99cea4a 100755 --- a/os/qemu-run.sh +++ b/os/qemu-run.sh @@ -14,7 +14,15 @@ arch="${ARCH:-riscv}" fs="fs-${arch}.img" disk="disk.img" vfat="vfat.img" -test_img="../sdcard-rv.img" +test_img="${TESTIMG_RV:-}" +if [ -z "$test_img" ]; then + for candidate in "../../CCYOS/sdcard-rv.img" "../sdcard-rv.img"; do + if [ -f "$candidate" ]; then + test_img="$candidate" + break + fi + done +fi # 1. 转换为纯二进制 rust-objcopy --strip-all "$ELF_FILE" -O binary "$BIN_FILE" @@ -63,12 +71,12 @@ else QEMU_ARGS="$QEMU_ARGS -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.1" # 官方测试盘是额外裸 ext4 盘,固定注册为 /dev/vdb 后由 rcS 挂载到 /tests。 - if [ -f "$test_img" ]; then + if [ -n "$test_img" ] && [ -f "$test_img" ]; then echo "Attaching official test image ${test_img} as vdb" QEMU_ARGS="$QEMU_ARGS -drive file=$test_img,if=none,format=raw,id=test0" QEMU_ARGS="$QEMU_ARGS -device virtio-blk-device,drive=test0,bus=virtio-mmio-bus.0" else - echo "Warning: official test image ${test_img} not found; skipping vdb attachment" >&2 + echo "Warning: official test image not found; set TESTIMG_RV=/path/to/sdcard-rv.img to attach one" >&2 fi fi From 42f0f67f77afe4283f5d9799a20189077976d2e6 Mon Sep 17 00:00:00 2001 From: LittleSand <1840309785@qq.com> Date: Wed, 24 Jun 2026 09:57:23 +0800 Subject: [PATCH 10/19] fix(loongarch): support ls7a rtc --- os/src/device/rtc/rtc_goldfish.rs | 149 ++++++++++++++++++++++++++---- 1 file changed, 133 insertions(+), 16 deletions(-) diff --git a/os/src/device/rtc/rtc_goldfish.rs b/os/src/device/rtc/rtc_goldfish.rs index 980167ae..63c0fe77 100644 --- a/os/src/device/rtc/rtc_goldfish.rs +++ b/os/src/device/rtc/rtc_goldfish.rs @@ -8,14 +8,28 @@ use crate::{ kernel::current_memory_space, mm::address::{PA, VA}, pr_info, pr_warn, - util::read, + util::{read, write}, }; -const TIMER_TIME_LOW: usize = 0x00; -const TIMER_TIME_HIGH: usize = 0x04; +const GOLDFISH_TIME_LOW: usize = 0x00; +const GOLDFISH_TIME_HIGH: usize = 0x04; + +const LS7A_TOYREAD0: usize = 0x2c; +const LS7A_TOYREAD1: usize = 0x30; +const LS7A_RTCCTRL: usize = 0x40; +const LS7A_RTCCTRL_RTCEN: u32 = 1 << 13; +const LS7A_RTCCTRL_TOYEN: u32 = 1 << 11; +const LS7A_RTCCTRL_EO: u32 = 1 << 8; + +#[derive(Debug, Clone, Copy)] +enum RtcBackend { + Goldfish, + Ls7a, +} pub struct RtcGoldfish { base: VA, + backend: RtcBackend, } impl Driver for RtcGoldfish { @@ -28,7 +42,10 @@ impl Driver for RtcGoldfish { } fn get_id(&self) -> String { - String::from("rtc_goldfish") + match self.backend { + RtcBackend::Goldfish => String::from("rtc_goldfish"), + RtcBackend::Ls7a => String::from("rtc_ls7a"), + } } fn as_rtc(&self) -> Option<&dyn RtcDriver> { @@ -39,41 +56,141 @@ impl Driver for RtcGoldfish { impl RtcDriver for RtcGoldfish { // read seconds since 1970-01-01 fn read_epoch(&self) -> u64 { + match self.backend { + RtcBackend::Goldfish => self.read_goldfish_epoch(), + RtcBackend::Ls7a => self.read_ls7a_epoch(), + } + } +} + +impl RtcGoldfish { + fn read_goldfish_epoch(&self) -> u64 { let base = self.base.as_usize(); - let low: u32 = read(base + TIMER_TIME_LOW); - let high: u32 = read(base + TIMER_TIME_HIGH); + let low: u32 = read(base + GOLDFISH_TIME_LOW); + let high: u32 = read(base + GOLDFISH_TIME_HIGH); let ns = ((high as u64) << 32) | (low as u64); ns / 1_000_000_000u64 } + + fn read_ls7a_epoch(&self) -> u64 { + let base = self.base.as_usize(); + + let mut year: u32 = read(base + LS7A_TOYREAD1); + let toy0: u32 = read(base + LS7A_TOYREAD0); + let year_after: u32 = read(base + LS7A_TOYREAD1); + let toy0 = if year == year_after { + toy0 + } else { + year = year_after; + read(base + LS7A_TOYREAD0) + }; + + let month = (toy0 >> 26) & 0x3f; + let day = (toy0 >> 21) & 0x1f; + let hour = (toy0 >> 16) & 0x1f; + let minute = (toy0 >> 10) & 0x3f; + let second = (toy0 >> 4) & 0x3f; + + utc_to_epoch(1900 + year as i32, month, day, hour, minute, second).unwrap_or(0) + } } -fn init_dt(dt: &FdtNode) { +fn init_dt(dt: &FdtNode, backend: RtcBackend) { let reg = dt .reg() .and_then(|mut reg| reg.next()) - .expect("No reg property found for goldfish-rtc"); + .expect("No reg property found for RTC"); let paddr = reg.starting_address as usize; let size = reg.size.unwrap_or(0); if size == 0 { - pr_warn!( - "[Device] goldfish-rtc device tree node {} has no size", - dt.name - ); + pr_warn!("[Device] RTC device tree node {} has no size", dt.name); return; } let vaddr = current_memory_space() .lock() .map_mmio(PA::from_usize(paddr), size) .ok() - .expect("Failed to map MMIO region for goldfish-rtc"); - let rtc = Arc::new(RtcGoldfish { base: vaddr }); + .expect("Failed to map MMIO region for RTC"); + if matches!(backend, RtcBackend::Ls7a) { + let ctrl: u32 = read(vaddr.as_usize() + LS7A_RTCCTRL); + write( + vaddr.as_usize() + LS7A_RTCCTRL, + ctrl | LS7A_RTCCTRL_EO | LS7A_RTCCTRL_TOYEN | LS7A_RTCCTRL_RTCEN, + ); + } + + let rtc = Arc::new(RtcGoldfish { + base: vaddr, + backend, + }); DRIVERS.write().push(rtc.clone()); RTC_DRIVERS.write().push(rtc); - pr_info!("[Device] RTC Goldfish initialized"); + pr_info!("[Device] RTC {:?} initialized", backend); +} + +fn init_goldfish_dt(dt: &FdtNode) { + init_dt(dt, RtcBackend::Goldfish); +} + +fn init_ls7a_dt(dt: &FdtNode) { + init_dt(dt, RtcBackend::Ls7a); +} + +fn utc_to_epoch( + year: i32, + month: u32, + day: u32, + hour: u32, + minute: u32, + second: u32, +) -> Option { + if !(1..=12).contains(&month) + || !(1..=31).contains(&day) + || hour > 23 + || minute > 59 + || second > 60 + || year < 1970 + { + return None; + } + + let mut days = 0u64; + for y in 1970..year { + days += if is_leap_year(y) { 366 } else { 365 }; + } + + for m in 1..month { + days += days_in_month(year, m)? as u64; + } + + let dim = days_in_month(year, month)?; + if day > dim { + return None; + } + + days += (day - 1) as u64; + Some(days * 86_400 + hour as u64 * 3_600 + minute as u64 * 60 + second.min(59) as u64) +} + +fn days_in_month(year: i32, month: u32) -> Option { + Some(match month { + 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31, + 4 | 6 | 9 | 11 => 30, + 2 if is_leap_year(year) => 29, + 2 => 28, + _ => return None, + }) +} + +fn is_leap_year(year: i32) -> bool { + year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) } pub fn driver_init() { DEVICE_TREE_REGISTRY .lock() - .insert("google,goldfish-rtc", init_dt); + .insert("google,goldfish-rtc", init_goldfish_dt); + DEVICE_TREE_REGISTRY + .lock() + .insert("loongson,ls7a-rtc", init_ls7a_dt); } From 824f80cdbedc37fee79c46a03e0495b73e159595 Mon Sep 17 00:00:00 2001 From: LittleSand <1840309785@qq.com> Date: Wed, 24 Jun 2026 12:24:15 +0800 Subject: [PATCH 11/19] fix(loongarch): poll network on timer ticks --- os/src/arch/loongarch/trap/trap_handler.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/os/src/arch/loongarch/trap/trap_handler.rs b/os/src/arch/loongarch/trap/trap_handler.rs index c270dcb4..de689fcc 100644 --- a/os/src/arch/loongarch/trap/trap_handler.rs +++ b/os/src/arch/loongarch/trap/trap_handler.rs @@ -260,6 +260,11 @@ fn user_panic(estat: usize, era: usize, trap_frame: &TrapFrame) { /// 处理时钟中断 fn check_timer() { let _ticks = TIMER_TICKS.fetch_add(1, Ordering::Relaxed); + + // Loopback/null-net paths do not have device interrupts to advance smoltcp. + crate::net::socket::poll_network_interfaces(); + crate::kernel::syscall::io::wake_poll_waiters(); + while let Some(task) = TIMER_QUEUE.lock().pop_due_task(get_time()) { wake_up_task(task); } From ecc729d2cefb88e32e52a5c14a51e1abdbc4e0d4 Mon Sep 17 00:00:00 2001 From: LittleSand <1840309785@qq.com> Date: Wed, 24 Jun 2026 12:27:02 +0800 Subject: [PATCH 12/19] fix(loongarch): use hardware time for milliseconds --- os/src/arch/loongarch/timer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/os/src/arch/loongarch/timer.rs b/os/src/arch/loongarch/timer.rs index 2bd769a1..fe330930 100644 --- a/os/src/arch/loongarch/timer.rs +++ b/os/src/arch/loongarch/timer.rs @@ -74,5 +74,5 @@ pub fn ack_timer_interrupt() { /// 获取当前时间(毫秒) pub fn get_time_ms() -> usize { - get_ticks() * 1000 / TICKS_PER_SEC + (get_time() as u128 * 1000u128 / clock_freq() as u128) as usize } From caf3186b56df15aee596b6b8ac622dc7f080cd88 Mon Sep 17 00:00:00 2001 From: LittleSand <1840309785@qq.com> Date: Wed, 24 Jun 2026 13:15:39 +0800 Subject: [PATCH 13/19] fix(loongarch): use raw boot trap frame storage --- os/src/arch/loongarch/trap/trap_handler.rs | 39 +++++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/os/src/arch/loongarch/trap/trap_handler.rs b/os/src/arch/loongarch/trap/trap_handler.rs index de689fcc..00a8f2b6 100644 --- a/os/src/arch/loongarch/trap/trap_handler.rs +++ b/os/src/arch/loongarch/trap/trap_handler.rs @@ -1,6 +1,9 @@ //! LoongArch64 陷阱处理实现(与 RISC-V 路径一致的接口)。 -use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; +use core::{ + cell::UnsafeCell, + sync::atomic::{AtomicBool, AtomicUsize, Ordering}, +}; use crate::arch::constant::{ CSR_BADI, CSR_BADV, CSR_CRMD_PLV_MASK, CSR_EENTRY, CSR_ESTAT_IS_MASK, CSR_TLBRENT, @@ -12,7 +15,6 @@ use crate::arch::trap::restore; use crate::ipc::check_signal; use crate::kernel::syscall::dispatch::dispatch_syscall; use crate::kernel::{TIMER, TIMER_QUEUE, schedule, send_signal_process, wake_up_task}; -use crate::sync::SpinLock; use super::TrapFrame; @@ -22,8 +24,25 @@ macro_rules! emergency_println { }; } -/// 仅在单核环境下使用的默认 TrapFrame;后续可由调度器替换为 per-CPU/任务帧 -pub static BOOT_TRAP_FRAME: SpinLock = SpinLock::new(TrapFrame::empty()); +/// 仅在单核启动/兜底路径使用的 TrapFrame。 +/// +/// trap_entry 会通过 KScratch0 异步写入这里;普通 SpinLock 不能保护这种写入, +/// 安装入口时也不应在这个保存区上持锁。 +struct BootTrapFrame(UnsafeCell); + +unsafe impl Sync for BootTrapFrame {} + +impl BootTrapFrame { + const fn new() -> Self { + Self(UnsafeCell::new(TrapFrame::empty())) + } + + fn get(&self) -> *mut TrapFrame { + self.0.get() + } +} + +static BOOT_TRAP_FRAME: BootTrapFrame = BootTrapFrame::new(); static FIRST_TRAP_LOGGED: AtomicBool = AtomicBool::new(false); static FIRST_USER_TIMER_LOGGED: AtomicBool = AtomicBool::new(false); @@ -96,21 +115,23 @@ pub(super) fn install_runtime_trap() { } fn install_trap_entry() { - // 将 TrapFrame 指针写入 KScratch0,并设置 EENTRY 指向 trap_entry + // 将 TrapFrame 指针写入 KScratch0,并设置 EENTRY 指向 trap_entry。 + // BOOT_TRAP_FRAME 本身就是 trap 保存区,不能用会在安装期间释放的锁来保护。 unsafe { - let mut boot_trap_frame = BOOT_TRAP_FRAME.lock(); + let boot_trap_frame = BOOT_TRAP_FRAME.get(); // 设置内核栈指针用于用户态陷阱的栈切换 let sp: usize; core::arch::asm!("addi.d {0}, $sp, 0", out(reg) sp, options(nostack, preserves_flags)); - boot_trap_frame.kernel_sp = sp; - boot_trap_frame.cpu_ptr = crate::kernel::current_cpu() as *const _ as usize; + (*boot_trap_frame).kernel_sp = sp; + (*boot_trap_frame).cpu_ptr = crate::kernel::current_cpu() as *const _ as usize; // KScratch0 <- TrapFrame 指针 core::arch::asm!( "csrwr {0}, 0x30", - in(reg) (&mut *boot_trap_frame as *mut TrapFrame as usize), + in(reg) boot_trap_frame as usize, options(nostack, preserves_flags) ); + // EENTRY <- trap_entry(注意 CSR 编号为 0xc) core::arch::asm!( "csrwr {val}, {csr}", From f27de7225d964a8ba3c5212bd7179f3a53be2302 Mon Sep 17 00:00:00 2001 From: LittleSand <1840309785@qq.com> Date: Wed, 24 Jun 2026 13:16:28 +0800 Subject: [PATCH 14/19] fix(loongarch): save user fpu context across traps --- os/src/arch/loongarch/trap/trap_entry.S | 122 +++++++++++++++++++++++ os/src/arch/loongarch/trap/trap_frame.rs | 20 +++- os/src/uapi/signal.rs | 3 +- 3 files changed, 142 insertions(+), 3 deletions(-) diff --git a/os/src/arch/loongarch/trap/trap_entry.S b/os/src/arch/loongarch/trap/trap_entry.S index 0a7bb97f..993a45b0 100644 --- a/os/src/arch/loongarch/trap/trap_entry.S +++ b/os/src/arch/loongarch/trap/trap_entry.S @@ -6,6 +6,122 @@ .globl __restore .globl trap_handler +.equ TF_FREGS, 304 +.equ TF_FCSR, 560 +.equ TF_FCC, 568 + +.macro save_fp_state base, tmp0, tmp1 + fst.d $f0, \base, TF_FREGS + 0 * 8 + fst.d $f1, \base, TF_FREGS + 1 * 8 + fst.d $f2, \base, TF_FREGS + 2 * 8 + fst.d $f3, \base, TF_FREGS + 3 * 8 + fst.d $f4, \base, TF_FREGS + 4 * 8 + fst.d $f5, \base, TF_FREGS + 5 * 8 + fst.d $f6, \base, TF_FREGS + 6 * 8 + fst.d $f7, \base, TF_FREGS + 7 * 8 + fst.d $f8, \base, TF_FREGS + 8 * 8 + fst.d $f9, \base, TF_FREGS + 9 * 8 + fst.d $f10, \base, TF_FREGS + 10 * 8 + fst.d $f11, \base, TF_FREGS + 11 * 8 + fst.d $f12, \base, TF_FREGS + 12 * 8 + fst.d $f13, \base, TF_FREGS + 13 * 8 + fst.d $f14, \base, TF_FREGS + 14 * 8 + fst.d $f15, \base, TF_FREGS + 15 * 8 + fst.d $f16, \base, TF_FREGS + 16 * 8 + fst.d $f17, \base, TF_FREGS + 17 * 8 + fst.d $f18, \base, TF_FREGS + 18 * 8 + fst.d $f19, \base, TF_FREGS + 19 * 8 + fst.d $f20, \base, TF_FREGS + 20 * 8 + fst.d $f21, \base, TF_FREGS + 21 * 8 + fst.d $f22, \base, TF_FREGS + 22 * 8 + fst.d $f23, \base, TF_FREGS + 23 * 8 + fst.d $f24, \base, TF_FREGS + 24 * 8 + fst.d $f25, \base, TF_FREGS + 25 * 8 + fst.d $f26, \base, TF_FREGS + 26 * 8 + fst.d $f27, \base, TF_FREGS + 27 * 8 + fst.d $f28, \base, TF_FREGS + 28 * 8 + fst.d $f29, \base, TF_FREGS + 29 * 8 + fst.d $f30, \base, TF_FREGS + 30 * 8 + fst.d $f31, \base, TF_FREGS + 31 * 8 + + movfcsr2gr \tmp0, $fcsr0 + st.d \tmp0, \base, TF_FCSR + + movcf2gr \tmp0, $fcc0 + move \tmp1, \tmp0 + movcf2gr \tmp0, $fcc1 + bstrins.d \tmp1, \tmp0, 15, 8 + movcf2gr \tmp0, $fcc2 + bstrins.d \tmp1, \tmp0, 23, 16 + movcf2gr \tmp0, $fcc3 + bstrins.d \tmp1, \tmp0, 31, 24 + movcf2gr \tmp0, $fcc4 + bstrins.d \tmp1, \tmp0, 39, 32 + movcf2gr \tmp0, $fcc5 + bstrins.d \tmp1, \tmp0, 47, 40 + movcf2gr \tmp0, $fcc6 + bstrins.d \tmp1, \tmp0, 55, 48 + movcf2gr \tmp0, $fcc7 + bstrins.d \tmp1, \tmp0, 63, 56 + st.d \tmp1, \base, TF_FCC +.endm + +.macro restore_fp_state base, tmp0, tmp1 + fld.d $f0, \base, TF_FREGS + 0 * 8 + fld.d $f1, \base, TF_FREGS + 1 * 8 + fld.d $f2, \base, TF_FREGS + 2 * 8 + fld.d $f3, \base, TF_FREGS + 3 * 8 + fld.d $f4, \base, TF_FREGS + 4 * 8 + fld.d $f5, \base, TF_FREGS + 5 * 8 + fld.d $f6, \base, TF_FREGS + 6 * 8 + fld.d $f7, \base, TF_FREGS + 7 * 8 + fld.d $f8, \base, TF_FREGS + 8 * 8 + fld.d $f9, \base, TF_FREGS + 9 * 8 + fld.d $f10, \base, TF_FREGS + 10 * 8 + fld.d $f11, \base, TF_FREGS + 11 * 8 + fld.d $f12, \base, TF_FREGS + 12 * 8 + fld.d $f13, \base, TF_FREGS + 13 * 8 + fld.d $f14, \base, TF_FREGS + 14 * 8 + fld.d $f15, \base, TF_FREGS + 15 * 8 + fld.d $f16, \base, TF_FREGS + 16 * 8 + fld.d $f17, \base, TF_FREGS + 17 * 8 + fld.d $f18, \base, TF_FREGS + 18 * 8 + fld.d $f19, \base, TF_FREGS + 19 * 8 + fld.d $f20, \base, TF_FREGS + 20 * 8 + fld.d $f21, \base, TF_FREGS + 21 * 8 + fld.d $f22, \base, TF_FREGS + 22 * 8 + fld.d $f23, \base, TF_FREGS + 23 * 8 + fld.d $f24, \base, TF_FREGS + 24 * 8 + fld.d $f25, \base, TF_FREGS + 25 * 8 + fld.d $f26, \base, TF_FREGS + 26 * 8 + fld.d $f27, \base, TF_FREGS + 27 * 8 + fld.d $f28, \base, TF_FREGS + 28 * 8 + fld.d $f29, \base, TF_FREGS + 29 * 8 + fld.d $f30, \base, TF_FREGS + 30 * 8 + fld.d $f31, \base, TF_FREGS + 31 * 8 + + ld.d \tmp0, \base, TF_FCSR + movgr2fcsr $fcsr0, \tmp0 + + ld.d \tmp0, \base, TF_FCC + bstrpick.d \tmp1, \tmp0, 7, 0 + movgr2cf $fcc0, \tmp1 + bstrpick.d \tmp1, \tmp0, 15, 8 + movgr2cf $fcc1, \tmp1 + bstrpick.d \tmp1, \tmp0, 23, 16 + movgr2cf $fcc2, \tmp1 + bstrpick.d \tmp1, \tmp0, 31, 24 + movgr2cf $fcc3, \tmp1 + bstrpick.d \tmp1, \tmp0, 39, 32 + movgr2cf $fcc4, \tmp1 + bstrpick.d \tmp1, \tmp0, 47, 40 + movgr2cf $fcc5, \tmp1 + bstrpick.d \tmp1, \tmp0, 55, 48 + movgr2cf $fcc6, \tmp1 + bstrpick.d \tmp1, \tmp0, 63, 56 + movgr2cf $fcc7, \tmp1 +.endm + trap_entry: boot_trap_entry: # 保留原始寄存器,避免覆盖 KScratch0(其长期保存 TrapFrame 指针) @@ -69,6 +185,7 @@ boot_trap_entry: # 若来自用户态,则切换到保存的内核栈 andi $t2, $t1, 0x3 # PRMD.PLV 位 beqz $t2, 1f + save_fp_state $a0, $t2, $t3 ld.d $sp, $a0, 288 # kernel_sp 1: # 调用 Rust trap_handler(trap_frame) @@ -87,6 +204,11 @@ __restore: ld.d $t0, $r21, 280 # PRMD csrwr $t0, 0x1 + andi $t0, $t0, 0x3 # PRMD.PLV 位 + beqz $t0, 1f + restore_fp_state $r21, $t0, $t1 +1: + # 恢复通用寄存器(保持 r21 基址到最后) ld.d $ra, $r21, 8 ld.d $tp, $r21, 16 diff --git a/os/src/arch/loongarch/trap/trap_frame.rs b/os/src/arch/loongarch/trap/trap_frame.rs index c238db29..140f2b29 100644 --- a/os/src/arch/loongarch/trap/trap_frame.rs +++ b/os/src/arch/loongarch/trap/trap_frame.rs @@ -22,6 +22,12 @@ pub struct TrapFrame { pub kernel_sp: usize, /// 当前 CPU 结构体指针(供 trap_entry 设置 tp) pub cpu_ptr: usize, + /// 浮点寄存器 f0-f31。 + pub fregs: [u64; 32], + /// 浮点控制状态寄存器 fcsr0。 + pub fcsr: u64, + /// 浮点条件码寄存器 fcc0-fcc7,按 8-bit lane 打包。 + pub fcc: u64, } impl TrapFrame { @@ -35,6 +41,9 @@ impl TrapFrame { prmd: 0, kernel_sp: 0, cpu_ptr: 0, + fregs: [0; 32], + fcsr: 0, + fcc: 0, } } @@ -234,7 +243,13 @@ impl TrapFrame { } MContextT { gregs, - fpregs: [0; 66], + fpregs: { + let mut fpregs = [0; 66]; + fpregs[..32].copy_from_slice(&self.fregs); + fpregs[32] = self.fcsr; + fpregs[33] = self.fcc; + fpregs + }, } } @@ -243,6 +258,9 @@ impl TrapFrame { for i in 0..32 { self.regs[i] = mcontext.gregs[i] as usize; } + self.fregs.copy_from_slice(&mcontext.fpregs[..32]); + self.fcsr = mcontext.fpregs[32]; + self.fcc = mcontext.fpregs[33]; } } diff --git a/os/src/uapi/signal.rs b/os/src/uapi/signal.rs index 3ba98bc8..60843e85 100644 --- a/os/src/uapi/signal.rs +++ b/os/src/uapi/signal.rs @@ -540,8 +540,7 @@ pub struct MContextT { /// 通用寄存器数组 pub gregs: [c_ulong; 32], /// 浮点寄存器数组 - /// XXX: 实际并未使用浮点寄存器 - /// 且注意struct pending? + /// LoongArch uses 0..31 for FPRs, 32 for FCSR, and 33 for packed FCC. pub fpregs: [c_ulonglong; 66], } From cb1e195c933ea8fcb43985007f7a23e06e038d17 Mon Sep 17 00:00:00 2001 From: LittleSand <1840309785@qq.com> Date: Wed, 24 Jun 2026 14:47:38 +0800 Subject: [PATCH 15/19] fix(loongarch): restore fpu using target prmd --- os/src/arch/loongarch/trap/trap_entry.S | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/os/src/arch/loongarch/trap/trap_entry.S b/os/src/arch/loongarch/trap/trap_entry.S index 993a45b0..a291074a 100644 --- a/os/src/arch/loongarch/trap/trap_entry.S +++ b/os/src/arch/loongarch/trap/trap_entry.S @@ -202,10 +202,10 @@ __restore: ld.d $t0, $r21, 256 # ERA csrwr $t0, 0x6 ld.d $t0, $r21, 280 # PRMD + andi $t1, $t0, 0x3 # 目标 PRMD.PLV 位;csrwr 会把旧 PRMD 写回 $t0 csrwr $t0, 0x1 - andi $t0, $t0, 0x3 # PRMD.PLV 位 - beqz $t0, 1f + beqz $t1, 1f restore_fp_state $r21, $t0, $t1 1: From 78cccad2bf4b01f9af50c07754c828df252dc4b9 Mon Sep 17 00:00:00 2001 From: LittleSand <1840309785@qq.com> Date: Wed, 24 Jun 2026 14:52:38 +0800 Subject: [PATCH 16/19] fix(net): defer timer network polling to worker --- os/src/arch/loongarch/trap/trap_handler.rs | 6 +++--- os/src/arch/riscv/trap/trap_handler.rs | 6 ++---- os/src/kernel/task/work_queue.rs | 4 +++- os/src/net/socket.rs | 25 ++++++++++++++++++++++ 4 files changed, 33 insertions(+), 8 deletions(-) diff --git a/os/src/arch/loongarch/trap/trap_handler.rs b/os/src/arch/loongarch/trap/trap_handler.rs index 00a8f2b6..7d224232 100644 --- a/os/src/arch/loongarch/trap/trap_handler.rs +++ b/os/src/arch/loongarch/trap/trap_handler.rs @@ -282,9 +282,9 @@ fn user_panic(estat: usize, era: usize, trap_frame: &TrapFrame) { fn check_timer() { let _ticks = TIMER_TICKS.fetch_add(1, Ordering::Relaxed); - // Loopback/null-net paths do not have device interrupts to advance smoltcp. - crate::net::socket::poll_network_interfaces(); - crate::kernel::syscall::io::wake_poll_waiters(); + // Loopback/null-net paths need periodic progress, but smoltcp should not run + // directly in hard interrupt context. + crate::net::socket::request_network_poll(); while let Some(task) = TIMER_QUEUE.lock().pop_due_task(get_time()) { wake_up_task(task); diff --git a/os/src/arch/riscv/trap/trap_handler.rs b/os/src/arch/riscv/trap/trap_handler.rs index 8e6d92ac..e9515689 100644 --- a/os/src/arch/riscv/trap/trap_handler.rs +++ b/os/src/arch/riscv/trap/trap_handler.rs @@ -335,10 +335,8 @@ pub fn kernel_trap(scause: scause::Scause, sepc_old: usize, sstatus_old: sstatus pub fn check_timer() { let _ticks = TIMER_TICKS.fetch_add(1, Ordering::Relaxed); - // 推进网络栈,避免在仅有 loopback/null-net 且任务阻塞在 select/poll 时网络停滞。 - // 在时钟中断里推进一次网络栈,保证即便缺少真实网卡中断也能推进 TCP 状态机/重传等。 - crate::net::socket::poll_network_interfaces(); - crate::kernel::syscall::io::wake_poll_waiters(); + // 推进网络栈的请求放到 kworker 中执行,避免在硬中断上下文里持有网络栈锁。 + crate::net::socket::request_network_poll(); while let Some(task) = TIMER_QUEUE.lock().pop_due_task(get_time()) { wake_up_task(task); diff --git a/os/src/kernel/task/work_queue.rs b/os/src/kernel/task/work_queue.rs index d9c91492..410f5fd3 100644 --- a/os/src/kernel/task/work_queue.rs +++ b/os/src/kernel/task/work_queue.rs @@ -75,13 +75,15 @@ pub fn kworker() { let mut queue = GLOBAL_WORK_QUEUE.lock(); if let Some(work) = queue.work_queue.pop_front() { + drop(queue); (work.task)(); } else { queue.sleeping += 1; sleep_task(current_task(), true); drop(queue); yield_task(); - GLOBAL_WORK_QUEUE.lock().sleeping -= 1; + let mut queue = GLOBAL_WORK_QUEUE.lock(); + queue.sleeping = queue.sleeping.saturating_sub(1); } } } diff --git a/os/src/net/socket.rs b/os/src/net/socket.rs index 5a6ba2ad..6c442b1a 100644 --- a/os/src/net/socket.rs +++ b/os/src/net/socket.rs @@ -1,10 +1,12 @@ //! Socket implementation using smoltcp use crate::arch::Arch; +use crate::kernel::{GLOBAL_WORK_QUEUE, WorkItem}; use crate::net::NetworkError; use crate::sync::SpinLock; use crate::vfs::{File, FsError, InodeMetadata}; use alloc::collections::VecDeque; +use core::sync::atomic::{AtomicBool, Ordering}; use lazy_static::lazy_static; use smoltcp::iface::SocketHandle as SmoltcpHandle; use smoltcp::wire::{IpAddress, IpEndpoint, Ipv4Address}; @@ -23,6 +25,8 @@ lazy_static! { SpinLock::new(BTreeMap::new()); } +static NETWORK_POLL_PENDING: AtomicBool = AtomicBool::new(false); + use crate::uapi::fcntl::OpenFlags; use crate::uapi::socket::SocketOptions; @@ -486,6 +490,27 @@ pub fn poll_network_interfaces() { crate::net::stack::network_stack().poll(); } +fn network_poll_work() { + NETWORK_POLL_PENDING.store(false, Ordering::Release); + poll_network_interfaces(); + crate::kernel::syscall::io::wake_poll_waiters(); +} + +/// Request a network poll from thread context. +/// +/// Timer interrupts use this as a lightweight bottom-half handoff so smoltcp +/// and poll wait queues are not driven directly from hard interrupt context. +pub fn request_network_poll() { + if NETWORK_POLL_PENDING + .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire) + .is_ok() + { + GLOBAL_WORK_QUEUE + .lock() + .schedule_work(WorkItem::new(network_poll_work)); + } +} + /// Poll smoltcp + dispatch UDP datagrams to per-fd queues. pub fn poll_network_and_dispatch() { crate::net::stack::network_stack().poll_and_dispatch(); From 01b8254907b9e1dea77366c2f88cf84e8168f68e Mon Sep 17 00:00:00 2001 From: LittleSand <1840309785@qq.com> Date: Wed, 24 Jun 2026 14:58:16 +0800 Subject: [PATCH 17/19] fix(ext4): shift extents on insert --- third_party/ext4_rs/src/ext4_impls/extents.rs | 54 +++++++++++++++--- vendor/ext4_rs/src/ext4_impls/extents.rs | 55 ++++++++++++++++--- 2 files changed, 95 insertions(+), 14 deletions(-) diff --git a/third_party/ext4_rs/src/ext4_impls/extents.rs b/third_party/ext4_rs/src/ext4_impls/extents.rs index d2f61828..869311ac 100644 --- a/third_party/ext4_rs/src/ext4_impls/extents.rs +++ b/third_party/ext4_rs/src/ext4_impls/extents.rs @@ -6,6 +6,14 @@ use alloc::format; use core::mem::size_of; impl Ext4 { + fn extent_insert_pos(node: &ExtentPathNode, new_extent: &Ext4Extent) -> usize { + match node.extent { + Some(extent) if new_extent.first_block < extent.first_block => node.position, + Some(_) => node.position + 1, + None => node.position, + } + } + /// Find an extent in the extent tree. /// /// Params: @@ -417,13 +425,28 @@ impl Ext4 { return self.insert_extent(inode_ref, new_extent); } - // Not empty, insert at search result pos + 1 + // Not empty, insert at search result position. + let insert_pos = Self::extent_insert_pos(node, new_extent); log::info!( "[insert_new_extent] Inserting at root at position {} (entries: {})", - node.position + 1, + insert_pos, header.entries_count ); - *inode_ref.inode.root_extent_mut_at(node.position + 1) = *new_extent; + let entries_count = header.entries_count as usize; + if insert_pos > entries_count { + return_errno_with_message!(Errno::EINVAL, "Invalid root extent insert position"); + } + let root_capacity = (15 * core::mem::size_of::() + - core::mem::size_of::()) + / core::mem::size_of::(); + if entries_count >= root_capacity { + return_errno_with_message!(Errno::EINVAL, "Root extent node has no insert space"); + } + for pos in (insert_pos..entries_count).rev() { + let extent = inode_ref.inode.root_extent_at(pos); + *inode_ref.inode.root_extent_mut_at(pos + 1) = extent; + } + *inode_ref.inode.root_extent_mut_at(insert_pos) = *new_extent; inode_ref.inode.root_extent_header_mut().entries_count += 1; log::debug!("[insert_new_extent] Successfully inserted at root:"); @@ -441,16 +464,33 @@ impl Ext4 { log::info!( "[insert_new_extent] Inserting at non-root node at depth {}, position {}", depth, - node.position + 1 + Self::extent_insert_pos(node, new_extent) ); // load block let node_block = node.pblock_of_node; let mut ext4block = Block::load(self.block_device.clone(), node_block * BLOCK_SIZE); - let new_ex_offset = core::mem::size_of::() - + core::mem::size_of::() * (node.position + 1); + let insert_pos = Self::extent_insert_pos(node, new_extent); + let entries_count = header.entries_count as usize; + if insert_pos > entries_count { + return_errno_with_message!(Errno::EINVAL, "Invalid extent insert position"); + } + let extent_size = core::mem::size_of::(); + let extent_base = core::mem::size_of::(); + let new_ex_offset = extent_base + extent_size * insert_pos; + let used_end_after_insert = extent_base + extent_size * (entries_count + 1); + if used_end_after_insert > ext4block.data.len() { + return_errno_with_message!(Errno::EINVAL, "Extent block has no insert space"); + } // insert new extent + if insert_pos < entries_count { + let move_start = new_ex_offset; + let move_end = extent_base + extent_size * entries_count; + ext4block + .data + .copy_within(move_start..move_end, move_start + extent_size); + } let ex: &mut Ext4Extent = ext4block.read_offset_as_mut(new_ex_offset); *ex = *new_extent; let header: &mut Ext4ExtentHeader = ext4block.read_offset_as_mut(0); @@ -488,7 +528,7 @@ impl Ext4 { depth ); log::debug!(" - Block address: {}", node_block); - log::debug!(" - Extent position: {}", node.position + 1); + log::debug!(" - Extent position: {}", insert_pos); log::debug!( " - Extent: logical={}, physical={}, length={}", new_extent.first_block, diff --git a/vendor/ext4_rs/src/ext4_impls/extents.rs b/vendor/ext4_rs/src/ext4_impls/extents.rs index cf7e7e6f..39fead1a 100644 --- a/vendor/ext4_rs/src/ext4_impls/extents.rs +++ b/vendor/ext4_rs/src/ext4_impls/extents.rs @@ -7,6 +7,14 @@ use crate::utils::crc::*; impl Ext4 { + fn extent_insert_pos(node: &ExtentPathNode, new_extent: &Ext4Extent) -> usize { + match node.extent { + Some(extent) if new_extent.first_block < extent.first_block => node.position, + Some(_) => node.position + 1, + None => node.position, + } + } + /// Find an extent in the extent tree. /// /// Params: @@ -386,10 +394,25 @@ impl Ext4 { } - // Not empty, insert at search result pos + 1 + // Not empty, insert at search result position. + let insert_pos = Self::extent_insert_pos(node, new_extent); log::info!("[insert_new_extent] Inserting at root at position {} (entries: {})", - node.position + 1, header.entries_count); - *inode_ref.inode.root_extent_mut_at(node.position + 1) = *new_extent; + insert_pos, header.entries_count); + let entries_count = header.entries_count as usize; + if insert_pos > entries_count { + return_errno_with_message!(Errno::EINVAL, "Invalid root extent insert position"); + } + let root_capacity = (15 * core::mem::size_of::() + - core::mem::size_of::()) + / core::mem::size_of::(); + if entries_count >= root_capacity { + return_errno_with_message!(Errno::EINVAL, "Root extent node has no insert space"); + } + for pos in (insert_pos..entries_count).rev() { + let extent = inode_ref.inode.root_extent_at(pos); + *inode_ref.inode.root_extent_mut_at(pos + 1) = extent; + } + *inode_ref.inode.root_extent_mut_at(insert_pos) = *new_extent; inode_ref.inode.root_extent_header_mut().entries_count += 1; log::debug!("[insert_new_extent] Successfully inserted at root:"); @@ -403,15 +426,33 @@ impl Ext4 { } else { // insert at nonroot log::info!("[insert_new_extent] Inserting at non-root node at depth {}, position {}", - depth, node.position + 1); + depth, Self::extent_insert_pos(node, new_extent)); // load block let node_block = node.pblock_of_node; let mut ext4block = Block::load(self.block_device.clone(), node_block * BLOCK_SIZE); - let new_ex_offset = core::mem::size_of::() + core::mem::size_of::() * (node.position + 1); + let insert_pos = Self::extent_insert_pos(node, new_extent); + let entries_count = header.entries_count as usize; + if insert_pos > entries_count { + return_errno_with_message!(Errno::EINVAL, "Invalid extent insert position"); + } + let extent_size = core::mem::size_of::(); + let extent_base = core::mem::size_of::(); + let new_ex_offset = extent_base + extent_size * insert_pos; + let used_end_after_insert = extent_base + extent_size * (entries_count + 1); + if used_end_after_insert > ext4block.data.len() { + return_errno_with_message!(Errno::EINVAL, "Extent block has no insert space"); + } // insert new extent + if insert_pos < entries_count { + let move_start = new_ex_offset; + let move_end = extent_base + extent_size * entries_count; + ext4block + .data + .copy_within(move_start..move_end, move_start + extent_size); + } let ex: &mut Ext4Extent = ext4block.read_offset_as_mut(new_ex_offset); *ex = *new_extent; let header: &mut Ext4ExtentHeader = ext4block.read_offset_as_mut(0); @@ -439,7 +480,7 @@ impl Ext4 { log::debug!(" - Node header: entries={}, max={}, depth={}", node_header_entries, node_header_max, depth); log::debug!(" - Block address: {}", node_block); - log::debug!(" - Extent position: {}", node.position + 1); + log::debug!(" - Extent position: {}", insert_pos); log::debug!(" - Extent: logical={}, physical={}, length={}", new_extent.first_block, new_extent.get_pblock(), new_extent.get_actual_len()); @@ -1252,4 +1293,4 @@ impl Ext4 { pub fn ext4_extent_tail_offset(header: &Ext4ExtentHeader) -> usize { size_of::() + (header.max_entries_count as usize * size_of::()) -} \ No newline at end of file +} From 39e40466994b78d21db417324a09059a0f7d9bd0 Mon Sep 17 00:00:00 2001 From: LittleSand <1840309785@qq.com> Date: Wed, 24 Jun 2026 15:00:25 +0800 Subject: [PATCH 18/19] fix(ext4): bound bitmap bit access --- third_party/ext4_rs/src/utils/bitmap.rs | 20 +++++++++++++++++--- vendor/ext4_rs/src/utils/bitmap.rs | 22 ++++++++++++++++++---- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/third_party/ext4_rs/src/utils/bitmap.rs b/third_party/ext4_rs/src/utils/bitmap.rs index aee4885f..54439dcf 100644 --- a/third_party/ext4_rs/src/utils/bitmap.rs +++ b/third_party/ext4_rs/src/utils/bitmap.rs @@ -2,7 +2,9 @@ /// Parameter bmap: Bitmap array /// Parameter bit: Bit index in the bitmap pub fn ext4_bmap_is_bit_set(bmap: &[u8], bit: u32) -> bool { - bmap[(bit >> 3) as usize] & (1 << (bit & 7)) != 0 + bmap.get((bit >> 3) as usize) + .map(|byte| byte & (1 << (bit & 7)) != 0) + .unwrap_or(true) } /// Check if a bit is cleared in the bitmap @@ -16,14 +18,18 @@ pub fn ext4_bmap_is_bit_clr(bmap: &[u8], bit: u32) -> bool { /// Parameter bmap: Bitmap array /// Parameter bit: Bit index in the bitmap pub fn ext4_bmap_bit_set(bmap: &mut [u8], bit: u32) { - bmap[(bit >> 3) as usize] |= 1 << (bit & 7); + if let Some(byte) = bmap.get_mut((bit >> 3) as usize) { + *byte |= 1 << (bit & 7); + } } /// Clear a bit in the bitmap /// Parameter bmap: Bitmap array /// Parameter bit: Bit index in the bitmap pub fn ext4_bmap_bit_clr(bmap: &mut [u8], bit: u32) { - bmap[(bit >> 3) as usize] &= !(1 << (bit & 7)); + if let Some(byte) = bmap.get_mut((bit >> 3) as usize) { + *byte &= !(1 << (bit & 7)); + } } /// Find a free bit in the bitmap @@ -32,6 +38,10 @@ pub fn ext4_bmap_bit_clr(bmap: &mut [u8], bit: u32) { /// Parameter ebit: End bit index /// Parameter bit_id: Reference to store the free bit index pub fn ext4_bmap_bit_find_clr(bmap: &[u8], sbit: u32, ebit: u32, bit_id: &mut u32) -> bool { + if bmap.is_empty() || ebit <= sbit { + return false; + } + let mut i: u32; let mut bcnt = ebit - sbit; @@ -97,6 +107,10 @@ pub fn ext4_bmap_bit_find_clr(bmap: &[u8], sbit: u32, ebit: u32, bit_id: &mut u3 /// Parameter start_bit: The start index of the bit range to clear /// Parameter end_bit: The end index of the bit range to clear pub fn ext4_bmap_bits_free(bmap: &mut [u8], start_bit: u32, end_bit: u32) { + if end_bit < start_bit { + return; + } + for bit in start_bit..=end_bit { ext4_bmap_bit_clr(bmap, bit); } diff --git a/vendor/ext4_rs/src/utils/bitmap.rs b/vendor/ext4_rs/src/utils/bitmap.rs index 95341cdf..6e00d87d 100644 --- a/vendor/ext4_rs/src/utils/bitmap.rs +++ b/vendor/ext4_rs/src/utils/bitmap.rs @@ -2,7 +2,9 @@ /// Parameter bmap: Bitmap array /// Parameter bit: Bit index in the bitmap pub fn ext4_bmap_is_bit_set(bmap: &[u8], bit: u32) -> bool { - bmap[(bit >> 3) as usize] & (1 << (bit & 7)) != 0 + bmap.get((bit >> 3) as usize) + .map(|byte| byte & (1 << (bit & 7)) != 0) + .unwrap_or(true) } /// Check if a bit is cleared in the bitmap @@ -16,14 +18,18 @@ pub fn ext4_bmap_is_bit_clr(bmap: &[u8], bit: u32) -> bool { /// Parameter bmap: Bitmap array /// Parameter bit: Bit index in the bitmap pub fn ext4_bmap_bit_set(bmap: &mut [u8], bit: u32) { - bmap[(bit >> 3) as usize] |= 1 << (bit & 7); + if let Some(byte) = bmap.get_mut((bit >> 3) as usize) { + *byte |= 1 << (bit & 7); + } } /// Clear a bit in the bitmap /// Parameter bmap: Bitmap array /// Parameter bit: Bit index in the bitmap pub fn ext4_bmap_bit_clr(bmap: &mut [u8], bit: u32) { - bmap[(bit >> 3) as usize] &= !(1 << (bit & 7)); + if let Some(byte) = bmap.get_mut((bit >> 3) as usize) { + *byte &= !(1 << (bit & 7)); + } } /// Find a free bit in the bitmap @@ -32,6 +38,10 @@ pub fn ext4_bmap_bit_clr(bmap: &mut [u8], bit: u32) { /// Parameter ebit: End bit index /// Parameter bit_id: Reference to store the free bit index pub fn ext4_bmap_bit_find_clr(bmap: &[u8], sbit: u32, ebit: u32, bit_id: &mut u32) -> bool { + if bmap.is_empty() || ebit <= sbit { + return false; + } + let mut i: u32; let mut bcnt = ebit - sbit; @@ -97,7 +107,11 @@ pub fn ext4_bmap_bit_find_clr(bmap: &[u8], sbit: u32, ebit: u32, bit_id: &mut u3 /// Parameter start_bit: The start index of the bit range to clear /// Parameter end_bit: The end index of the bit range to clear pub fn ext4_bmap_bits_free(bmap: &mut [u8], start_bit: u32, end_bit: u32) { + if end_bit < start_bit { + return; + } + for bit in start_bit..=end_bit { ext4_bmap_bit_clr(bmap, bit); } -} \ No newline at end of file +} From 70f1e9fbe579b973e014ebc24f4776086d77a3dd Mon Sep 17 00:00:00 2001 From: LittleSand <1840309785@qq.com> Date: Wed, 24 Jun 2026 15:02:39 +0800 Subject: [PATCH 19/19] fix(ext4): bound direntry slice copies --- third_party/ext4_rs/src/ext4_defs/direntry.rs | 20 ++++++++----------- vendor/ext4_rs/src/ext4_defs/direntry.rs | 16 ++++++--------- 2 files changed, 14 insertions(+), 22 deletions(-) diff --git a/third_party/ext4_rs/src/ext4_defs/direntry.rs b/third_party/ext4_rs/src/ext4_defs/direntry.rs index f69ec963..4715340e 100644 --- a/third_party/ext4_rs/src/ext4_defs/direntry.rs +++ b/third_party/ext4_rs/src/ext4_defs/direntry.rs @@ -211,12 +211,10 @@ impl Ext4DirEntry { /// Copy the directory entry to a slice. pub fn copy_to_slice(&self, array: &mut [u8], offset: usize) { - let de_ptr = self as *const Ext4DirEntry as *const u8; - let array_ptr = array as *mut [u8] as *mut u8; let count = core::mem::size_of::() / core::mem::size_of::(); - unsafe { - core::ptr::copy_nonoverlapping(de_ptr, array_ptr.add(offset), count); - } + assert!(offset <= array.len() && array.len() - offset >= count); + let data = unsafe { core::slice::from_raw_parts(self as *const _ as *const u8, count) }; + array[offset..offset + count].copy_from_slice(data); } } @@ -242,12 +240,10 @@ impl Ext4DirEntryTail { } pub fn copy_to_slice(&self, array: &mut [u8]) { - unsafe { - let offset = BLOCK_SIZE - core::mem::size_of::(); - let de_ptr = self as *const Ext4DirEntryTail as *const u8; - let array_ptr = array as *mut [u8] as *mut u8; - let count = core::mem::size_of::(); - core::ptr::copy_nonoverlapping(de_ptr, array_ptr.add(offset), count); - } + let offset = BLOCK_SIZE - core::mem::size_of::(); + let count = core::mem::size_of::(); + assert!(array.len() >= BLOCK_SIZE); + let data = unsafe { core::slice::from_raw_parts(self as *const _ as *const u8, count) }; + array[offset..offset + count].copy_from_slice(data); } } diff --git a/vendor/ext4_rs/src/ext4_defs/direntry.rs b/vendor/ext4_rs/src/ext4_defs/direntry.rs index 50ba0859..3bfdcda2 100644 --- a/vendor/ext4_rs/src/ext4_defs/direntry.rs +++ b/vendor/ext4_rs/src/ext4_defs/direntry.rs @@ -214,12 +214,10 @@ impl Ext4DirEntry { /// Copy the directory entry to a slice. pub fn copy_to_slice(&self, array: &mut [u8], offset: usize) { - let de_ptr = self as *const Ext4DirEntry as *const u8; - let array_ptr = array as *mut [u8] as *mut u8; let count = core::mem::size_of::() / core::mem::size_of::(); - unsafe { - core::ptr::copy_nonoverlapping(de_ptr, array_ptr.add(offset), count); - } + assert!(offset <= array.len() && array.len() - offset >= count); + let data = unsafe { core::slice::from_raw_parts(self as *const _ as *const u8, count) }; + array[offset..offset + count].copy_from_slice(data); } } @@ -245,12 +243,10 @@ impl Ext4DirEntryTail{ } pub fn copy_to_slice(&self, array: &mut [u8]) { - unsafe { let offset = BLOCK_SIZE - core::mem::size_of::(); - let de_ptr = self as *const Ext4DirEntryTail as *const u8; - let array_ptr = array as *mut [u8] as *mut u8; let count = core::mem::size_of::(); - core::ptr::copy_nonoverlapping(de_ptr, array_ptr.add(offset), count); - } + assert!(array.len() >= BLOCK_SIZE); + let data = unsafe { core::slice::from_raw_parts(self as *const _ as *const u8, count) }; + array[offset..offset + count].copy_from_slice(data); } }