1use 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
31const 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#[inline]
41fn extent_entry_offset(i: usize) -> usize {
42 EXT4_EXT_NODE_SIZE * (1 + i)
43}
44
45#[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#[derive(Debug, Clone, PartialEq, Eq)]
57pub enum ExtentMutation {
58 WriteRoot { bytes: Vec<u8> },
60 WriteTreeBlock { block: u64, bytes: Vec<u8> },
63 AllocLeafBlock { bytes: Vec<u8> },
66 FreeLeafBlock { block: u64 },
68 FreePhysicalRun { start: u64, len: u32 },
72}
73
74fn 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
90fn 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
111fn 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
132fn 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
145fn 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
164pub fn plan_insert_extent(root: &[u8], new: Extent) -> Result<Vec<ExtentMutation>> {
174 let (header, mut entries) = read_leaf_entries(root)?;
175
176 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 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 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 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#[derive(Debug, Clone, PartialEq, Eq)]
225pub struct PromotionPlan {
226 pub leaf_bytes: Vec<u8>,
231 pub new_root_bytes: Vec<u8>,
235}
236
237fn 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
262fn 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()); out[EXT4_EXT_HDR_MAX_OFF..EXT4_EXT_HDR_MAX_OFF + 2].copy_from_slice(&4u16.to_le_bytes()); out[EXT4_EXT_HDR_DEPTH_OFF..EXT4_EXT_HDR_DEPTH_OFF + 2].copy_from_slice(&1u16.to_le_bytes()); 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
281pub 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
352pub trait DeepReader {
361 fn read_block(&self, block: u64, out: &mut [u8]) -> Result<()>;
364}
365
366#[derive(Debug, Clone, PartialEq, Eq)]
370pub struct DeepInsertPlan {
371 pub new_root: Vec<u8>,
373 pub block_writes: Vec<(u64, Vec<u8>)>,
378 pub allocated_blocks: Vec<u64>,
382}
383
384struct PathFrame {
395 block: u64,
397 bytes: Vec<u8>,
399}
400
401fn 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 buf
411}
412
413fn 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
427fn 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
441fn node_max_entries(block_size: usize) -> u16 {
446 ((block_size - EXT4_EXT_NODE_SIZE - 4) / EXT4_EXT_NODE_SIZE) as u16
447}
448
449fn 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
469fn 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
493fn 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()); 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
510fn 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
522pub struct RepackedTree {
524 pub new_root: Vec<u8>,
526 pub block_writes: Vec<(u64, Vec<u8>)>,
529 pub used_nodes: Vec<u64>,
532 pub allocated_blocks: Vec<u64>,
535}
536
537pub 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 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 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
625fn 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
639fn 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 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
669fn 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
678struct PropagatedSplit {
682 new_logical: u32,
684 new_child: u64,
686}
687
688fn 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 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 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
743pub 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 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 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 let first_key_before = leaf_entries.first().map(|e| e.logical_block);
790 insert_into_leaf_sorted(&mut leaf_entries, new);
791
792 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 let mut propagated: Option<PropagatedSplit>;
807 let mut correction: Option<(u64, u32)> = None;
810
811 if (leaf_entries.len() as u16) <= leaf_cap {
812 if is_root_leaf {
814 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 return Ok(DeepInsertPlan {
829 new_root: root_bytes.to_vec(),
830 block_writes,
831 allocated_blocks,
832 });
833 }
834 propagated = None;
836 correction = Some((leaf_frame.block, first_key));
837 } else {
838 if is_root_leaf {
840 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 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 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 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 continue;
937 }
938 return Ok(DeepInsertPlan {
941 new_root: root_bytes.to_vec(),
942 block_writes,
943 allocated_blocks,
944 });
945 } else {
946 if is_root_here {
948 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 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 Err(Error::CorruptExtentTree(
1006 "deep insert: unexpected path exhaustion",
1007 ))
1008}
1009
1010pub 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
1051pub 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
1068pub 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
1088pub 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 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 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 #[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 #[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 #[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 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 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 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 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()); assert!(plan_split_extent(&root, 0, 10).is_err()); }
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), ]);
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), ]);
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 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()); 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 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 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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 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 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 #[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 #[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 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}