1use crate::block_io::BlockDevice;
18use crate::error::{Error, Result};
19
20pub const DIRECT_COUNT: u32 = 12;
22
23#[inline]
24fn ppb(block_size: u32) -> u32 {
25 block_size / 4
26}
27
28#[inline]
29fn write_u32_le(buf: &mut [u8], idx: usize, val: u32) {
30 let off = idx * 4;
31 buf[off..off + 4].copy_from_slice(&val.to_le_bytes());
32}
33
34#[inline]
35fn read_u32_le(buf: &[u8], idx: usize) -> u32 {
36 let off = idx * 4;
37 u32::from_le_bytes(buf[off..off + 4].try_into().unwrap())
38}
39
40#[derive(Debug, Clone)]
50pub struct IndirectPlan {
51 pub i_block: [u8; 60],
52 pub block_writes: Vec<(u64, Vec<u8>)>,
53 pub indirect_blocks_allocated: Vec<u64>,
54}
55
56pub fn count_indirect_blocks(data_blocks: u32, block_size: u32) -> u64 {
71 let ppb = (block_size / 4) as u64;
72 if ppb == 0 {
73 return 0;
74 }
75 let mut remaining = data_blocks as u64;
76 let mut count: u64 = 0;
77
78 if remaining <= DIRECT_COUNT as u64 {
80 return 0;
81 }
82 remaining -= DIRECT_COUNT as u64;
83
84 count += 1;
86 if remaining <= ppb {
87 return count;
88 }
89 remaining -= ppb;
90
91 count += 1; let in_double = remaining.min(ppb * ppb);
94 count += in_double.div_ceil(ppb);
95 if remaining <= ppb * ppb {
96 return count;
97 }
98 remaining -= ppb * ppb;
99
100 count += 1; let in_triple = remaining.min(ppb * ppb * ppb);
103 let l2_count = in_triple.div_ceil(ppb * ppb);
104 let l3_count = in_triple.div_ceil(ppb);
105 count += l2_count + l3_count;
106 count
107}
108
109pub fn plan_contiguous<F>(
122 data_block_count: u32,
123 first_data_phys: u64,
124 block_size: u32,
125 mut alloc_one: F,
126) -> Result<IndirectPlan>
127where
128 F: FnMut() -> Result<u64>,
129{
130 if !block_size.is_power_of_two() || block_size < 1024 {
131 return Err(Error::InvalidArgument(
132 "indirect_mut: block_size out of range",
133 ));
134 }
135 let ppb = ppb(block_size);
136 if ppb == 0 {
137 return Err(Error::InvalidArgument(
138 "indirect_mut: block_size too small for any pointers",
139 ));
140 }
141
142 let mut plan = IndirectPlan {
143 i_block: [0u8; 60],
144 block_writes: Vec::new(),
145 indirect_blocks_allocated: Vec::new(),
146 };
147
148 let last_data_phys = first_data_phys
150 .checked_add(data_block_count.saturating_sub(1) as u64)
151 .ok_or(Error::InvalidArgument(
152 "indirect_mut: physical address overflow",
153 ))?;
154 if last_data_phys > u32::MAX as u64 {
155 return Err(Error::InvalidArgument(
156 "indirect_mut: data physical block exceeds u32 (ext2/3 cap)",
157 ));
158 }
159
160 let mut remaining = data_block_count;
161 let mut logical_offset: u32 = 0;
162
163 let emit_data_ptrs = |buf: &mut [u8], idx: usize, n: u32, base: u32| {
166 for i in 0..n {
167 write_u32_le(buf, idx + i as usize, base + i);
168 }
169 };
170
171 {
173 let direct_count = remaining.min(DIRECT_COUNT);
174 let base = (first_data_phys + logical_offset as u64) as u32;
175 emit_data_ptrs(&mut plan.i_block, 0, direct_count, base);
176 remaining -= direct_count;
177 logical_offset += direct_count;
178 }
179 if remaining == 0 {
180 return Ok(plan);
181 }
182
183 {
185 let single_blk = alloc_one()?;
186 check_u32(single_blk, "single-indirect block")?;
187 plan.indirect_blocks_allocated.push(single_blk);
188 write_u32_le(&mut plan.i_block, 12, single_blk as u32);
189
190 let n = remaining.min(ppb);
191 let mut buf = vec![0u8; block_size as usize];
192 let base = (first_data_phys + logical_offset as u64) as u32;
193 emit_data_ptrs(&mut buf, 0, n, base);
194 plan.block_writes.push((single_blk, buf));
195
196 remaining -= n;
197 logical_offset += n;
198 }
199 if remaining == 0 {
200 return Ok(plan);
201 }
202
203 {
205 let outer_blk = alloc_one()?;
206 check_u32(outer_blk, "double-indirect outer block")?;
207 plan.indirect_blocks_allocated.push(outer_blk);
208 write_u32_le(&mut plan.i_block, 13, outer_blk as u32);
209
210 let tier_capacity = ppb as u64 * ppb as u64;
212 let tier_count = (remaining as u64).min(tier_capacity) as u32;
213 let inner_blocks_needed = tier_count.div_ceil(ppb);
214
215 let mut outer_buf = vec![0u8; block_size as usize];
216 for outer_idx in 0..inner_blocks_needed {
217 let inner_blk = alloc_one()?;
218 check_u32(inner_blk, "double-indirect inner block")?;
219 plan.indirect_blocks_allocated.push(inner_blk);
220 write_u32_le(&mut outer_buf, outer_idx as usize, inner_blk as u32);
221
222 let inner_count = remaining.min(ppb);
223 let mut inner_buf = vec![0u8; block_size as usize];
224 let base = (first_data_phys + logical_offset as u64) as u32;
225 emit_data_ptrs(&mut inner_buf, 0, inner_count, base);
226 plan.block_writes.push((inner_blk, inner_buf));
227
228 remaining -= inner_count;
229 logical_offset += inner_count;
230 }
231 plan.block_writes.push((outer_blk, outer_buf));
232 }
233 if remaining == 0 {
234 return Ok(plan);
235 }
236
237 {
239 let l1_blk = alloc_one()?;
240 check_u32(l1_blk, "triple-indirect L1 block")?;
241 plan.indirect_blocks_allocated.push(l1_blk);
242 write_u32_le(&mut plan.i_block, 14, l1_blk as u32);
243
244 let tier_capacity = ppb as u64 * ppb as u64 * ppb as u64;
245 let tier_count = (remaining as u64).min(tier_capacity) as u32;
246 let l2_blocks_needed = tier_count.div_ceil(ppb * ppb);
247
248 let mut l1_buf = vec![0u8; block_size as usize];
249 for l1_idx in 0..l2_blocks_needed {
250 let l2_blk = alloc_one()?;
251 check_u32(l2_blk, "triple-indirect L2 block")?;
252 plan.indirect_blocks_allocated.push(l2_blk);
253 write_u32_le(&mut l1_buf, l1_idx as usize, l2_blk as u32);
254
255 let l3_capacity_under_this_l2 = ppb as u64 * ppb as u64;
258 let l3_count = (remaining as u64).min(l3_capacity_under_this_l2) as u32;
259 let l3_blocks_needed = l3_count.div_ceil(ppb);
260
261 let mut l2_buf = vec![0u8; block_size as usize];
262 for l2_idx in 0..l3_blocks_needed {
263 let l3_blk = alloc_one()?;
264 check_u32(l3_blk, "triple-indirect L3 block")?;
265 plan.indirect_blocks_allocated.push(l3_blk);
266 write_u32_le(&mut l2_buf, l2_idx as usize, l3_blk as u32);
267
268 let inner_count = remaining.min(ppb);
269 let mut l3_buf = vec![0u8; block_size as usize];
270 let base = (first_data_phys + logical_offset as u64) as u32;
271 emit_data_ptrs(&mut l3_buf, 0, inner_count, base);
272 plan.block_writes.push((l3_blk, l3_buf));
273
274 remaining -= inner_count;
275 logical_offset += inner_count;
276 }
277 plan.block_writes.push((l2_blk, l2_buf));
278 }
279 plan.block_writes.push((l1_blk, l1_buf));
280 }
281
282 if remaining > 0 {
283 return Err(Error::InvalidArgument(
284 "indirect_mut: data block count exceeds triple-indirect address space",
285 ));
286 }
287 Ok(plan)
288}
289
290fn check_u32(v: u64, what: &'static str) -> Result<()> {
291 if v > u32::MAX as u64 {
292 let _ = what;
295 return Err(Error::Corrupt(
296 "indirect_mut: indirect block address exceeds u32 (ext2/3 cap)",
297 ));
298 }
299 Ok(())
300}
301
302#[derive(Debug, Clone, Copy, PartialEq, Eq)]
304pub struct FreeRun {
305 pub start: u64,
306 pub len: u32,
307}
308
309#[derive(Debug, Clone, Default)]
316pub struct FreedBlocks {
317 pub data_runs: Vec<FreeRun>,
318 pub indirect_blocks: Vec<u64>,
319}
320
321pub fn collect_for_free(
333 i_block: &[u8; 60],
334 block_size: u32,
335 expected_data_blocks: u32,
336 dev: &dyn BlockDevice,
337) -> Result<FreedBlocks> {
338 if !block_size.is_power_of_two() || block_size < 1024 {
339 return Err(Error::InvalidArgument(
340 "indirect_mut: block_size out of range",
341 ));
342 }
343 let ppb = ppb(block_size);
344
345 let mut data_blocks: Vec<u64> = Vec::new();
346 let mut indirect_blocks: Vec<u64> = Vec::new();
347 let mut remaining = expected_data_blocks as u64;
348
349 let direct_count = (remaining.min(DIRECT_COUNT as u64)) as usize;
351 for i in 0..direct_count {
352 let p = read_u32_le(i_block, i) as u64;
353 if p != 0 {
354 data_blocks.push(p);
355 }
356 }
357 remaining = remaining.saturating_sub(direct_count as u64);
358
359 let single_blk = read_u32_le(i_block, 12) as u64;
361 if remaining > 0 && single_blk != 0 {
362 indirect_blocks.push(single_blk);
363 let buf = read_block(dev, single_blk, block_size)?;
364 let take = remaining.min(ppb as u64) as usize;
365 for i in 0..take {
366 let p = read_u32_le(&buf, i) as u64;
367 if p != 0 {
368 data_blocks.push(p);
369 }
370 }
371 remaining = remaining.saturating_sub(take as u64);
372 } else {
373 remaining = remaining.saturating_sub(ppb as u64);
374 }
375
376 let double_blk = read_u32_le(i_block, 13) as u64;
378 if remaining > 0 && double_blk != 0 {
379 indirect_blocks.push(double_blk);
380 let outer = read_block(dev, double_blk, block_size)?;
381 let outer_take = remaining.min(ppb as u64 * ppb as u64).div_ceil(ppb as u64) as usize;
382 for outer_idx in 0..outer_take {
383 let inner_blk = read_u32_le(&outer, outer_idx) as u64;
384 if inner_blk == 0 {
385 remaining = remaining.saturating_sub(ppb as u64);
386 continue;
387 }
388 indirect_blocks.push(inner_blk);
389 let inner = read_block(dev, inner_blk, block_size)?;
390 let take = remaining.min(ppb as u64) as usize;
391 for i in 0..take {
392 let p = read_u32_le(&inner, i) as u64;
393 if p != 0 {
394 data_blocks.push(p);
395 }
396 }
397 remaining = remaining.saturating_sub(take as u64);
398 }
399 } else {
400 remaining = remaining.saturating_sub(ppb as u64 * ppb as u64);
401 }
402
403 let triple_blk = read_u32_le(i_block, 14) as u64;
405 if remaining > 0 && triple_blk != 0 {
406 indirect_blocks.push(triple_blk);
407 let l1 = read_block(dev, triple_blk, block_size)?;
408 let l2_count = remaining
409 .min(ppb as u64 * ppb as u64 * ppb as u64)
410 .div_ceil(ppb as u64 * ppb as u64) as usize;
411 for l1_idx in 0..l2_count {
412 let l2_blk = read_u32_le(&l1, l1_idx) as u64;
413 if l2_blk == 0 {
414 remaining = remaining.saturating_sub(ppb as u64 * ppb as u64);
415 continue;
416 }
417 indirect_blocks.push(l2_blk);
418 let l2 = read_block(dev, l2_blk, block_size)?;
419 let l3_count = remaining.min(ppb as u64 * ppb as u64).div_ceil(ppb as u64) as usize;
420 for l2_idx in 0..l3_count {
421 let l3_blk = read_u32_le(&l2, l2_idx) as u64;
422 if l3_blk == 0 {
423 remaining = remaining.saturating_sub(ppb as u64);
424 continue;
425 }
426 indirect_blocks.push(l3_blk);
427 let l3 = read_block(dev, l3_blk, block_size)?;
428 let take = remaining.min(ppb as u64) as usize;
429 for i in 0..take {
430 let p = read_u32_le(&l3, i) as u64;
431 if p != 0 {
432 data_blocks.push(p);
433 }
434 }
435 remaining = remaining.saturating_sub(take as u64);
436 }
437 }
438 }
439
440 data_blocks.sort_unstable();
442 data_blocks.dedup();
443 let data_runs = coalesce_runs(&data_blocks);
444
445 indirect_blocks.sort_unstable();
447 indirect_blocks.dedup();
448
449 Ok(FreedBlocks {
450 data_runs,
451 indirect_blocks,
452 })
453}
454
455fn read_block(dev: &dyn BlockDevice, block_no: u64, block_size: u32) -> Result<Vec<u8>> {
456 let mut buf = vec![0u8; block_size as usize];
457 let off = block_no
458 .checked_mul(block_size as u64)
459 .ok_or(Error::Corrupt("indirect_mut: block byte offset overflow"))?;
460 dev.read_at(off, &mut buf)?;
461 Ok(buf)
462}
463
464fn coalesce_runs(sorted: &[u64]) -> Vec<FreeRun> {
465 let mut out: Vec<FreeRun> = Vec::new();
466 for &b in sorted {
467 if let Some(last) = out.last_mut() {
468 if last.start + last.len as u64 == b {
469 last.len += 1;
470 continue;
471 }
472 }
473 out.push(FreeRun { start: b, len: 1 });
474 }
475 out
476}
477
478#[cfg(test)]
479mod tests {
480 use super::*;
481 use crate::block_io::BlockDevice;
482 use std::sync::Mutex;
483
484 struct MemDev {
488 data: Mutex<Vec<u8>>,
489 }
490
491 impl MemDev {
492 fn new(size: usize) -> Self {
493 Self {
494 data: Mutex::new(vec![0u8; size]),
495 }
496 }
497 fn write_block(&self, block_no: u64, block_size: u32, contents: &[u8]) {
498 let mut d = self.data.lock().unwrap();
499 let off = (block_no as usize) * (block_size as usize);
500 d[off..off + contents.len()].copy_from_slice(contents);
501 }
502 }
503
504 impl BlockDevice for MemDev {
505 fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<()> {
506 let d = self.data.lock().unwrap();
507 let start = offset as usize;
508 buf.copy_from_slice(&d[start..start + buf.len()]);
509 Ok(())
510 }
511 fn size_bytes(&self) -> u64 {
512 self.data.lock().unwrap().len() as u64
513 }
514 }
515
516 fn make_alloc(start: u64) -> impl FnMut() -> Result<u64> {
518 let mut next = start;
519 move || {
520 let v = next;
521 next += 1;
522 Ok(v)
523 }
524 }
525
526 #[test]
527 fn plan_contiguous_direct_only() {
528 let bs = 1024u32;
529 let plan = plan_contiguous(5, 100, bs, make_alloc(1000)).unwrap();
530 assert!(plan.indirect_blocks_allocated.is_empty());
531 assert!(plan.block_writes.is_empty());
532 for i in 0..5 {
533 assert_eq!(read_u32_le(&plan.i_block, i), 100 + i as u32);
534 }
535 for i in 5..15 {
537 assert_eq!(read_u32_le(&plan.i_block, i), 0);
538 }
539 }
540
541 #[test]
542 fn plan_contiguous_fills_all_direct() {
543 let bs = 1024u32;
544 let plan = plan_contiguous(12, 200, bs, make_alloc(500)).unwrap();
545 assert!(plan.indirect_blocks_allocated.is_empty());
546 assert!(plan.block_writes.is_empty());
547 for i in 0..12 {
548 assert_eq!(read_u32_le(&plan.i_block, i), 200 + i as u32);
549 }
550 for i in 12..15 {
552 assert_eq!(read_u32_le(&plan.i_block, i), 0);
553 }
554 }
555
556 #[test]
557 fn plan_contiguous_into_single_indirect() {
558 let bs = 1024u32; let plan = plan_contiguous(17, 300, bs, make_alloc(900)).unwrap();
561 assert_eq!(plan.indirect_blocks_allocated, vec![900]);
562 assert_eq!(plan.block_writes.len(), 1);
563 assert_eq!(read_u32_le(&plan.i_block, 12), 900);
565 let (blk_no, ref buf) = plan.block_writes[0];
567 assert_eq!(blk_no, 900);
568 for i in 0..5 {
569 assert_eq!(read_u32_le(buf, i), 300 + 12 + i as u32);
570 }
571 assert_eq!(read_u32_le(buf, 5), 0);
573 }
574
575 #[test]
576 fn plan_contiguous_fills_single_indirect_exactly() {
577 let bs = 1024u32; let plan = plan_contiguous(268, 1000, bs, make_alloc(50_000)).unwrap();
580 assert_eq!(plan.indirect_blocks_allocated, vec![50_000]);
581 assert_eq!(plan.block_writes.len(), 1);
582 let (_, ref buf) = plan.block_writes[0];
583 for i in 0..256 {
585 assert_eq!(read_u32_le(buf, i), 1000 + 12 + i as u32);
586 }
587 assert_eq!(read_u32_le(&plan.i_block, 13), 0);
589 }
590
591 #[test]
592 fn plan_contiguous_into_double_indirect() {
593 let bs = 1024u32; let plan = plan_contiguous(271, 2000, bs, make_alloc(100_000)).unwrap();
597 assert_eq!(
599 plan.indirect_blocks_allocated,
600 vec![100_000, 100_001, 100_002]
601 );
602 assert_eq!(read_u32_le(&plan.i_block, 12), 100_000);
603 assert_eq!(read_u32_le(&plan.i_block, 13), 100_001);
604 assert_eq!(read_u32_le(&plan.i_block, 14), 0);
605
606 let writes: std::collections::HashMap<u64, &Vec<u8>> =
608 plan.block_writes.iter().map(|(b, v)| (*b, v)).collect();
609 let single_buf = writes[&100_000];
611 for i in 0..256 {
612 assert_eq!(read_u32_le(single_buf, i), 2000 + 12 + i as u32);
613 }
614 let outer_buf = writes[&100_001];
616 assert_eq!(read_u32_le(outer_buf, 0), 100_002);
617 assert_eq!(read_u32_le(outer_buf, 1), 0);
618 let inner_buf = writes[&100_002];
620 for i in 0..3 {
621 assert_eq!(read_u32_le(inner_buf, i), 2000 + 268 + i as u32);
622 }
623 assert_eq!(read_u32_le(inner_buf, 3), 0);
624 }
625
626 #[test]
627 fn plan_contiguous_into_triple_indirect() {
628 let bs = 1024u32; let count = 65_805u32;
633 let plan = plan_contiguous(count, 500_000, bs, make_alloc(1_000_000)).unwrap();
634 assert_eq!(plan.indirect_blocks_allocated.len(), 1 + 1 + 256 + 3);
640 assert_ne!(read_u32_le(&plan.i_block, 14), 0);
641 }
642
643 #[test]
644 fn plan_contiguous_rejects_overflow_beyond_triple() {
645 let bs = 1024u32; let too_big = 12 + 256 + 65_536 + (256u32 * 256 * 256) + 1;
647 let result = plan_contiguous(too_big, 1, bs, make_alloc(1));
651 assert!(matches!(result, Err(Error::InvalidArgument(_))));
652 }
653
654 #[test]
655 fn count_indirect_blocks_matches_plan_contiguous() {
656 let bs = 1024u32;
660 for n in [
661 1u32,
662 11,
663 12,
664 13,
665 12 + 256,
666 12 + 257,
667 12 + 256 + 1,
668 271,
669 1000,
670 12 + 256 + 65_536,
671 65_805,
672 ] {
673 let predicted = count_indirect_blocks(n, bs);
674 let plan = plan_contiguous(n, 1, bs, make_alloc(1)).unwrap();
675 assert_eq!(
676 predicted as usize,
677 plan.indirect_blocks_allocated.len(),
678 "mismatch for n={n}: predicted={predicted}, actual={}",
679 plan.indirect_blocks_allocated.len()
680 );
681 }
682 }
683
684 #[test]
685 fn collect_for_free_walks_tree_and_coalesces_runs() {
686 let bs = 1024u32; let dev = MemDev::new(1024 * 4096);
688
689 let mut indirect = vec![0u8; bs as usize];
694 write_u32_le(&mut indirect, 0, 200);
695 write_u32_le(&mut indirect, 1, 201);
696 write_u32_le(&mut indirect, 2, 202);
697 dev.write_block(50, bs, &indirect);
698
699 let mut i_block = [0u8; 60];
700 for i in 0..5 {
701 write_u32_le(&mut i_block, i, 100 + i as u32);
702 }
703 write_u32_le(&mut i_block, 7, 999);
704 write_u32_le(&mut i_block, 12, 50);
705
706 let freed = collect_for_free(&i_block, bs, 15, &dev).unwrap();
708
709 assert_eq!(freed.indirect_blocks, vec![50]);
711
712 assert_eq!(
715 freed.data_runs,
716 vec![
717 FreeRun { start: 100, len: 5 },
718 FreeRun { start: 200, len: 3 },
719 FreeRun { start: 999, len: 1 },
720 ]
721 );
722 }
723
724 #[test]
725 fn collect_for_free_skips_sparse_holes() {
726 let bs = 1024u32;
727 let dev = MemDev::new(1024 * 64);
728 let mut i_block = [0u8; 60];
730 write_u32_le(&mut i_block, 0, 100);
731 write_u32_le(&mut i_block, 2, 102);
732
733 let freed = collect_for_free(&i_block, bs, 12, &dev).unwrap();
734 assert!(freed.indirect_blocks.is_empty());
735 assert_eq!(
737 freed.data_runs,
738 vec![
739 FreeRun { start: 100, len: 1 },
740 FreeRun { start: 102, len: 1 },
741 ]
742 );
743 }
744
745 #[test]
746 fn collect_for_free_zero_size_yields_nothing() {
747 let bs = 1024u32;
748 let dev = MemDev::new(1024 * 64);
749 let mut i_block = [0u8; 60];
750 for i in 0..12 {
752 write_u32_le(&mut i_block, i, 100 + i as u32);
753 }
754 let freed = collect_for_free(&i_block, bs, 0, &dev).unwrap();
755 assert!(freed.indirect_blocks.is_empty());
756 assert!(freed.data_runs.is_empty());
757 }
758
759 #[test]
760 fn round_trip_plan_then_collect() {
761 let bs = 1024u32; let count = 271u32; let plan = plan_contiguous(count, 5_000, bs, make_alloc(99_000)).unwrap();
767
768 let dev = MemDev::new(1024 * 200_000);
769 for (blk, buf) in &plan.block_writes {
770 dev.write_block(*blk, bs, buf);
771 }
772
773 let freed = collect_for_free(&plan.i_block, bs, count, &dev).unwrap();
774
775 let mut got_indirect = freed.indirect_blocks.clone();
777 let mut want_indirect = plan.indirect_blocks_allocated.clone();
778 got_indirect.sort_unstable();
779 want_indirect.sort_unstable();
780 assert_eq!(got_indirect, want_indirect);
781
782 assert_eq!(
784 freed.data_runs,
785 vec![FreeRun {
786 start: 5_000,
787 len: count
788 }]
789 );
790 }
791}