Skip to main content

fs_ext4/
extent_mut.rs

1//! Extent tree mutation — planning layer (E7).
2//!
3//! Produces typed [`ExtentMutation`] values describing how the extent tree
4//! should change. Does NOT write to disk; E10 composes these into file write
5//! operations and E11 journals them.
6//!
7//! Scope for Phase 4 initial landing:
8//! - Operations on the inline root (leaf, depth 0). The inline root carries up
9//!   to 4 entries on a standard 60-byte i_block area, which covers the common
10//!   case of small files (≤ 4 contiguous extents).
11//! - `insert_extent`: place a new leaf extent in sorted position; returns an
12//!   error tagged `LEAF_FULL_NEEDS_PROMOTION` when the root is saturated and
13//!   the caller must promote to depth > 0 (future task).
14//! - `split_extent`: split one extent at a logical-block boundary (used when
15//!   partially overwriting, freeing a middle run, or converting uninit→init on
16//!   a sub-range).
17//! - `merge_adjacent`: fuse two neighbours whose `[log+len, phys+len)` line up.
18//! - `free_extent`: drop an entry and report the physical block range to
19//!   hand to the block bitmap freer (E5).
20//!
21//! Multi-level tree mutation (leaf-block split, internal-node rebalance) is
22//! deferred. Hitting it returns [`Error::Corrupt`] with a clear message so
23//! callers can fall back to copy-on-write or error out cleanly.
24
25use crate::error::{Error, Result};
26use crate::extent::{
27    Extent, ExtentHeader, ExtentIdx, EXT4_EXT_MAGIC, EXT4_EXT_MAX_DEPTH, EXT4_EXT_NODE_SIZE,
28    EXT_INIT_MAX_LEN,
29};
30
31// Byte offsets within a 12-byte extent tree node header.
32const EXT4_EXT_HDR_MAGIC_OFF: usize = 0;
33const EXT4_EXT_HDR_ENTRIES_OFF: usize = 2;
34const EXT4_EXT_HDR_MAX_OFF: usize = 4;
35const EXT4_EXT_HDR_DEPTH_OFF: usize = 6;
36const EXT4_EXT_HDR_GEN_OFF: usize = 8;
37
38/// Byte offset of extent entry `i` within a node body (entries follow the
39/// 12-byte header, so entry 0 is at offset `EXT4_EXT_NODE_SIZE`).
40#[inline]
41fn extent_entry_offset(i: usize) -> usize {
42    EXT4_EXT_NODE_SIZE * (1 + i)
43}
44
45/// Split a 64-bit physical block address into the hi u16 and lo u32 fields
46/// used by ext4 extent and index entries.
47#[inline]
48pub(crate) fn split_phys_block(block: u64) -> (u16, u32) {
49    let hi = ((block >> 32) & 0xFFFF) as u16;
50    let lo = (block & 0xFFFF_FFFF) as u32;
51    (hi, lo)
52}
53
54/// A primitive change the extent-write path wants to make to the tree. The
55/// caller (E10 or a test) converts this into real I/O under a transaction.
56#[derive(Debug, Clone, PartialEq, Eq)]
57pub enum ExtentMutation {
58    /// Overwrite the inline root (60 bytes of `i_block`) with these bytes.
59    WriteRoot { bytes: Vec<u8> },
60    /// Rewrite an existing external extent node. The filesystem patches its
61    /// checksum and journals this together with the inode and bitmap updates.
62    WriteTreeBlock { block: u64, bytes: Vec<u8> },
63    /// Allocate a fresh leaf block and write these bytes to it. Returned for
64    /// future multi-level operations; not produced by the current plan fns.
65    AllocLeafBlock { bytes: Vec<u8> },
66    /// Free a previously-used leaf/index block back to the bitmap.
67    FreeLeafBlock { block: u64 },
68    /// Free a contiguous physical-block run back to the bitmap (from a
69    /// dropped leaf extent). `start` is the first physical block, `len` is
70    /// the count.
71    FreePhysicalRun { start: u64, len: u32 },
72}
73
74/// Build a leaf extent entry's 12 bytes.
75fn encode_extent(e: &Extent) -> [u8; EXT4_EXT_NODE_SIZE] {
76    let mut buf = [0u8; EXT4_EXT_NODE_SIZE];
77    buf[0..4].copy_from_slice(&e.logical_block.to_le_bytes());
78    let ee_len: u16 = if e.uninitialized {
79        e.length + EXT_INIT_MAX_LEN
80    } else {
81        e.length
82    };
83    buf[4..6].copy_from_slice(&ee_len.to_le_bytes());
84    let (phys_hi, phys_lo) = split_phys_block(e.physical_block);
85    buf[6..8].copy_from_slice(&phys_hi.to_le_bytes());
86    buf[8..12].copy_from_slice(&phys_lo.to_le_bytes());
87    buf
88}
89
90/// Re-emit the inline root from a header + sorted extent list. `root_len` is
91/// the size of the i_block area (always 60 for inline root, larger for a
92/// full-block leaf).
93fn build_root(header: &ExtentHeader, extents: &[Extent], root_len: usize) -> Vec<u8> {
94    let mut out = vec![0u8; root_len];
95    out[EXT4_EXT_HDR_MAGIC_OFF..EXT4_EXT_HDR_MAGIC_OFF + 2]
96        .copy_from_slice(&EXT4_EXT_MAGIC.to_le_bytes());
97    out[EXT4_EXT_HDR_ENTRIES_OFF..EXT4_EXT_HDR_ENTRIES_OFF + 2]
98        .copy_from_slice(&(extents.len() as u16).to_le_bytes());
99    out[EXT4_EXT_HDR_MAX_OFF..EXT4_EXT_HDR_MAX_OFF + 2].copy_from_slice(&header.max.to_le_bytes());
100    out[EXT4_EXT_HDR_DEPTH_OFF..EXT4_EXT_HDR_DEPTH_OFF + 2]
101        .copy_from_slice(&header.depth.to_le_bytes());
102    out[EXT4_EXT_HDR_GEN_OFF..EXT4_EXT_HDR_GEN_OFF + 4]
103        .copy_from_slice(&header.generation.to_le_bytes());
104    for (i, e) in extents.iter().enumerate() {
105        let off = extent_entry_offset(i);
106        out[off..off + EXT4_EXT_NODE_SIZE].copy_from_slice(&encode_extent(e));
107    }
108    out
109}
110
111/// Read the leaf entries from an inline root. Errors on non-leaf (the root
112/// has descended into index entries — caller must handle the multi-level
113/// case separately).
114fn read_leaf_entries(root: &[u8]) -> Result<(ExtentHeader, Vec<Extent>)> {
115    let header = ExtentHeader::parse(root)?;
116    if !header.is_leaf() {
117        return Err(Error::CorruptExtentTree(
118            "extent_mut: multi-level tree mutation not yet supported",
119        ));
120    }
121    let mut out = Vec::with_capacity(header.entries as usize);
122    for i in 0..header.entries {
123        let off = extent_entry_offset(i as usize);
124        if off + EXT4_EXT_NODE_SIZE > root.len() {
125            return Err(Error::CorruptExtentTree("leaf entry out of range"));
126        }
127        out.push(Extent::parse(&root[off..off + EXT4_EXT_NODE_SIZE])?);
128    }
129    Ok((header, out))
130}
131
132/// Longest extent `ee_len` can describe in `uninitialized` state: 32768
133/// initialized blocks, 32767 uninitialized (an `ee_len` above 32768 is what
134/// marks an extent uninitialized, so 32768 itself is reserved for the
135/// initialized one). These are the two maxima the format documentation
136/// gives with `ee_len`, and a merge never crosses them.
137fn max_extent_len(uninitialized: bool) -> u32 {
138    if uninitialized {
139        u32::from(EXT_INIT_MAX_LEN) - 1
140    } else {
141        u32::from(EXT_INIT_MAX_LEN)
142    }
143}
144
145/// Returns true iff two adjacent leaf extents are physically contiguous, in
146/// the same uninit state, and short enough together to be one extent —
147/// candidate for a merge.
148///
149/// The length cap is not optional (#387): an initialized extent longer than
150/// 32768 blocks encodes as an uninitialized one of `len - 32768`, and two
151/// uninitialized ones past 32767 overflow `ee_len`.
152fn are_contiguous(a: &Extent, b: &Extent) -> bool {
153    if a.uninitialized != b.uninitialized {
154        return false;
155    }
156    if u32::from(a.length) + u32::from(b.length) > max_extent_len(a.uninitialized) {
157        return false;
158    }
159    let a_log_end = a.logical_block as u64 + a.length as u64;
160    let a_phys_end = a.physical_block + a.length as u64;
161    a_log_end == b.logical_block as u64 && a_phys_end == b.physical_block
162}
163
164/// Plan insertion of `new` into the inline-root leaf. Preserves sort order on
165/// `logical_block`, rejects overlaps, and auto-merges with the prev / next
166/// entry when contiguous.
167///
168/// Errors:
169/// - `LEAF_FULL_NEEDS_PROMOTION` if there is no room AND no merge was
170///   possible. Caller must promote to an internal-root tree (future task).
171/// - `extent overlaps existing` if `new`'s logical range overlaps an existing
172///   entry.
173pub fn plan_insert_extent(root: &[u8], new: Extent) -> Result<Vec<ExtentMutation>> {
174    let (header, mut entries) = read_leaf_entries(root)?;
175
176    // Reject overlaps. Assumes entries sorted by logical_block.
177    for e in &entries {
178        let e_end = e.logical_block as u64 + e.length as u64;
179        let n_end = new.logical_block as u64 + new.length as u64;
180        if !(n_end <= e.logical_block as u64 || new.logical_block as u64 >= e_end) {
181            return Err(Error::CorruptExtentTree("extent overlaps existing"));
182        }
183    }
184
185    // Find insert position (stable — first index whose logical_block > new).
186    let pos = entries
187        .iter()
188        .position(|e| e.logical_block as u64 > new.logical_block as u64)
189        .unwrap_or(entries.len());
190    entries.insert(pos, new);
191
192    // Auto-merge with left neighbour if contiguous.
193    if pos > 0 && are_contiguous(&entries[pos - 1], &entries[pos]) {
194        let right = entries.remove(pos);
195        entries[pos - 1].length += right.length;
196    }
197    // Auto-merge with right neighbour (pos may have shifted — recompute).
198    let pos = entries
199        .iter()
200        .position(|e| {
201            e.logical_block == new.logical_block
202                || (e.logical_block as u64) < new.logical_block as u64
203                    && (e.logical_block as u64 + e.length as u64) > new.logical_block as u64
204        })
205        .unwrap_or(entries.len().saturating_sub(1));
206    if pos + 1 < entries.len() && are_contiguous(&entries[pos], &entries[pos + 1]) {
207        let right = entries.remove(pos + 1);
208        entries[pos].length += right.length;
209    }
210
211    if entries.len() as u16 > header.max {
212        return Err(Error::CorruptExtentTree(
213            "LEAF_FULL_NEEDS_PROMOTION: root has no slot for new extent",
214        ));
215    }
216
217    let new_root = build_root(&header, &entries, root.len());
218    Ok(vec![ExtentMutation::WriteRoot { bytes: new_root }])
219}
220
221/// Layout for a promote-leaf-to-depth-1 operation: the new leaf block that
222/// holds the five entries (four old + the new one), and the rewritten inline
223/// root that indexes it.
224#[derive(Debug, Clone, PartialEq, Eq)]
225pub struct PromotionPlan {
226    /// Full-block bytes for the freshly-allocated leaf node. Header at the
227    /// start, sorted leaf entries, zeros to end. If `reserved_tail_csum` was
228    /// true the last 4 bytes are reserved for the caller to patch with
229    /// `checksum.patch_extent_tail`.
230    pub leaf_bytes: Vec<u8>,
231    /// 60 bytes of the new inline root: depth=1, entries=1, single index
232    /// entry whose logical_block = 0 and leaf_block = the physical block the
233    /// caller allocated for `leaf_bytes`.
234    pub new_root_bytes: Vec<u8>,
235}
236
237/// Build a depth-0 leaf block (full `block_size` bytes). Reserves 4 tail
238/// bytes for the ext4_extent_tail CRC when `reserved_tail_csum` is true.
239fn build_leaf_block(
240    generation: u32,
241    extents: &[Extent],
242    block_size: usize,
243    reserved_tail_csum: bool,
244) -> Vec<u8> {
245    let mut out = vec![0u8; block_size];
246    let header_body_len = block_size.saturating_sub(if reserved_tail_csum { 4 } else { 0 });
247    let max_entries = ((header_body_len - EXT4_EXT_NODE_SIZE) / EXT4_EXT_NODE_SIZE) as u16;
248    out[EXT4_EXT_HDR_MAGIC_OFF..EXT4_EXT_HDR_MAGIC_OFF + 2]
249        .copy_from_slice(&EXT4_EXT_MAGIC.to_le_bytes());
250    out[EXT4_EXT_HDR_ENTRIES_OFF..EXT4_EXT_HDR_ENTRIES_OFF + 2]
251        .copy_from_slice(&(extents.len() as u16).to_le_bytes());
252    out[EXT4_EXT_HDR_MAX_OFF..EXT4_EXT_HDR_MAX_OFF + 2].copy_from_slice(&max_entries.to_le_bytes());
253    out[EXT4_EXT_HDR_DEPTH_OFF..EXT4_EXT_HDR_DEPTH_OFF + 2].copy_from_slice(&0u16.to_le_bytes());
254    out[EXT4_EXT_HDR_GEN_OFF..EXT4_EXT_HDR_GEN_OFF + 4].copy_from_slice(&generation.to_le_bytes());
255    for (i, e) in extents.iter().enumerate() {
256        let off = extent_entry_offset(i);
257        out[off..off + EXT4_EXT_NODE_SIZE].copy_from_slice(&encode_extent(e));
258    }
259    out
260}
261
262/// Build a 60-byte depth-1 inline root containing a single index entry
263/// pointing at `leaf_phys` (logical 0).
264fn build_depth1_index_root(generation: u32, leaf_phys: u64) -> Vec<u8> {
265    let mut out = vec![0u8; 60];
266    out[EXT4_EXT_HDR_MAGIC_OFF..EXT4_EXT_HDR_MAGIC_OFF + 2]
267        .copy_from_slice(&EXT4_EXT_MAGIC.to_le_bytes());
268    out[EXT4_EXT_HDR_ENTRIES_OFF..EXT4_EXT_HDR_ENTRIES_OFF + 2]
269        .copy_from_slice(&1u16.to_le_bytes()); // entries=1
270    out[EXT4_EXT_HDR_MAX_OFF..EXT4_EXT_HDR_MAX_OFF + 2].copy_from_slice(&4u16.to_le_bytes()); // max=4 (inline root)
271    out[EXT4_EXT_HDR_DEPTH_OFF..EXT4_EXT_HDR_DEPTH_OFF + 2].copy_from_slice(&1u16.to_le_bytes()); // depth=1
272    out[EXT4_EXT_HDR_GEN_OFF..EXT4_EXT_HDR_GEN_OFF + 4].copy_from_slice(&generation.to_le_bytes());
273    let ei_block: u32 = 0;
274    let (ei_leaf_hi, ei_leaf_lo) = split_phys_block(leaf_phys);
275    out[12..16].copy_from_slice(&ei_block.to_le_bytes());
276    out[16..20].copy_from_slice(&ei_leaf_lo.to_le_bytes());
277    out[20..22].copy_from_slice(&ei_leaf_hi.to_le_bytes());
278    out
279}
280
281/// Plan a depth-0 → depth-1 promotion. The caller must have already seen
282/// `LEAF_FULL_NEEDS_PROMOTION` from `plan_insert_extent` and allocated one
283/// fresh filesystem block to hold the new leaf.
284///
285/// Produces:
286///   - `leaf_bytes`: the entire new leaf block (`block_size` bytes) containing
287///     all existing extents + `new`, sorted and merge-compatible with the
288///     existing layout. Last 4 bytes are reserved for the caller to patch
289///     with `checksum.patch_extent_tail` when metadata_csum is on.
290///   - `new_root_bytes`: 60 bytes, depth=1, one index entry → `new_leaf_phys`.
291///
292/// Errors if `root` is not a leaf (depth != 0) — the multi-level split case
293/// is a further task. Also rejects overlaps with existing entries.
294pub fn plan_promote_leaf(
295    root: &[u8],
296    new: Extent,
297    block_size: usize,
298    new_leaf_phys: u64,
299    reserved_tail_csum: bool,
300) -> Result<PromotionPlan> {
301    let (header, mut entries) = read_leaf_entries(root)?;
302
303    for e in &entries {
304        let e_end = e.logical_block as u64 + e.length as u64;
305        let n_end = new.logical_block as u64 + new.length as u64;
306        if !(n_end <= e.logical_block as u64 || new.logical_block as u64 >= e_end) {
307            return Err(Error::CorruptExtentTree("extent overlaps existing"));
308        }
309    }
310
311    let pos = entries
312        .iter()
313        .position(|e| e.logical_block as u64 > new.logical_block as u64)
314        .unwrap_or(entries.len());
315    entries.insert(pos, new);
316
317    if pos > 0 && are_contiguous(&entries[pos - 1], &entries[pos]) {
318        let right = entries.remove(pos);
319        entries[pos - 1].length += right.length;
320    }
321    let merged_pos = entries
322        .iter()
323        .position(|e| {
324            e.logical_block == new.logical_block
325                || ((e.logical_block as u64) < new.logical_block as u64
326                    && (e.logical_block as u64 + e.length as u64) > new.logical_block as u64)
327        })
328        .unwrap_or(entries.len().saturating_sub(1));
329    if merged_pos + 1 < entries.len()
330        && are_contiguous(&entries[merged_pos], &entries[merged_pos + 1])
331    {
332        let right = entries.remove(merged_pos + 1);
333        entries[merged_pos].length += right.length;
334    }
335
336    let leaf_capacity_bytes = block_size.saturating_sub(if reserved_tail_csum { 4 } else { 0 });
337    let leaf_max = ((leaf_capacity_bytes - EXT4_EXT_NODE_SIZE) / EXT4_EXT_NODE_SIZE) as u16;
338    if entries.len() as u16 > leaf_max {
339        return Err(Error::CorruptExtentTree(
340            "plan_promote_leaf: entry count exceeds leaf capacity",
341        ));
342    }
343
344    let leaf_bytes = build_leaf_block(header.generation, &entries, block_size, reserved_tail_csum);
345    let new_root_bytes = build_depth1_index_root(header.generation, new_leaf_phys);
346    Ok(PromotionPlan {
347        leaf_bytes,
348        new_root_bytes,
349    })
350}
351
352// ----------------------------------------------------------------------------
353// Depth ≥ 2 mutation: descend, insert at leaf, propagate splits, promote root.
354// ----------------------------------------------------------------------------
355
356/// Read interface used by [`plan_insert_extent_deep`] to fetch any internal
357/// or leaf block referenced by an index entry on the way down. Callers wire
358/// this through a `BlockDevice` (or a buffered cache when running under a
359/// transaction).
360pub trait DeepReader {
361    /// Read exactly `out.len()` bytes of the block numbered `block`. The
362    /// caller picks the buffer size (always `block_size`).
363    fn read_block(&self, block: u64, out: &mut [u8]) -> Result<()>;
364}
365
366/// Result of a deep insert. The caller stages each component into its
367/// transaction (inode-i_block patch + per-block writes + bitmap/BGD updates
368/// for the freshly-allocated blocks).
369#[derive(Debug, Clone, PartialEq, Eq)]
370pub struct DeepInsertPlan {
371    /// New 60 bytes of `i_block`. Depth may have changed (root promotion).
372    pub new_root: Vec<u8>,
373    /// Block writes, one per modified-or-new block. Each entry is
374    /// `(block_num, full block_size bytes)`. Non-root blocks have their last
375    /// 4 bytes zeroed and reserved — the caller patches `et_checksum` via
376    /// [`crate::checksum::Checksummer::patch_extent_tail`].
377    pub block_writes: Vec<(u64, Vec<u8>)>,
378    /// Block numbers freshly pulled from the allocator closure, in the order
379    /// they were drawn. The caller marks each used in the bitmap and bumps
380    /// BGD/SB counters in the same transaction.
381    pub allocated_blocks: Vec<u64>,
382}
383
384/// One frame on the descent path: the block we read and its raw bytes.
385///
386/// The root frame uses `block = 0` as a placeholder — root is recognised
387/// by its position (the first frame) rather than by the field.
388///
389/// It also carried `idx_in_parent`, the position of the index entry in
390/// the parent that selected this child. Every construction site set it
391/// and nothing ever read it; the crate-wide `allow(dead_code)` is what
392/// kept that invisible. If a rebalance ever needs to rewrite a parent's
393/// entry in place, it comes back — as a field something reads.
394struct PathFrame {
395    /// Block number of this node. `0` if this is the inline root.
396    block: u64,
397    /// Raw bytes (60 for root, `block_size` for everything below).
398    bytes: Vec<u8>,
399}
400
401/// Encode a 12-byte index entry pointing at `child_phys` for the subtree
402/// whose smallest logical block is `logical_block`.
403fn encode_index(logical_block: u32, child_phys: u64) -> [u8; EXT4_EXT_NODE_SIZE] {
404    let mut buf = [0u8; EXT4_EXT_NODE_SIZE];
405    let (leaf_hi, leaf_lo) = split_phys_block(child_phys);
406    buf[0..4].copy_from_slice(&logical_block.to_le_bytes());
407    buf[4..8].copy_from_slice(&leaf_lo.to_le_bytes());
408    buf[8..10].copy_from_slice(&leaf_hi.to_le_bytes());
409    // 0x0A..0x0C is ei_unused — already zero.
410    buf
411}
412
413/// Read every leaf entry from a node body, regardless of whether it's inline
414/// or a full block. Caller has already verified the header is a leaf.
415fn parse_leaf_entries(node: &[u8], header: &ExtentHeader) -> Result<Vec<Extent>> {
416    let mut out = Vec::with_capacity(header.entries as usize);
417    for i in 0..header.entries {
418        let off = extent_entry_offset(i as usize);
419        if off + EXT4_EXT_NODE_SIZE > node.len() {
420            return Err(Error::CorruptExtentTree("leaf entry out of range"));
421        }
422        out.push(Extent::parse(&node[off..off + EXT4_EXT_NODE_SIZE])?);
423    }
424    Ok(out)
425}
426
427/// Read every index entry from an internal node body. Caller has already
428/// verified the header is non-leaf.
429fn parse_index_entries(node: &[u8], header: &ExtentHeader) -> Result<Vec<ExtentIdx>> {
430    let mut out = Vec::with_capacity(header.entries as usize);
431    for i in 0..header.entries {
432        let off = extent_entry_offset(i as usize);
433        if off + EXT4_EXT_NODE_SIZE > node.len() {
434            return Err(Error::CorruptExtentTree("index entry out of range"));
435        }
436        out.push(ExtentIdx::parse(&node[off..off + EXT4_EXT_NODE_SIZE])?);
437    }
438    Ok(out)
439}
440
441/// Capacity (in entries) of a non-root leaf or index block. The trailing
442/// 4-byte tail is always reserved — every consumer that uses this code path
443/// is on a metadata_csum volume in practice, and reserving the slot when it
444/// isn't strictly needed costs nothing on layout.
445fn node_max_entries(block_size: usize) -> u16 {
446    ((block_size - EXT4_EXT_NODE_SIZE - 4) / EXT4_EXT_NODE_SIZE) as u16
447}
448
449/// Build a leaf block (full `block_size` bytes) from the given entries. Last
450/// 4 bytes are zeroed and reserved for the caller to patch with the
451/// extent_tail CRC.
452fn build_full_leaf_block(generation: u32, extents: &[Extent], block_size: usize) -> Vec<u8> {
453    let mut out = vec![0u8; block_size];
454    let max = node_max_entries(block_size);
455    out[EXT4_EXT_HDR_MAGIC_OFF..EXT4_EXT_HDR_MAGIC_OFF + 2]
456        .copy_from_slice(&EXT4_EXT_MAGIC.to_le_bytes());
457    out[EXT4_EXT_HDR_ENTRIES_OFF..EXT4_EXT_HDR_ENTRIES_OFF + 2]
458        .copy_from_slice(&(extents.len() as u16).to_le_bytes());
459    out[EXT4_EXT_HDR_MAX_OFF..EXT4_EXT_HDR_MAX_OFF + 2].copy_from_slice(&max.to_le_bytes());
460    out[EXT4_EXT_HDR_DEPTH_OFF..EXT4_EXT_HDR_DEPTH_OFF + 2].copy_from_slice(&0u16.to_le_bytes());
461    out[EXT4_EXT_HDR_GEN_OFF..EXT4_EXT_HDR_GEN_OFF + 4].copy_from_slice(&generation.to_le_bytes());
462    for (i, e) in extents.iter().enumerate() {
463        let off = extent_entry_offset(i);
464        out[off..off + EXT4_EXT_NODE_SIZE].copy_from_slice(&encode_extent(e));
465    }
466    out
467}
468
469/// Build a non-root index block (full `block_size` bytes) at the given
470/// depth. Last 4 bytes are zeroed and reserved for the extent_tail CRC.
471fn build_full_index_block(
472    generation: u32,
473    depth: u16,
474    indices: &[(u32, u64)],
475    block_size: usize,
476) -> Vec<u8> {
477    let mut out = vec![0u8; block_size];
478    let max = node_max_entries(block_size);
479    out[EXT4_EXT_HDR_MAGIC_OFF..EXT4_EXT_HDR_MAGIC_OFF + 2]
480        .copy_from_slice(&EXT4_EXT_MAGIC.to_le_bytes());
481    out[EXT4_EXT_HDR_ENTRIES_OFF..EXT4_EXT_HDR_ENTRIES_OFF + 2]
482        .copy_from_slice(&(indices.len() as u16).to_le_bytes());
483    out[EXT4_EXT_HDR_MAX_OFF..EXT4_EXT_HDR_MAX_OFF + 2].copy_from_slice(&max.to_le_bytes());
484    out[EXT4_EXT_HDR_DEPTH_OFF..EXT4_EXT_HDR_DEPTH_OFF + 2].copy_from_slice(&depth.to_le_bytes());
485    out[EXT4_EXT_HDR_GEN_OFF..EXT4_EXT_HDR_GEN_OFF + 4].copy_from_slice(&generation.to_le_bytes());
486    for (i, (logical, child)) in indices.iter().enumerate() {
487        let off = extent_entry_offset(i);
488        out[off..off + EXT4_EXT_NODE_SIZE].copy_from_slice(&encode_index(*logical, *child));
489    }
490    out
491}
492
493/// Build the 60-byte inline root for an index node at the given depth.
494fn build_inline_index_root(generation: u32, depth: u16, indices: &[(u32, u64)]) -> Vec<u8> {
495    let mut out = vec![0u8; 60];
496    out[EXT4_EXT_HDR_MAGIC_OFF..EXT4_EXT_HDR_MAGIC_OFF + 2]
497        .copy_from_slice(&EXT4_EXT_MAGIC.to_le_bytes());
498    out[EXT4_EXT_HDR_ENTRIES_OFF..EXT4_EXT_HDR_ENTRIES_OFF + 2]
499        .copy_from_slice(&(indices.len() as u16).to_le_bytes());
500    out[EXT4_EXT_HDR_MAX_OFF..EXT4_EXT_HDR_MAX_OFF + 2].copy_from_slice(&4u16.to_le_bytes()); // max=4 (inline root)
501    out[EXT4_EXT_HDR_DEPTH_OFF..EXT4_EXT_HDR_DEPTH_OFF + 2].copy_from_slice(&depth.to_le_bytes());
502    out[EXT4_EXT_HDR_GEN_OFF..EXT4_EXT_HDR_GEN_OFF + 4].copy_from_slice(&generation.to_le_bytes());
503    for (i, (logical, child)) in indices.iter().enumerate() {
504        let off = extent_entry_offset(i);
505        out[off..off + EXT4_EXT_NODE_SIZE].copy_from_slice(&encode_index(*logical, *child));
506    }
507    out
508}
509
510/// Build the 60-byte inline root for a leaf node (depth 0).
511fn build_inline_leaf_root(generation: u32, extents: &[Extent]) -> Vec<u8> {
512    let header = ExtentHeader {
513        magic: EXT4_EXT_MAGIC,
514        entries: extents.len() as u16,
515        max: 4,
516        depth: 0,
517        generation,
518    };
519    build_root(&header, extents, 60)
520}
521
522/// An extent tree laid out again over blocks the file already owns.
523pub struct RepackedTree {
524    /// The 60 bytes for the inode's `i_block`.
525    pub new_root: Vec<u8>,
526    /// Blocks to write, and what to write into them. The tail checksum is
527    /// the caller's, as everywhere else here.
528    pub block_writes: Vec<(u64, Vec<u8>)>,
529    /// The blocks from `nodes` that the layout used, in the order it used
530    /// them. Everything else in `nodes` is now unreferenced.
531    pub used_nodes: Vec<u64>,
532    /// Blocks `alloc` handed out, when the file's own were not enough. The
533    /// caller accounts for them as it does for any allocation.
534    pub allocated_blocks: Vec<u64>,
535}
536
537/// Lay `entries` out again as a tree over `nodes`, packing every node full.
538///
539/// This is what a punch needs. The survivors of a punch are a subset of the
540/// entries the tree already held, so packing them takes no more nodes than
541/// the tree already has, and the blocks it does not use go back to free
542/// space. Nothing is allocated: a punch frees, and an allocation inside it
543/// would be a second thing that can fail.
544///
545/// Four or fewer entries need no nodes at all — they go in the inode, which
546/// is the case the punch path handled before (#258).
547///
548/// `alloc` covers the case where they are not enough. A punch inside one
549/// extent leaves a head and a tail where there was one record, so the
550/// survivors can outnumber the entries by one — and since this packs every
551/// node full, a tree it laid out before has no spare room for that one. It is
552/// called only then, and only for as many blocks as the layout is short.
553///
554/// # Errors
555///
556/// Whatever `alloc` returns when the layout needs a block the file does not
557/// already hold.
558pub fn plan_repack_tree(
559    generation: u32,
560    entries: &[Extent],
561    block_size: u32,
562    nodes: &[u64],
563    alloc: &mut dyn FnMut() -> Result<u64>,
564) -> Result<RepackedTree> {
565    let bs = block_size as usize;
566    let mut used_nodes: Vec<u64> = Vec::new();
567    let mut allocated_blocks: Vec<u64> = Vec::new();
568    let mut block_writes: Vec<(u64, Vec<u8>)> = Vec::new();
569
570    if entries.len() <= 4 {
571        return Ok(RepackedTree {
572            new_root: build_inline_leaf_root(generation, entries),
573            block_writes,
574            used_nodes,
575            allocated_blocks,
576        });
577    }
578
579    let mut supply = nodes.iter().copied();
580    let mut take = |used: &mut Vec<u64>, fresh: &mut Vec<u64>| -> Result<u64> {
581        match supply.next() {
582            Some(block) => {
583                used.push(block);
584                Ok(block)
585            }
586            None => {
587                let block = alloc()?;
588                fresh.push(block);
589                Ok(block)
590            }
591        }
592    };
593
594    let cap = node_max_entries(bs) as usize;
595
596    // The leaves, each packed full, and the index entries naming them.
597    let mut indices: Vec<(u32, u64)> = Vec::new();
598    for chunk in entries.chunks(cap) {
599        let block = take(&mut used_nodes, &mut allocated_blocks)?;
600        block_writes.push((block, build_full_leaf_block(generation, chunk, bs)));
601        indices.push((chunk[0].logical_block, block));
602    }
603
604    // Index levels, until what is left fits the inode's four entries.
605    let mut depth = 1u16;
606    while indices.len() > 4 {
607        let mut above: Vec<(u32, u64)> = Vec::new();
608        for chunk in indices.chunks(cap) {
609            let block = take(&mut used_nodes, &mut allocated_blocks)?;
610            block_writes.push((block, build_full_index_block(generation, depth, chunk, bs)));
611            above.push((chunk[0].0, block));
612        }
613        indices = above;
614        depth += 1;
615    }
616
617    Ok(RepackedTree {
618        new_root: build_inline_index_root(generation, depth, &indices),
619        block_writes,
620        used_nodes,
621        allocated_blocks,
622    })
623}
624
625/// Reject any insert whose logical range overlaps an existing leaf entry.
626fn check_no_overlap(entries: &[Extent], new: &Extent) -> Result<()> {
627    let n_start = new.logical_block as u64;
628    let n_end = n_start + new.length as u64;
629    for e in entries {
630        let e_start = e.logical_block as u64;
631        let e_end = e_start + e.length as u64;
632        if !(n_end <= e_start || n_start >= e_end) {
633            return Err(Error::CorruptExtentTree("extent overlaps existing"));
634        }
635    }
636    Ok(())
637}
638
639/// Insert `new` into `entries` (sorted by `logical_block`) and auto-merge
640/// with neighbours when contiguous. Mirrors `plan_insert_extent`'s logic but
641/// works on a generic vector rather than the inline root.
642fn insert_into_leaf_sorted(entries: &mut Vec<Extent>, new: Extent) {
643    let pos = entries
644        .iter()
645        .position(|e| e.logical_block as u64 > new.logical_block as u64)
646        .unwrap_or(entries.len());
647    entries.insert(pos, new);
648    if pos > 0 && are_contiguous(&entries[pos - 1], &entries[pos]) {
649        let right = entries.remove(pos);
650        entries[pos - 1].length += right.length;
651    }
652    // Recompute the merged-into position to consider a right merge.
653    let merged_pos = entries
654        .iter()
655        .position(|e| {
656            e.logical_block == new.logical_block
657                || ((e.logical_block as u64) < new.logical_block as u64
658                    && (e.logical_block as u64 + e.length as u64) > new.logical_block as u64)
659        })
660        .unwrap_or(entries.len().saturating_sub(1));
661    if merged_pos + 1 < entries.len()
662        && are_contiguous(&entries[merged_pos], &entries[merged_pos + 1])
663    {
664        let right = entries.remove(merged_pos + 1);
665        entries[merged_pos].length += right.length;
666    }
667}
668
669/// Insert a new index entry in sorted-by-logical_block order.
670fn insert_into_index_sorted(entries: &mut Vec<(u32, u64)>, new_logical: u32, new_child: u64) {
671    let pos = entries
672        .iter()
673        .position(|(l, _)| *l > new_logical)
674        .unwrap_or(entries.len());
675    entries.insert(pos, (new_logical, new_child));
676}
677
678/// State carried up the tree during split propagation. When `Some`, the
679/// parent must absorb a new index entry pointing at the freshly-split-off
680/// sibling.
681struct PropagatedSplit {
682    /// First logical block in the new sibling subtree.
683    new_logical: u32,
684    /// Block number of the new sibling node.
685    new_child: u64,
686}
687
688/// Walk the tree from the root to the leaf that should hold `target_logical`.
689/// Returns the descent path with one frame per level (root first, leaf last).
690fn descend_to_leaf(
691    root_bytes: &[u8],
692    target_logical: u32,
693    block_size: u32,
694    reader: &dyn DeepReader,
695) -> Result<Vec<PathFrame>> {
696    let root_header = ExtentHeader::parse(root_bytes)?;
697    if root_header.depth > EXT4_EXT_MAX_DEPTH {
698        return Err(Error::CorruptExtentTree(
699            "extent tree depth exceeds spec maximum",
700        ));
701    }
702    let mut path = Vec::with_capacity(root_header.depth as usize + 1);
703    path.push(PathFrame {
704        block: 0,
705        bytes: root_bytes.to_vec(),
706    });
707
708    loop {
709        let frame = path.last().unwrap();
710        let header = ExtentHeader::parse(&frame.bytes)?;
711        if header.is_leaf() {
712            return Ok(path);
713        }
714        if header.entries == 0 {
715            return Err(Error::CorruptExtentTree(
716                "internal node with zero index entries",
717            ));
718        }
719        // Find largest index entry whose ei_block <= target_logical (mirrors
720        // the read-side descent in extent::lookup_verified).
721        //
722        // `ExtentHeader::parse` bounds `entries` by `max` but nothing bounds
723        // `max` by the node, so every entry is range-checked here: a root
724        // claiming five index entries in its four-entry area, or a child
725        // block claiming more than the block holds, is corrupt (#327).
726        let entries = parse_index_entries(&frame.bytes, &header)?;
727        let chosen_idx = entries
728            .iter()
729            .take_while(|idx| idx.logical_block <= target_logical)
730            .last();
731        // If target is below the smallest index entry, descend into the
732        // first child (the new extent will sort into the leftmost subtree).
733        let idx = *chosen_idx.unwrap_or(&entries[0]);
734        let mut child_buf = vec![0u8; block_size as usize];
735        reader.read_block(idx.leaf_block, &mut child_buf)?;
736        path.push(PathFrame {
737            block: idx.leaf_block,
738            bytes: child_buf,
739        });
740    }
741}
742
743/// Insert `new` into the extent tree rooted at `root_bytes` (60 bytes from
744/// the inode's `i_block`), descending through internal nodes as needed,
745/// splitting full nodes, and promoting the root if the inline area can no
746/// longer hold the post-split index entries.
747///
748/// `block_size` must match the volume's filesystem block size.
749///
750/// `reader` provides the bytes of any internal/leaf block we walk through on
751/// the way down. `alloc` is called once per freshly-needed block; the caller
752/// is responsible for marking each result used in the bitmap (and bumping
753/// BGD/SB counters) inside the same transaction as the inode write.
754///
755/// Caps the post-insert tree depth at [`EXT4_EXT_MAX_DEPTH`]. Beyond that
756/// returns [`Error::CorruptExtentTree`].
757pub fn plan_insert_extent_deep(
758    root_bytes: &[u8],
759    new: Extent,
760    block_size: u32,
761    reader: &dyn DeepReader,
762    alloc: &mut dyn FnMut() -> Result<u64>,
763) -> Result<DeepInsertPlan> {
764    if root_bytes.len() < 12 {
765        return Err(Error::CorruptExtentTree("root buffer too small"));
766    }
767    let bs = block_size as usize;
768    let root_header = ExtentHeader::parse(root_bytes)?;
769    let generation = root_header.generation;
770
771    // Descend to the leaf that should hold `new`.
772    let mut path = descend_to_leaf(root_bytes, new.logical_block, block_size, reader)?;
773
774    let mut block_writes: Vec<(u64, Vec<u8>)> = Vec::new();
775    let mut allocated_blocks: Vec<u64> = Vec::new();
776
777    // ---- Leaf level ----
778    let leaf_frame = path.pop().unwrap();
779    let leaf_header = ExtentHeader::parse(&leaf_frame.bytes)?;
780    debug_assert!(leaf_header.is_leaf());
781    let mut leaf_entries = parse_leaf_entries(&leaf_frame.bytes, &leaf_header)?;
782    check_no_overlap(&leaf_entries, &new)?;
783    // THE KEY A PARENT HOLDS FOR THIS LEAF, which is the leaf's first
784    // logical block. An insert in front of everything moves it, and a parent
785    // still naming the old one describes a tree e2fsck rejects:
786    // "Logical start N does not match logical start M at next level" (#260).
787    // Each index key must equal the first logical block of the subtree it
788    // points at, so every ancestor's key is corrected here.
789    let first_key_before = leaf_entries.first().map(|e| e.logical_block);
790    insert_into_leaf_sorted(&mut leaf_entries, new);
791
792    // Capacity for this leaf depends on whether it's the inline root or a
793    // full block. The inline root holds up to 4 entries (no tail); a full
794    // block holds up to `node_max_entries(bs)` entries (with tail reserved).
795    let is_root_leaf = path.is_empty();
796    let leaf_cap = if is_root_leaf {
797        4
798    } else {
799        node_max_entries(bs)
800    };
801
802    // `propagated` carries the (logical, block) tuple of the split-off
803    // sibling that the next-higher level must absorb as a new index entry.
804    // Initially set by the leaf-split branch below; replaced by the in-loop
805    // split branch each time a non-root index node also overflows.
806    let mut propagated: Option<PropagatedSplit>;
807    // `(child block, its new first key)`, while a parent still names the old
808    // one.
809    let mut correction: Option<(u64, u32)> = None;
810
811    if (leaf_entries.len() as u16) <= leaf_cap {
812        // Leaf fits — emit it.
813        if is_root_leaf {
814            // Root + leaf: rewrite the inline root and we're done.
815            let new_root = build_inline_leaf_root(generation, &leaf_entries);
816            return Ok(DeepInsertPlan {
817                new_root,
818                block_writes,
819                allocated_blocks,
820            });
821        }
822        let new_leaf = build_full_leaf_block(generation, &leaf_entries, bs);
823        block_writes.push((leaf_frame.block, new_leaf));
824        let first_key = leaf_entries[0].logical_block;
825        if first_key_before == Some(first_key) {
826            // Every level above is unchanged, so the inline root bytes go
827            // back verbatim.
828            return Ok(DeepInsertPlan {
829                new_root: root_bytes.to_vec(),
830                block_writes,
831                allocated_blocks,
832            });
833        }
834        // The leaf's first key moved, so the keys naming it have to follow.
835        propagated = None;
836        correction = Some((leaf_frame.block, first_key));
837    } else {
838        // Leaf overflowed — split it 50/50.
839        if is_root_leaf {
840            // Special case: inline root is a leaf and overflowed. Promote to
841            // depth 1 by allocating a single block and dumping all entries
842            // there, then rewriting the root with a single index entry.
843            // (This mirrors `plan_promote_leaf`, inlined so we control the
844            // alloc + write emission.)
845            let new_leaf_block = alloc()?;
846            allocated_blocks.push(new_leaf_block);
847            let new_leaf_bytes = build_full_leaf_block(generation, &leaf_entries, bs);
848            block_writes.push((new_leaf_block, new_leaf_bytes));
849            let new_root = build_inline_index_root(
850                generation,
851                1,
852                &[(leaf_entries[0].logical_block, new_leaf_block)],
853            );
854            return Ok(DeepInsertPlan {
855                new_root,
856                block_writes,
857                allocated_blocks,
858            });
859        }
860        // Non-root leaf split.
861        let mid = leaf_entries.len() / 2;
862        let right_entries: Vec<Extent> = leaf_entries.split_off(mid);
863        let left_entries = leaf_entries;
864        let right_first_logical = right_entries[0].logical_block;
865        let new_right_block = alloc()?;
866        allocated_blocks.push(new_right_block);
867
868        let left_bytes = build_full_leaf_block(generation, &left_entries, bs);
869        let right_bytes = build_full_leaf_block(generation, &right_entries, bs);
870        block_writes.push((leaf_frame.block, left_bytes));
871        block_writes.push((new_right_block, right_bytes));
872
873        propagated = Some(PropagatedSplit {
874            new_logical: right_first_logical,
875            new_child: new_right_block,
876        });
877        let left_first = left_entries[0].logical_block;
878        if first_key_before != Some(left_first) {
879            correction = Some((leaf_frame.block, left_first));
880        }
881    }
882
883    // ---- Walk up internal levels, absorbing the propagated split. ----
884    // Invariant: at the top of every loop iteration, `propagated` is Some —
885    // a level absorbs without overflow (sets propagated=None) only on a
886    // path that returns immediately, since every higher level is unchanged.
887    while let Some(frame) = path.pop() {
888        let header = ExtentHeader::parse(&frame.bytes)?;
889        debug_assert!(!header.is_leaf());
890        let mut indices: Vec<(u32, u64)> = parse_index_entries(&frame.bytes, &header)?
891            .into_iter()
892            .map(|i| (i.logical_block, i.leaf_block))
893            .collect();
894
895        // A child whose first key moved is named here; correct it, and pass
896        // the correction on only while it is this node's own first key,
897        // which is what the node above holds for it.
898        if let Some((child, key)) = correction.take() {
899            let at = indices
900                .iter()
901                .position(|(_, block)| *block == child)
902                .ok_or(Error::CorruptExtentTree(
903                    "the index entry naming a child this insert changed is missing",
904                ))?;
905            indices[at].0 = key;
906            if at == 0 && frame.block != 0 {
907                correction = Some((frame.block, key));
908            }
909        }
910
911        if let Some(split) = propagated.take() {
912            insert_into_index_sorted(&mut indices, split.new_logical, split.new_child);
913        }
914
915        let is_root_here = path.is_empty();
916        let cap = if is_root_here {
917            4
918        } else {
919            node_max_entries(bs)
920        };
921
922        if (indices.len() as u16) <= cap {
923            if is_root_here {
924                let new_root = build_inline_index_root(generation, header.depth, &indices);
925                return Ok(DeepInsertPlan {
926                    new_root,
927                    block_writes,
928                    allocated_blocks,
929                });
930            }
931            let bytes = build_full_index_block(generation, header.depth, &indices, bs);
932            block_writes.push((frame.block, bytes));
933            if correction.is_some() {
934                // This node is written, but the key naming it above moved
935                // too, so the level above still has to be rewritten.
936                continue;
937            }
938            // No more propagation — every level above this is unchanged, so
939            // the inline root bytes can be returned verbatim.
940            return Ok(DeepInsertPlan {
941                new_root: root_bytes.to_vec(),
942                block_writes,
943                allocated_blocks,
944            });
945        } else {
946            // Index overflow.
947            if is_root_here {
948                // Root promotion: allocate two new index blocks, split the
949                // index entries 50/50 between them, rewrite the inline root
950                // as depth+1 with two index entries.
951                let new_depth = header.depth + 1;
952                if new_depth > EXT4_EXT_MAX_DEPTH {
953                    return Err(Error::CorruptExtentTree(
954                        "extent tree depth would exceed spec maximum",
955                    ));
956                }
957                let mid = indices.len() / 2;
958                let right_indices: Vec<(u32, u64)> = indices.split_off(mid);
959                let left_indices = indices;
960                let left_first = left_indices[0].0;
961                let right_first = right_indices[0].0;
962                let left_block = alloc()?;
963                allocated_blocks.push(left_block);
964                let right_block = alloc()?;
965                allocated_blocks.push(right_block);
966                let left_bytes =
967                    build_full_index_block(generation, header.depth, &left_indices, bs);
968                let right_bytes =
969                    build_full_index_block(generation, header.depth, &right_indices, bs);
970                block_writes.push((left_block, left_bytes));
971                block_writes.push((right_block, right_bytes));
972                let new_root = build_inline_index_root(
973                    generation,
974                    new_depth,
975                    &[(left_first, left_block), (right_first, right_block)],
976                );
977                return Ok(DeepInsertPlan {
978                    new_root,
979                    block_writes,
980                    allocated_blocks,
981                });
982            }
983            // Non-root index split.
984            let mid = indices.len() / 2;
985            let right_indices: Vec<(u32, u64)> = indices.split_off(mid);
986            let left_indices = indices;
987            let right_first = right_indices[0].0;
988            let new_right_block = alloc()?;
989            allocated_blocks.push(new_right_block);
990            let left_bytes = build_full_index_block(generation, header.depth, &left_indices, bs);
991            let right_bytes = build_full_index_block(generation, header.depth, &right_indices, bs);
992            block_writes.push((frame.block, left_bytes));
993            block_writes.push((new_right_block, right_bytes));
994            propagated = Some(PropagatedSplit {
995                new_logical: right_first,
996                new_child: new_right_block,
997            });
998        }
999    }
1000
1001    // The loop only exits when every level absorbed without spilling to the
1002    // root, but in that case we already returned from inside the loop. If
1003    // execution reaches here the invariant above is broken — surface as a
1004    // logic bug rather than silently producing the wrong tree.
1005    Err(Error::CorruptExtentTree(
1006        "deep insert: unexpected path exhaustion",
1007    ))
1008}
1009
1010/// Plan a split of `entries[idx]` at logical block `split_at` — the entry is
1011/// replaced by two halves [`start..split_at`, `split_at..end`]. The caller
1012/// uses this when partially overwriting, freeing a middle run, or converting
1013/// uninit→init on a sub-range.
1014///
1015/// Errors if `split_at` is not strictly inside the chosen extent.
1016pub fn plan_split_extent(root: &[u8], idx: usize, split_at: u32) -> Result<Vec<ExtentMutation>> {
1017    let (header, mut entries) = read_leaf_entries(root)?;
1018    let e = *entries
1019        .get(idx)
1020        .ok_or(Error::CorruptExtentTree("split idx out of range"))?;
1021    let start = e.logical_block;
1022    let end = start + e.length as u32;
1023    if split_at <= start || split_at >= end {
1024        return Err(Error::CorruptExtentTree("split point outside extent"));
1025    }
1026    let left = Extent {
1027        logical_block: start,
1028        length: (split_at - start) as u16,
1029        physical_block: e.physical_block,
1030        uninitialized: e.uninitialized,
1031    };
1032    let right = Extent {
1033        logical_block: split_at,
1034        length: (end - split_at) as u16,
1035        physical_block: e.physical_block + (split_at - start) as u64,
1036        uninitialized: e.uninitialized,
1037    };
1038    entries[idx] = left;
1039    entries.insert(idx + 1, right);
1040
1041    if entries.len() as u16 > header.max {
1042        return Err(Error::CorruptExtentTree(
1043            "LEAF_FULL_NEEDS_PROMOTION: split exceeds root capacity",
1044        ));
1045    }
1046
1047    let new_root = build_root(&header, &entries, root.len());
1048    Ok(vec![ExtentMutation::WriteRoot { bytes: new_root }])
1049}
1050
1051/// Plan fusing entries[i] and entries[i+1] when they're physically adjacent
1052/// and in the same uninit state. No-op (returns empty mutation list) if the
1053/// pair isn't contiguous.
1054pub fn plan_merge_adjacent(root: &[u8], i: usize) -> Result<Vec<ExtentMutation>> {
1055    let (header, mut entries) = read_leaf_entries(root)?;
1056    if i + 1 >= entries.len() {
1057        return Err(Error::CorruptExtentTree("merge idx out of range"));
1058    }
1059    if !are_contiguous(&entries[i], &entries[i + 1]) {
1060        return Ok(Vec::new());
1061    }
1062    let right = entries.remove(i + 1);
1063    entries[i].length += right.length;
1064    let new_root = build_root(&header, &entries, root.len());
1065    Ok(vec![ExtentMutation::WriteRoot { bytes: new_root }])
1066}
1067
1068/// Plan removal of `entries[idx]`: drops the entry from the root and emits a
1069/// [`ExtentMutation::FreePhysicalRun`] so the caller can free the backing
1070/// blocks via the bitmap allocator (E5).
1071pub fn plan_free_extent(root: &[u8], idx: usize) -> Result<Vec<ExtentMutation>> {
1072    let (header, mut entries) = read_leaf_entries(root)?;
1073    let removed = entries
1074        .get(idx)
1075        .copied()
1076        .ok_or(Error::CorruptExtentTree("free idx out of range"))?;
1077    entries.remove(idx);
1078    let new_root = build_root(&header, &entries, root.len());
1079    Ok(vec![
1080        ExtentMutation::WriteRoot { bytes: new_root },
1081        ExtentMutation::FreePhysicalRun {
1082            start: removed.physical_block,
1083            len: removed.length as u32,
1084        },
1085    ])
1086}
1087
1088/// Plan marking logical blocks `[first, end)` initialized in an inline-root
1089/// leaf, wherever an uninitialized extent covers them.
1090///
1091/// Each uninitialized extent the range touches is split into at most three:
1092/// an uninitialized head, the initialized middle and an uninitialized tail.
1093/// A piece contiguous with its neighbour in the same state is merged back.
1094/// Returns the new root and the logical blocks that changed state. Those
1095/// blocks read as zeros until now, and their physical contents are whatever
1096/// was there, so the caller has to write every byte of them.
1097///
1098/// Errors:
1099/// - `multi-level tree mutation not yet supported` on a root that isn't a leaf;
1100/// - `LEAF_FULL_NEEDS_PROMOTION` if the pieces don't fit in the root.
1101pub fn plan_initialize_range(root: &[u8], first: u32, end: u32) -> Result<(Vec<u8>, Vec<u32>)> {
1102    let (header, entries) = read_leaf_entries(root)?;
1103    let mut out: Vec<Extent> = Vec::with_capacity(entries.len() + 2);
1104    let mut converted = Vec::new();
1105    for e in entries {
1106        let el = e.logical_block;
1107        let er = el + u32::from(e.length);
1108        if !e.uninitialized || er <= first || el >= end {
1109            out.push(e);
1110            continue;
1111        }
1112        let lo = el.max(first);
1113        let hi = er.min(end);
1114        let piece = |from: u32, to: u32, uninitialized: bool| Extent {
1115            logical_block: from,
1116            length: (to - from) as u16,
1117            physical_block: e.physical_block + u64::from(from - el),
1118            uninitialized,
1119        };
1120        if el < lo {
1121            out.push(piece(el, lo, true));
1122        }
1123        out.push(piece(lo, hi, false));
1124        if hi < er {
1125            out.push(piece(hi, er, true));
1126        }
1127        converted.extend(lo..hi);
1128    }
1129    let mut merged: Vec<Extent> = Vec::with_capacity(out.len());
1130    for e in out {
1131        match merged.last_mut() {
1132            Some(prev) if are_contiguous(prev, &e) => prev.length += e.length,
1133            _ => merged.push(e),
1134        }
1135    }
1136    if merged.len() as u16 > header.max {
1137        return Err(Error::CorruptExtentTree(
1138            "LEAF_FULL_NEEDS_PROMOTION: initializing part of an extent exceeds root capacity",
1139        ));
1140    }
1141    let header = ExtentHeader {
1142        entries: merged.len() as u16,
1143        ..header
1144    };
1145    Ok((build_root(&header, &merged, root.len()), converted))
1146}
1147
1148#[cfg(test)]
1149mod tests {
1150    use super::*;
1151
1152    /// Build an inline root (60 bytes) for a leaf with the given extents.
1153    fn mk_root(extents: &[Extent]) -> Vec<u8> {
1154        let header = ExtentHeader {
1155            magic: EXT4_EXT_MAGIC,
1156            entries: extents.len() as u16,
1157            max: 4,
1158            depth: 0,
1159            generation: 0,
1160        };
1161        build_root(&header, extents, 60)
1162    }
1163
1164    fn ext(log: u32, len: u16, phys: u64, uninit: bool) -> Extent {
1165        Extent {
1166            logical_block: log,
1167            length: len,
1168            physical_block: phys,
1169            uninitialized: uninit,
1170        }
1171    }
1172
1173    /// `n` single-block extents, one every other block.
1174    fn striped(n: u32) -> Vec<Extent> {
1175        (0..n)
1176            .map(|i| ext(i * 2, 1, 1000 + i as u64, false))
1177            .collect()
1178    }
1179
1180    /// The layout uses the file's own blocks, and packs every node full.
1181    #[test]
1182    fn a_repack_uses_the_blocks_the_file_already_holds() {
1183        let bs = 1024u32;
1184        let cap = node_max_entries(bs as usize) as usize;
1185        let entries = striped(3 * cap as u32);
1186        let nodes: Vec<u64> = (5000..5100).collect();
1187        let mut refused = || {
1188            Err(Error::CorruptExtentTree(
1189                "a repack of these must not allocate",
1190            ))
1191        };
1192        let plan = plan_repack_tree(7, &entries, bs, &nodes, &mut refused).expect("repack");
1193
1194        assert_eq!(plan.allocated_blocks, Vec::<u64>::new());
1195        assert_eq!(
1196            plan.used_nodes,
1197            nodes[..3],
1198            "three full leaves, nothing more"
1199        );
1200        assert_eq!(plan.block_writes.len(), 3);
1201        let root = ExtentHeader::parse(&plan.new_root).expect("root");
1202        assert_eq!((root.depth, root.entries), (1, 3));
1203    }
1204
1205    /// Four or fewer survivors go in the inode, and the file's blocks are
1206    /// all left over for the caller to free.
1207    #[test]
1208    fn a_repack_of_four_entries_needs_no_blocks() {
1209        let entries = striped(4);
1210        let nodes = [5000u64, 5001];
1211        let mut refused = || Err(Error::CorruptExtentTree("must not allocate"));
1212        let plan = plan_repack_tree(7, &entries, 1024, &nodes, &mut refused).expect("repack");
1213
1214        assert!(plan.used_nodes.is_empty() && plan.block_writes.is_empty());
1215        let root = ExtentHeader::parse(&plan.new_root).expect("root");
1216        assert_eq!((root.depth, root.entries), (0, 4));
1217    }
1218
1219    /// A punch inside one extent leaves a head and a tail where there was one
1220    /// record, so the survivors can outnumber the entries — and a tree this
1221    /// packed full has no room for the extra. The layout then allocates, and
1222    /// only for what it is short (CodeRabbit on #262).
1223    #[test]
1224    fn a_repack_allocates_only_what_the_file_cannot_cover() {
1225        let bs = 1024u32;
1226        let cap = node_max_entries(bs as usize) as usize;
1227        // A full root over full leaves: four leaves, each packed.
1228        let entries = striped((4 * cap + 1) as u32);
1229        let nodes: Vec<u64> = (5000..5004).collect();
1230        let mut next = 9000u64;
1231        let mut alloc = || {
1232            next += 1;
1233            Ok(next)
1234        };
1235        let plan = plan_repack_tree(7, &entries, bs, &nodes, &mut alloc).expect("repack");
1236
1237        // Five leaves for the extra entry, and an index level over them,
1238        // because five index entries do not fit the inode's four.
1239        assert_eq!(
1240            plan.used_nodes, nodes,
1241            "every block the file held is reused"
1242        );
1243        assert_eq!(plan.allocated_blocks, vec![9001, 9002]);
1244        assert_eq!(plan.block_writes.len(), 6);
1245        let root = ExtentHeader::parse(&plan.new_root).expect("root");
1246        assert_eq!((root.depth, root.entries), (2, 1));
1247    }
1248
1249    fn read_back(bytes: &[u8]) -> Vec<Extent> {
1250        read_leaf_entries(bytes).unwrap().1
1251    }
1252
1253    fn shape(extents: Vec<Extent>) -> Vec<(u32, u16, u64, bool)> {
1254        extents
1255            .iter()
1256            .map(|e| (e.logical_block, e.length, e.physical_block, e.uninitialized))
1257            .collect()
1258    }
1259
1260    #[test]
1261    fn initializing_the_middle_of_an_uninit_extent_splits_it_in_three() {
1262        let root = mk_root(&[ext(0, 16, 1000, true)]);
1263        let (bytes, converted) = plan_initialize_range(&root, 4, 6).unwrap();
1264        assert_eq!(converted, vec![4, 5]);
1265        assert_eq!(
1266            shape(read_back(&bytes)),
1267            vec![(0, 4, 1000, true), (4, 2, 1004, false), (6, 10, 1006, true)]
1268        );
1269    }
1270
1271    #[test]
1272    fn initializing_all_of_an_uninit_extent_merges_with_an_initialized_neighbour() {
1273        let root = mk_root(&[ext(0, 4, 1000, false), ext(4, 4, 1004, true)]);
1274        let (bytes, converted) = plan_initialize_range(&root, 4, 20).unwrap();
1275        assert_eq!(converted, vec![4, 5, 6, 7]);
1276        assert_eq!(shape(read_back(&bytes)), vec![(0, 8, 1000, false)]);
1277    }
1278
1279    #[test]
1280    fn initialized_extents_and_ranges_outside_are_left_alone() {
1281        let root = mk_root(&[ext(0, 4, 1000, false), ext(10, 4, 2000, true)]);
1282        let (bytes, converted) = plan_initialize_range(&root, 0, 10).unwrap();
1283        assert!(converted.is_empty());
1284        assert_eq!(shape(read_back(&bytes)), shape(read_back(&root)));
1285    }
1286
1287    #[test]
1288    fn initializing_that_overflows_the_root_is_refused() {
1289        let root = mk_root(&[
1290            ext(0, 16, 1000, true),
1291            ext(20, 1, 3000, false),
1292            ext(30, 1, 4000, false),
1293        ]);
1294        let err = plan_initialize_range(&root, 4, 6).unwrap_err();
1295        assert!(
1296            format!("{err:?}").contains("LEAF_FULL_NEEDS_PROMOTION"),
1297            "{err:?}"
1298        );
1299    }
1300
1301    #[test]
1302    fn insert_into_empty_root() {
1303        let root = mk_root(&[]);
1304        let muts = plan_insert_extent(&root, ext(0, 10, 1000, false)).unwrap();
1305        assert_eq!(muts.len(), 1);
1306        let ExtentMutation::WriteRoot { bytes } = &muts[0] else {
1307            panic!("expected WriteRoot");
1308        };
1309        let back = read_back(bytes);
1310        assert_eq!(back.len(), 1);
1311        assert_eq!(back[0].logical_block, 0);
1312        assert_eq!(back[0].length, 10);
1313    }
1314
1315    #[test]
1316    fn insert_sorted_ordering() {
1317        let root = mk_root(&[ext(100, 5, 2000, false)]);
1318        let muts = plan_insert_extent(&root, ext(0, 10, 1000, false)).unwrap();
1319        let ExtentMutation::WriteRoot { bytes } = &muts[0] else {
1320            panic!()
1321        };
1322        let back = read_back(bytes);
1323        assert_eq!(back[0].logical_block, 0);
1324        assert_eq!(back[1].logical_block, 100);
1325    }
1326
1327    #[test]
1328    fn insert_merges_left_contiguous() {
1329        let root = mk_root(&[ext(0, 10, 1000, false)]);
1330        // New extent at logical 10, phys 1010 — contiguous with the first.
1331        let muts = plan_insert_extent(&root, ext(10, 5, 1010, false)).unwrap();
1332        let ExtentMutation::WriteRoot { bytes } = &muts[0] else {
1333            panic!()
1334        };
1335        let back = read_back(bytes);
1336        assert_eq!(back.len(), 1, "should merge into single extent");
1337        assert_eq!(back[0].length, 15);
1338    }
1339
1340    #[test]
1341    fn insert_does_not_merge_across_uninit_boundary() {
1342        let root = mk_root(&[ext(0, 10, 1000, false)]);
1343        // Contiguous physically/logically but marked uninitialised — must NOT merge.
1344        let muts = plan_insert_extent(&root, ext(10, 5, 1010, true)).unwrap();
1345        let ExtentMutation::WriteRoot { bytes } = &muts[0] else {
1346            panic!()
1347        };
1348        let back = read_back(bytes);
1349        assert_eq!(back.len(), 2);
1350        assert!(!back[0].uninitialized);
1351        assert!(back[1].uninitialized);
1352    }
1353
1354    #[test]
1355    fn insert_rejects_overlap() {
1356        let root = mk_root(&[ext(0, 10, 1000, false)]);
1357        let err = plan_insert_extent(&root, ext(5, 5, 2000, false)).unwrap_err();
1358        match err {
1359            Error::CorruptExtentTree(msg) => assert!(msg.contains("overlaps")),
1360            _ => panic!("wrong error kind"),
1361        }
1362    }
1363
1364    #[test]
1365    fn insert_fails_when_root_full_and_no_merge() {
1366        let root = mk_root(&[
1367            ext(0, 5, 1000, false),
1368            ext(100, 5, 2000, false),
1369            ext(200, 5, 3000, false),
1370            ext(300, 5, 4000, false),
1371        ]);
1372        let err = plan_insert_extent(&root, ext(500, 5, 5000, false)).unwrap_err();
1373        match err {
1374            Error::CorruptExtentTree(msg) => assert!(msg.contains("LEAF_FULL_NEEDS_PROMOTION")),
1375            _ => panic!("wrong error kind"),
1376        }
1377    }
1378
1379    #[test]
1380    fn split_extent_at_midpoint() {
1381        let root = mk_root(&[ext(0, 10, 1000, false)]);
1382        let muts = plan_split_extent(&root, 0, 4).unwrap();
1383        let ExtentMutation::WriteRoot { bytes } = &muts[0] else {
1384            panic!()
1385        };
1386        let back = read_back(bytes);
1387        assert_eq!(back.len(), 2);
1388        assert_eq!(back[0].logical_block, 0);
1389        assert_eq!(back[0].length, 4);
1390        assert_eq!(back[0].physical_block, 1000);
1391        assert_eq!(back[1].logical_block, 4);
1392        assert_eq!(back[1].length, 6);
1393        assert_eq!(back[1].physical_block, 1004);
1394    }
1395
1396    #[test]
1397    fn split_rejects_boundary_point() {
1398        let root = mk_root(&[ext(0, 10, 1000, false)]);
1399        assert!(plan_split_extent(&root, 0, 0).is_err()); // start
1400        assert!(plan_split_extent(&root, 0, 10).is_err()); // end
1401    }
1402
1403    #[test]
1404    fn merge_adjacent_contiguous_pair() {
1405        let root = mk_root(&[
1406            ext(0, 5, 1000, false),
1407            ext(5, 5, 1005, false), // contiguous
1408        ]);
1409        let muts = plan_merge_adjacent(&root, 0).unwrap();
1410        let ExtentMutation::WriteRoot { bytes } = &muts[0] else {
1411            panic!()
1412        };
1413        let back = read_back(bytes);
1414        assert_eq!(back.len(), 1);
1415        assert_eq!(back[0].length, 10);
1416    }
1417
1418    #[test]
1419    fn merge_noop_when_not_contiguous() {
1420        let root = mk_root(&[
1421            ext(0, 5, 1000, false),
1422            ext(5, 5, 5000, false), // physically apart
1423        ]);
1424        let muts = plan_merge_adjacent(&root, 0).unwrap();
1425        assert!(muts.is_empty(), "no-op when not contiguous");
1426    }
1427
1428    #[test]
1429    fn free_extent_emits_physical_run() {
1430        let root = mk_root(&[ext(0, 5, 1000, false), ext(10, 3, 2000, false)]);
1431        let muts = plan_free_extent(&root, 0).unwrap();
1432        assert_eq!(muts.len(), 2);
1433        match &muts[1] {
1434            ExtentMutation::FreePhysicalRun { start, len } => {
1435                assert_eq!(*start, 1000);
1436                assert_eq!(*len, 5);
1437            }
1438            _ => panic!("expected FreePhysicalRun"),
1439        }
1440        let ExtentMutation::WriteRoot { bytes } = &muts[0] else {
1441            panic!()
1442        };
1443        let back = read_back(bytes);
1444        assert_eq!(back.len(), 1);
1445        assert_eq!(back[0].logical_block, 10);
1446    }
1447
1448    #[test]
1449    fn promote_leaf_moves_entries_into_new_leaf_block() {
1450        let root = mk_root(&[
1451            ext(0, 5, 1000, false),
1452            ext(100, 5, 2000, false),
1453            ext(200, 5, 3000, false),
1454            ext(300, 5, 4000, false),
1455        ]);
1456        let plan = plan_promote_leaf(&root, ext(500, 5, 5000, false), 4096, 900_000, true).unwrap();
1457
1458        assert_eq!(plan.new_root_bytes.len(), 60);
1459        let hdr = ExtentHeader::parse(&plan.new_root_bytes).unwrap();
1460        assert_eq!(hdr.depth, 1);
1461        assert_eq!(hdr.entries, 1);
1462        assert_eq!(hdr.max, 4);
1463        let idx = crate::extent::ExtentIdx::parse(
1464            &plan.new_root_bytes[EXT4_EXT_NODE_SIZE..2 * EXT4_EXT_NODE_SIZE],
1465        )
1466        .unwrap();
1467        assert_eq!(idx.logical_block, 0);
1468        assert_eq!(idx.leaf_block, 900_000);
1469
1470        assert_eq!(plan.leaf_bytes.len(), 4096);
1471        let leaf_hdr = ExtentHeader::parse(&plan.leaf_bytes).unwrap();
1472        assert_eq!(leaf_hdr.depth, 0);
1473        assert_eq!(leaf_hdr.entries, 5);
1474        let expected_max = ((4096 - 12 - 4) / 12) as u16;
1475        assert_eq!(leaf_hdr.max, expected_max);
1476        let parsed = read_back(&plan.leaf_bytes[..12 + 5 * 12]);
1477        assert_eq!(parsed.len(), 5);
1478        let logs: Vec<u32> = parsed.iter().map(|e| e.logical_block).collect();
1479        assert_eq!(logs, vec![0, 100, 200, 300, 500]);
1480    }
1481
1482    #[test]
1483    fn promote_leaf_rejects_overlap() {
1484        let root = mk_root(&[
1485            ext(0, 5, 1000, false),
1486            ext(100, 5, 2000, false),
1487            ext(200, 5, 3000, false),
1488            ext(300, 5, 4000, false),
1489        ]);
1490        let err = plan_promote_leaf(&root, ext(2, 5, 9000, false), 4096, 500, true).unwrap_err();
1491        match err {
1492            Error::CorruptExtentTree(m) => assert!(m.contains("overlaps")),
1493            _ => panic!("wrong error kind"),
1494        }
1495    }
1496
1497    #[test]
1498    fn promote_leaf_preserves_generation() {
1499        let extents = [
1500            ext(0, 5, 1000, false),
1501            ext(100, 5, 2000, false),
1502            ext(200, 5, 3000, false),
1503            ext(300, 5, 4000, false),
1504        ];
1505        let header = ExtentHeader {
1506            magic: EXT4_EXT_MAGIC,
1507            entries: 4,
1508            max: 4,
1509            depth: 0,
1510            generation: 0xDEAD_BEEF,
1511        };
1512        let root = build_root(&header, &extents, 60);
1513        let plan = plan_promote_leaf(&root, ext(500, 5, 5000, false), 4096, 777, false).unwrap();
1514        assert_eq!(
1515            ExtentHeader::parse(&plan.new_root_bytes)
1516                .unwrap()
1517                .generation,
1518            0xDEAD_BEEF
1519        );
1520        assert_eq!(
1521            ExtentHeader::parse(&plan.leaf_bytes).unwrap().generation,
1522            0xDEAD_BEEF
1523        );
1524    }
1525
1526    #[test]
1527    fn promote_leaf_merges_when_contiguous_with_new() {
1528        let root = mk_root(&[
1529            ext(0, 5, 1000, false),
1530            ext(100, 5, 2000, false),
1531            ext(200, 5, 3000, false),
1532            ext(300, 5, 4000, false),
1533        ]);
1534        let plan = plan_promote_leaf(&root, ext(305, 5, 4005, false), 4096, 123, false).unwrap();
1535        let leaf_hdr = ExtentHeader::parse(&plan.leaf_bytes).unwrap();
1536        assert_eq!(leaf_hdr.entries, 4);
1537        let parsed = read_back(&plan.leaf_bytes[..12 + 4 * 12]);
1538        assert_eq!(parsed[3].logical_block, 300);
1539        assert_eq!(parsed[3].length, 10);
1540    }
1541
1542    #[test]
1543    fn multi_level_tree_refused_cleanly() {
1544        // Synthetic internal-node root (depth=1).
1545        let mut buf = vec![0u8; 60];
1546        buf[0..2].copy_from_slice(&EXT4_EXT_MAGIC.to_le_bytes());
1547        buf[2..4].copy_from_slice(&0u16.to_le_bytes());
1548        buf[4..6].copy_from_slice(&4u16.to_le_bytes());
1549        buf[6..8].copy_from_slice(&1u16.to_le_bytes()); // depth=1 → not leaf
1550        let err = plan_insert_extent(&buf, ext(0, 1, 1000, false)).unwrap_err();
1551        match err {
1552            Error::CorruptExtentTree(msg) => assert!(msg.contains("multi-level")),
1553            _ => panic!("wrong error kind"),
1554        }
1555    }
1556
1557    /// The `WriteRoot` bytes of a single-mutation plan.
1558    fn root_of(muts: Vec<ExtentMutation>) -> Vec<u8> {
1559        match muts.into_iter().next() {
1560            Some(ExtentMutation::WriteRoot { bytes }) => bytes,
1561            other => panic!("expected WriteRoot, got {other:?}"),
1562        }
1563    }
1564
1565    /// Sum of the lengths, and whether any entry reads back uninitialized.
1566    fn total_and_any_uninit(entries: &[Extent]) -> (u64, bool) {
1567        (
1568            entries.iter().map(|e| e.length as u64).sum(),
1569            entries.iter().any(|e| e.uninitialized),
1570        )
1571    }
1572
1573    /// Two contiguous initialized extents whose sum passes 32768 stay two
1574    /// (#387): one `ee_len` over 32768 reads back as an uninitialized extent.
1575    #[test]
1576    fn an_insert_does_not_merge_past_the_initialized_extent_limit() {
1577        let m = plan_insert_extent(
1578            &mk_root(&[ext(0, 32768, 1000, false)]),
1579            ext(32768, 1, 33768, false),
1580        )
1581        .unwrap();
1582        let entries = read_back(&root_of(m));
1583        assert_eq!(
1584            total_and_any_uninit(&entries),
1585            (32769, false),
1586            "{entries:?}"
1587        );
1588        assert_eq!(entries.len(), 2, "{entries:?}");
1589    }
1590
1591    /// Two contiguous uninit extents whose sum passes 32767 stay two
1592    /// (#387): the merge used to overflow `ee_len`'s u16.
1593    #[test]
1594    fn an_insert_does_not_merge_past_the_uninit_extent_limit() {
1595        let r = std::panic::catch_unwind(|| {
1596            plan_insert_extent(
1597                &mk_root(&[ext(0, 20000, 1000, true)]),
1598                ext(20000, 20000, 21000, true),
1599            )
1600        });
1601        let entries = read_back(&root_of(r.expect("no panic").unwrap()));
1602        assert_eq!(total_and_any_uninit(&entries), (40000, true), "{entries:?}");
1603        assert!(entries.iter().all(|e| e.uninitialized), "{entries:?}");
1604    }
1605
1606    /// A merge exactly at the limit still happens: the cap is `<=`.
1607    #[test]
1608    fn an_insert_still_merges_up_to_the_limit() {
1609        let m = plan_insert_extent(
1610            &mk_root(&[ext(0, 32767, 1000, false)]),
1611            ext(32767, 1, 33767, false),
1612        )
1613        .unwrap();
1614        let entries = read_back(&root_of(m));
1615        assert_eq!(shape(entries), vec![(0, 32768, 1000, false)]);
1616        let m = plan_insert_extent(
1617            &mk_root(&[ext(0, 32766, 1000, true)]),
1618            ext(32766, 1, 33766, true),
1619        )
1620        .unwrap();
1621        assert_eq!(shape(read_back(&root_of(m))), vec![(0, 32767, 1000, true)]);
1622    }
1623
1624    /// A merge with the right-hand neighbour is capped too.
1625    #[test]
1626    fn an_insert_does_not_merge_right_past_the_limit() {
1627        let m = plan_insert_extent(
1628            &mk_root(&[ext(1, 32768, 1001, false)]),
1629            ext(0, 1, 1000, false),
1630        )
1631        .unwrap();
1632        let entries = read_back(&root_of(m));
1633        assert_eq!(
1634            total_and_any_uninit(&entries),
1635            (32769, false),
1636            "{entries:?}"
1637        );
1638    }
1639
1640    /// `plan_merge_adjacent` refuses a pair whose sum passes the limit.
1641    #[test]
1642    fn an_explicit_merge_does_not_pass_the_extent_limit() {
1643        let root = mk_root(&[ext(0, 30000, 1000, false), ext(30000, 5000, 31000, false)]);
1644        let m = plan_merge_adjacent(&root, 0).unwrap();
1645        assert!(m.is_empty(), "a merge past 32768 was planned: {m:?}");
1646    }
1647
1648    /// The deep-tree leaf insert (`insert_into_leaf_sorted`) is capped too.
1649    #[test]
1650    fn a_deep_leaf_insert_does_not_merge_past_the_extent_limit() {
1651        let mut entries = vec![ext(0, 30000, 1000, false)];
1652        insert_into_leaf_sorted(&mut entries, ext(30000, 5000, 31000, false));
1653        assert_eq!(
1654            total_and_any_uninit(&entries),
1655            (35000, false),
1656            "{entries:?}"
1657        );
1658        assert!(entries.iter().all(|e| e.length <= 32768), "{entries:?}");
1659    }
1660
1661    /// Promoting a full root to depth 1 does not merge past the limit.
1662    #[test]
1663    fn a_promotion_does_not_merge_past_the_extent_limit() {
1664        let root = mk_root(&[
1665            ext(0, 5, 1000, false),
1666            ext(100, 5, 2000, false),
1667            ext(200, 5, 3000, false),
1668            ext(300, 30000, 4000, false),
1669        ]);
1670        let plan =
1671            plan_promote_leaf(&root, ext(30300, 5000, 34000, false), 4096, 123, false).unwrap();
1672        let hdr = ExtentHeader::parse(&plan.leaf_bytes).unwrap();
1673        let entries = read_back(&plan.leaf_bytes[..12 + hdr.entries as usize * 12]);
1674        assert_eq!(
1675            total_and_any_uninit(&entries),
1676            (35015, false),
1677            "{entries:?}"
1678        );
1679    }
1680
1681    /// Index node bytes: `entries` index entries (all at logical 0, all
1682    /// pointing at `child`) under a header claiming `max`, at `depth`.
1683    fn index_node(len: usize, entries: u16, max: u16, depth: u16, child: u64) -> Vec<u8> {
1684        let mut buf = vec![0u8; len];
1685        buf[0..2].copy_from_slice(&EXT4_EXT_MAGIC.to_le_bytes());
1686        buf[2..4].copy_from_slice(&entries.to_le_bytes());
1687        buf[4..6].copy_from_slice(&max.to_le_bytes());
1688        buf[6..8].copy_from_slice(&depth.to_le_bytes());
1689        for i in 0..entries as usize {
1690            let off = extent_entry_offset(i);
1691            if off + EXT4_EXT_NODE_SIZE > len {
1692                break;
1693            }
1694            let (hi, lo) = split_phys_block(child);
1695            buf[off + 4..off + 8].copy_from_slice(&lo.to_le_bytes());
1696            buf[off + 8..off + 10].copy_from_slice(&hi.to_le_bytes());
1697        }
1698        buf
1699    }
1700
1701    /// Serves one block for every read.
1702    struct OneBlock(Vec<u8>);
1703
1704    impl DeepReader for OneBlock {
1705        fn read_block(&self, _block: u64, out: &mut [u8]) -> Result<()> {
1706            out.copy_from_slice(&self.0);
1707            Ok(())
1708        }
1709    }
1710
1711    /// `ExtentHeader::parse` checks `entries <= max` but not `max` against
1712    /// the node, so a 60-byte root claiming five index entries sent the
1713    /// descent slicing `[60..72]` (#327).
1714    #[test]
1715    fn a_root_claiming_more_index_entries_than_it_holds_is_refused() {
1716        let root = index_node(60, 5, 5, 1, 7000);
1717        let reader = OneBlock(vec![0u8; 1024]);
1718        let mut alloc = || -> Result<u64> { panic!("nothing is allocated") };
1719        let err =
1720            plan_insert_extent_deep(&root, ext(1000, 1, 9000, false), 1024, &reader, &mut alloc)
1721                .expect_err("a root with five index entries in a four-entry area");
1722        assert!(matches!(err, Error::CorruptExtentTree(_)), "{err:?}");
1723    }
1724
1725    /// The same through a child block whose header claims more entries
1726    /// than the block holds (#327).
1727    #[test]
1728    fn a_child_claiming_more_index_entries_than_its_block_holds_is_refused() {
1729        let root = index_node(60, 1, 4, 2, 7000);
1730        // 100 entries of 12 bytes after a 12-byte header: 1212 > 1024.
1731        let reader = OneBlock(index_node(1024, 100, 100, 1, 7001));
1732        let mut alloc = || -> Result<u64> { panic!("nothing is allocated") };
1733        let err =
1734            plan_insert_extent_deep(&root, ext(1000, 1, 9000, false), 1024, &reader, &mut alloc)
1735                .expect_err("a child with a hundred index entries in a 1 KiB block");
1736        assert!(matches!(err, Error::CorruptExtentTree(_)), "{err:?}");
1737    }
1738}