From cacaa2aadee5aabcf09bcddc942f68ad70d7c94d Mon Sep 17 00:00:00 2001 From: Afloat <132809511+Afloat16@users.noreply.github.com> Date: Wed, 4 Nov 2026 01:12:03 +0700 Subject: [PATCH] Preserve sparse ext positions across indirect levels (from #705) --- crates/formats/src/fsread/extfs.rs | 2 + crates/formats/tests/ext_sparse_indirect.rs | 41 +++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 crates/formats/tests/ext_sparse_indirect.rs diff --git a/crates/formats/src/fsread/extfs.rs b/crates/formats/src/fsread/extfs.rs index 429b88e9..76fd5531 100644 --- a/crates/formats/src/fsread/extfs.rs +++ b/crates/formats/src/fsread/extfs.rs @@ -107,6 +107,8 @@ fn file_block_map(data: &[u8], sb: &Sb, inode_off: u64, flags: u32, nblocks: u64 if let Some(ind) = r.u32_le(ib + 48).filter(|&p| p != 0) { read_indirect(data, sb, u64::from(ind), 1, per, &mut logical, &mut map); } + // The single-indirect span still occupies its logical blocks when its root is absent. + logical = 12 + per; // Double indirect. if let Some(dind) = r.u32_le(ib + 52).filter(|&p| p != 0) { read_indirect(data, sb, u64::from(dind), 2, per, &mut logical, &mut map); diff --git a/crates/formats/tests/ext_sparse_indirect.rs b/crates/formats/tests/ext_sparse_indirect.rs new file mode 100644 index 00000000..81e0ab80 --- /dev/null +++ b/crates/formats/tests/ext_sparse_indirect.rs @@ -0,0 +1,41 @@ +use craft_anything_formats::fsread::{self, Limits}; + +const BS: usize = 1024; +const FILE_INODE: usize = 5120 + 10 * 128; + +fn put(data: &mut [u8], at: usize, value: u32) { + data[at..at + 4].copy_from_slice(&value.to_le_bytes()); +} + +#[test] +fn a_missing_single_indirect_root_keeps_the_whole_sparse_span() { + let mut data = craft_anything_testkit::firmware::ext_image(false); + data.resize(14 * BS, 0); + put(&mut data, 1024 + 4, 14); + data[FILE_INODE + 40..FILE_INODE + 100].fill(0); + put(&mut data, FILE_INODE + 4, (269 * BS) as u32); + put(&mut data, FILE_INODE + 92, 11); + put(&mut data, 11 * BS, 12); + put(&mut data, 12 * BS, 13); + data[13 * BS..13 * BS + 4].copy_from_slice(b"tail"); + let image = fsread::extfs::read(&data, &Limits::default()); + let recovered = image.entries.iter().find(|e| e.path == "hello.txt").unwrap().data.as_ref().unwrap(); + assert!(recovered[..268 * BS].iter().all(|b| *b == 0)); + assert_eq!(&recovered[268 * BS..268 * BS + 4], b"tail"); + assert!(image.complete, "{:?}", image.warnings); +} + +#[test] +fn an_existing_single_indirect_block_keeps_its_original_position() { + let mut data = craft_anything_testkit::firmware::ext_image(false); + data.resize(12 * BS, 0); + put(&mut data, 1024 + 4, 12); + data[FILE_INODE + 40..FILE_INODE + 100].fill(0); + put(&mut data, FILE_INODE + 4, (13 * BS) as u32); + put(&mut data, FILE_INODE + 88, 11); + put(&mut data, 11 * BS, 8); + let image = fsread::extfs::read(&data, &Limits::default()); + let recovered = image.entries.iter().find(|e| e.path == "hello.txt").unwrap().data.as_ref().unwrap(); + assert!(recovered[..12 * BS].iter().all(|b| *b == 0)); + assert_eq!(&recovered[12 * BS..12 * BS + 22], b"Hello ext filesystem!\n"); +}