1use crate::bgd::{BgdFlags, BlockGroupDescriptor};
33use crate::error::{Error, Result};
34use crate::superblock::Superblock;
35
36#[derive(Debug, Clone, PartialEq, Eq)]
40pub struct BitmapWrite {
41 pub bitmap_block: u64,
44 pub bit_start: u32,
46 pub count: u32,
48 pub set: bool,
50}
51
52#[derive(Debug, Clone, PartialEq, Eq)]
55pub struct BgdCounterUpdate {
56 pub group_idx: u32,
57 pub free_blocks_delta: i32,
59 pub free_inodes_delta: i32,
61 pub used_dirs_delta: i32,
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub struct SuperblockCounterUpdate {
68 pub free_blocks_delta: i64,
69 pub free_inodes_delta: i32,
70}
71
72#[derive(Debug, Clone, PartialEq, Eq)]
74pub struct BlockAllocationPlan {
75 pub first_block: u64,
77 pub count: u32,
79 pub bitmap: BitmapWrite,
80 pub bgd: BgdCounterUpdate,
81 pub sb: SuperblockCounterUpdate,
82}
83
84#[derive(Debug, Clone, PartialEq, Eq)]
86pub struct InodeAllocationPlan {
87 pub inode: u32,
89 pub is_dir: bool,
91 pub bitmap: BitmapWrite,
92 pub bgd: BgdCounterUpdate,
93 pub sb: SuperblockCounterUpdate,
94}
95
96#[inline]
102pub fn bit_is_set(bitmap: &[u8], idx: u32) -> bool {
103 let byte = (idx / 8) as usize;
104 let mask = 1u8 << (idx % 8);
105 byte < bitmap.len() && bitmap[byte] & mask != 0
106}
107
108pub fn find_first_free(bitmap: &[u8], start: u32, max_bits: u32) -> Option<u32> {
116 let max_bits = max_bits.min(u32::try_from(bitmap.len().saturating_mul(8)).unwrap_or(u32::MAX));
128 if start >= max_bits {
129 return None;
130 }
131 let mut i = start;
132
133 while i < max_bits && !i.is_multiple_of(64) {
135 if !bit_is_set(bitmap, i) {
136 return Some(i);
137 }
138 i += 1;
139 }
140
141 while i + 64 <= max_bits {
145 let byte = (i as usize) / 8;
146 if byte + 8 > bitmap.len() {
147 break;
148 }
149 let word = u64::from_le_bytes(bitmap[byte..byte + 8].try_into().unwrap());
150 if word != u64::MAX {
151 let bit = word.trailing_ones();
152 let cand = i + bit;
153 if cand < max_bits {
155 return Some(cand);
156 }
157 return None;
158 }
159 i += 64;
160 }
161
162 while i < max_bits {
164 if !bit_is_set(bitmap, i) {
165 return Some(i);
166 }
167 i += 1;
168 }
169 None
170}
171
172pub fn find_free_run(bitmap: &[u8], start: u32, max_bits: u32, count: u32) -> Option<u32> {
183 if count == 0 {
184 return None;
185 }
186 let mut i = start;
187 while i + count <= max_bits {
188 let run_start = find_first_free(bitmap, i, max_bits)?;
191 if run_start + count > max_bits {
192 return None;
193 }
194 let mut j = run_start + 1;
197 while j < run_start + count && !bit_is_set(bitmap, j) {
198 j += 1;
199 }
200 if j - run_start >= count {
201 return Some(run_start);
202 }
203 i = j + 1;
205 }
206 None
207}
208
209pub fn plan_block_allocation<F>(
220 sb: &Superblock,
221 groups: &[BlockGroupDescriptor],
222 count: u32,
223 hint_group: u32,
224 mut bitmap_reader: F,
225) -> Result<BlockAllocationPlan>
226where
227 F: FnMut(u64) -> Result<Vec<u8>>,
228{
229 if count == 0 {
230 return Err(Error::Corrupt("plan_block_allocation: count == 0"));
231 }
232 let blocks_per_group = sb.blocks_per_group;
233 let ngroups = groups.len() as u32;
234 if ngroups == 0 {
235 return Err(Error::Corrupt("no block groups"));
236 }
237 let hint = hint_group.min(ngroups.saturating_sub(1));
238
239 for step in 0..ngroups {
240 let gi = (hint + step) % ngroups;
241 let bgd = &groups[gi as usize];
242 if bgd.free_blocks_count < count {
243 continue;
244 }
245 let max_bits = blocks_in_group(sb, gi);
248
249 let bitmap_bytes: Vec<u8> = if bgd.flags().contains(BgdFlags::BLOCK_UNINIT) {
250 vec![0u8; sb.block_size() as usize]
251 } else {
252 bitmap_reader(bgd.block_bitmap)?
253 };
254
255 let Some(bit_start) = find_free_run(&bitmap_bytes, 0, max_bits, count) else {
256 continue;
257 };
258
259 let group_first_block =
260 (gi as u64) * (blocks_per_group as u64) + sb.first_data_block as u64;
261 let first_block = group_first_block + bit_start as u64;
262
263 return Ok(BlockAllocationPlan {
264 first_block,
265 count,
266 bitmap: BitmapWrite {
267 bitmap_block: bgd.block_bitmap,
268 bit_start,
269 count,
270 set: true,
271 },
272 bgd: BgdCounterUpdate {
273 group_idx: gi,
274 free_blocks_delta: -(count as i32),
275 free_inodes_delta: 0,
276 used_dirs_delta: 0,
277 },
278 sb: SuperblockCounterUpdate {
279 free_blocks_delta: -(count as i64),
280 free_inodes_delta: 0,
281 },
282 });
283 }
284
285 Err(Error::Corrupt(
286 "no group has a contiguous free run of this size",
287 ))
288}
289
290fn blocks_in_group(sb: &Superblock, gi: u32) -> u32 {
293 let ngroups = sb.block_group_count() as u32;
294 if gi + 1 < ngroups {
295 return sb.blocks_per_group;
296 }
297 let remainder =
303 sb.blocks_count.saturating_sub(sb.first_data_block as u64) % sb.blocks_per_group as u64;
304 if remainder == 0 {
305 sb.blocks_per_group
306 } else {
307 remainder as u32
308 }
309}
310
311pub fn plan_inode_allocation<F>(
319 sb: &Superblock,
320 groups: &[BlockGroupDescriptor],
321 is_dir: bool,
322 hint_group: u32,
323 mut bitmap_reader: F,
324) -> Result<InodeAllocationPlan>
325where
326 F: FnMut(u64) -> Result<Vec<u8>>,
327{
328 let ngroups = groups.len() as u32;
329 if ngroups == 0 {
330 return Err(Error::Corrupt("no block groups"));
331 }
332
333 let start_group = if is_dir {
334 orlov_select_group(groups, hint_group)
335 } else {
336 hint_group.min(ngroups.saturating_sub(1))
337 };
338
339 for step in 0..ngroups {
340 let gi = (start_group + step) % ngroups;
341 let bgd = &groups[gi as usize];
342 if bgd.free_inodes_count == 0 {
343 continue;
344 }
345
346 let max_bits = sb.inodes_per_group;
347 let bitmap_bytes: Vec<u8> = if bgd.flags().contains(BgdFlags::INODE_UNINIT) {
348 vec![0u8; sb.block_size() as usize]
349 } else {
350 bitmap_reader(bgd.inode_bitmap)?
351 };
352
353 let floor = if gi == 0 {
364 sb.first_inode.saturating_sub(1)
365 } else {
366 0
367 };
368 let Some(bit_start) = find_first_free(&bitmap_bytes, floor, max_bits) else {
369 continue;
370 };
371
372 let inode = u64::from(gi)
378 .checked_mul(u64::from(sb.inodes_per_group))
379 .and_then(|base| base.checked_add(u64::from(bit_start) + 1))
380 .filter(|n| *n <= u64::from(sb.inodes_count))
381 .ok_or(Error::Corrupt(
382 "the group's inode range does not fit in the filesystem",
383 ))? as u32;
384
385 return Ok(InodeAllocationPlan {
386 inode,
387 is_dir,
388 bitmap: BitmapWrite {
389 bitmap_block: bgd.inode_bitmap,
390 bit_start,
391 count: 1,
392 set: true,
393 },
394 bgd: BgdCounterUpdate {
395 group_idx: gi,
396 free_blocks_delta: 0,
397 free_inodes_delta: -1,
398 used_dirs_delta: if is_dir { 1 } else { 0 },
399 },
400 sb: SuperblockCounterUpdate {
401 free_blocks_delta: 0,
402 free_inodes_delta: -1,
403 },
404 });
405 }
406
407 Err(Error::Corrupt("no group has a free inode"))
408}
409
410fn orlov_select_group(groups: &[BlockGroupDescriptor], hint: u32) -> u32 {
414 let ngroups = groups.len() as u32;
415 if ngroups == 0 {
416 return 0;
417 }
418 let hint = hint.min(ngroups.saturating_sub(1));
419
420 let total_free_inodes: u64 = groups.iter().map(|g| g.free_inodes_count as u64).sum();
421 let total_used_dirs: u64 = groups.iter().map(|g| g.used_dirs_count as u64).sum();
422 let avg_free_inodes = total_free_inodes / ngroups as u64;
423 let avg_used_dirs = total_used_dirs / ngroups as u64;
424
425 let mut best: Option<u32> = None;
429 let mut best_score: i64 = i64::MIN;
430 for step in 0..ngroups {
431 let gi = (hint + step) % ngroups;
432 let g = &groups[gi as usize];
433 let fi = g.free_inodes_count as i64;
434 let ud = g.used_dirs_count as i64;
435 let mut score = fi - ud;
437 if fi >= avg_free_inodes as i64 {
438 score += 1000;
439 }
440 if ud <= avg_used_dirs as i64 {
441 score += 500;
442 }
443 if score > best_score && g.free_inodes_count > 0 {
444 best_score = score;
445 best = Some(gi);
446 }
447 }
448 best.unwrap_or(hint)
449}
450
451pub fn apply_bitmap_write(buf: &mut [u8], w: &BitmapWrite) {
458 for b in 0..w.count {
459 let idx = (w.bit_start + b) as usize;
460 let byte = idx / 8;
461 let mask = 1u8 << (idx % 8);
462 if byte >= buf.len() {
463 break;
464 }
465 if w.set {
466 buf[byte] |= mask;
467 } else {
468 buf[byte] &= !mask;
469 }
470 }
471}
472
473#[cfg(test)]
474mod tests {
475 use super::*;
476
477 fn mk_sb(
478 block_size: u32,
479 blocks_per_group: u32,
480 inodes_per_group: u32,
481 total_blocks: u64,
482 ) -> Superblock {
483 let mut raw = vec![0u8; crate::superblock::SUPERBLOCK_SIZE];
485 raw[0x38..0x3A].copy_from_slice(&(crate::superblock::EXT4_MAGIC).to_le_bytes());
487 raw[0x00..0x04].copy_from_slice(&(inodes_per_group * 4).to_le_bytes());
488 raw[0x04..0x08].copy_from_slice(&(total_blocks as u32).to_le_bytes());
489 raw[0x14..0x18].copy_from_slice(&1u32.to_le_bytes()); raw[0x18..0x1C]
491 .copy_from_slice(&(block_size.trailing_zeros().saturating_sub(10)).to_le_bytes());
492 raw[0x20..0x24].copy_from_slice(&blocks_per_group.to_le_bytes());
493 raw[0x28..0x2C].copy_from_slice(&inodes_per_group.to_le_bytes());
494 raw[0x4C..0x50].copy_from_slice(&1u32.to_le_bytes()); raw[0x58..0x5A].copy_from_slice(&256u16.to_le_bytes()); raw[0xFE..0x100].copy_from_slice(&64u16.to_le_bytes()); crate::superblock::Superblock::parse(raw).unwrap()
498 }
499
500 fn mk_bgd(
501 free_blocks: u32,
502 free_inodes: u32,
503 used_dirs: u32,
504 flags: u16,
505 ) -> BlockGroupDescriptor {
506 BlockGroupDescriptor {
507 block_bitmap: 100,
508 inode_bitmap: 200,
509 inode_table: 300,
510 free_blocks_count: free_blocks,
511 free_inodes_count: free_inodes,
512 used_dirs_count: used_dirs,
513 flags,
514 itable_unused: 0,
515 block_bitmap_csum: 0,
516 inode_bitmap_csum: 0,
517 checksum: 0,
518 }
519 }
520
521 #[test]
532 fn a_bit_outside_the_bitmap_is_not_a_free_bit() {
533 let full = vec![0xFFu8];
536 assert_eq!(find_first_free(&full, 0, 1 << 31), None);
537 let partly = vec![0x0Fu8];
539 assert_eq!(find_first_free(&partly, 0, 1 << 31), Some(4));
540 assert_eq!(find_first_free(&partly, 8, 1 << 31), None);
541 assert_eq!(find_first_free(&[], 0, 1 << 31), None);
543 }
544
545 #[test]
552 fn a_reserved_inode_is_never_allocated() {
553 let sb = mk_sb(1024, 8192, 128, 1024);
554 let groups = vec![mk_bgd(100, 128, 0, BgdFlags::INODE_UNINIT.bits())];
557 assert_eq!(
558 sb.first_inode,
559 crate::superblock::GOOD_OLD_FIRST_INODE,
560 "the fixture superblock leaves s_first_ino zero; the floor comes from the parser"
561 );
562 let plan = plan_inode_allocation(&sb, &groups, false, 0, |_| {
563 panic!("an uninitialised group is not read")
564 })
565 .expect("a group with free inodes");
566 assert!(
567 plan.inode >= sb.first_inode,
568 "allocated inode {} , where the first non-reserved one is {} -- \
569 writing it puts the new file's inode over the root directory's",
570 plan.inode,
571 sb.first_inode
572 );
573
574 let groups = vec![
576 mk_bgd(100, 0, 0, 0),
577 mk_bgd(100, 128, 0, BgdFlags::INODE_UNINIT.bits()),
578 ];
579 let plan = plan_inode_allocation(&sb, &groups, false, 1, |_| unreachable!()).unwrap();
580 assert_eq!(plan.inode, 128 + 1);
581 }
582
583 #[test]
584 fn find_first_free_walks_bits() {
585 let buf = vec![0xFF, 0x0F, 0x00]; assert_eq!(find_first_free(&buf, 0, 24), Some(12));
587 assert_eq!(find_first_free(&buf, 20, 24), Some(20));
588 }
589
590 #[test]
591 fn find_first_free_word_aligned_fast_path() {
592 let mut buf = vec![0xFFu8; 8];
594 buf.extend_from_slice(&[0xFFu8; 2]); buf.push(0x00); buf.extend_from_slice(&[0xFFu8; 5]);
597 assert_eq!(find_first_free(&buf, 0, 128), Some(80));
598 }
599
600 #[test]
601 fn find_first_free_all_ones_in_range() {
602 let buf = vec![0xFFu8; 32]; assert_eq!(find_first_free(&buf, 0, 256), None);
605 assert_eq!(find_first_free(&buf, 63, 256), None);
606 assert_eq!(find_first_free(&buf, 64, 256), None);
607 }
608
609 #[test]
610 fn find_first_free_respects_max_bits_mid_word() {
611 let mut buf = vec![0xFFu8; 8]; buf.push(0x00); buf.extend_from_slice(&[0xFFu8; 7]);
615 assert_eq!(find_first_free(&buf, 0, 65), Some(64));
616 assert_eq!(find_first_free(&buf, 0, 64), None);
617 }
618
619 #[test]
620 fn find_first_free_unaligned_start_matches_per_bit() {
621 let buf: Vec<u8> = (0..128u8).collect(); let max = (buf.len() as u32) * 8;
624 for start in [0u32, 1, 7, 8, 63, 64, 65, 127, 200, 511] {
625 let fast = find_first_free(&buf, start, max);
626 let slow = {
627 let mut i = start;
628 loop {
629 if i >= max {
630 break None;
631 }
632 if !bit_is_set(&buf, i) {
633 break Some(i);
634 }
635 i += 1;
636 }
637 };
638 assert_eq!(fast, slow, "start={start}");
639 }
640 }
641
642 #[test]
643 fn find_free_run_handles_gaps() {
644 let buf = vec![0b0000_0011, 0xFF, 0x00];
646 assert_eq!(find_free_run(&buf, 0, 24, 5), Some(2));
648 assert_eq!(find_free_run(&buf, 0, 24, 7), Some(16));
650 }
651
652 #[test]
653 fn find_free_run_exact_fit() {
654 let buf = vec![0x00];
655 assert_eq!(find_free_run(&buf, 0, 8, 8), Some(0));
656 }
657
658 #[test]
659 fn find_free_run_rejects_too_short() {
660 let buf = vec![0xFE]; assert_eq!(find_free_run(&buf, 0, 8, 2), None);
662 }
663
664 #[test]
665 fn block_allocation_uses_first_group_with_room() {
666 let sb = mk_sb(4096, 32768, 8192, 65536);
667 let g0 = mk_bgd(100, 8000, 0, 0);
668 let g1 = mk_bgd(20000, 8000, 0, 0);
669 let groups = vec![g0, g1];
670 let read = |_block: u64| -> Result<Vec<u8>> { Ok(vec![0u8; 4096]) };
671 let plan = plan_block_allocation(&sb, &groups, 10, 0, read).unwrap();
672 assert_eq!(plan.first_block, 1); assert_eq!(plan.count, 10);
674 assert_eq!(plan.bgd.group_idx, 0);
675 assert_eq!(plan.bgd.free_blocks_delta, -10);
676 assert_eq!(plan.sb.free_blocks_delta, -10);
677 }
678
679 #[test]
680 fn block_allocation_skips_full_group() {
681 let sb = mk_sb(4096, 32768, 8192, 65536);
682 let g0 = mk_bgd(5, 8000, 0, 0); let g1 = mk_bgd(20000, 8000, 0, 0);
684 let groups = vec![g0, g1];
685 let read = |_b| Ok(vec![0u8; 4096]);
686 let plan = plan_block_allocation(&sb, &groups, 10, 0, read).unwrap();
687 assert_eq!(plan.bgd.group_idx, 1);
688 assert_eq!(plan.first_block, 1 + 32768);
690 }
691
692 #[test]
693 fn block_allocation_honours_block_uninit_flag() {
694 let sb = mk_sb(4096, 32768, 8192, 65536);
695 let g0 = mk_bgd(32768, 8000, 0, BgdFlags::BLOCK_UNINIT.bits());
697 let groups = vec![g0];
698 let mut call_count = 0;
699 let read = |_b| {
700 call_count += 1;
701 Ok(vec![0xFFu8; 4096])
702 };
703 let plan = plan_block_allocation(&sb, &groups, 4, 0, read).unwrap();
704 assert_eq!(plan.count, 4);
705 assert_eq!(call_count, 0, "UNINIT group should not read bitmap");
706 }
707
708 #[test]
717 fn inode_allocation_returns_one_based_number() {
718 let sb = mk_sb(4096, 32768, 8192, 65536);
719 let g0 = mk_bgd(1000, 8000, 0, 0);
720 let groups = vec![g0];
721 let read = |_b| Ok(vec![0u8; 4096]);
722 let plan = plan_inode_allocation(&sb, &groups, false, 0, read).unwrap();
723 assert_eq!(
724 plan.inode, sb.first_inode,
725 "group 0's first available inode is s_first_ino, not inode 1"
726 );
727 assert_eq!(plan.bitmap.bit_start, sb.first_inode - 1, "one-based");
728 assert!(!plan.is_dir);
729 assert_eq!(plan.bgd.used_dirs_delta, 0);
730 }
731
732 #[test]
733 fn inode_allocation_dir_bumps_used_dirs() {
734 let sb = mk_sb(4096, 32768, 8192, 65536);
735 let g0 = mk_bgd(1000, 8000, 0, 0);
736 let groups = vec![g0];
737 let read = |_b| Ok(vec![0u8; 4096]);
738 let plan = plan_inode_allocation(&sb, &groups, true, 0, read).unwrap();
739 assert!(plan.is_dir);
740 assert_eq!(plan.bgd.used_dirs_delta, 1);
741 assert_eq!(plan.bgd.free_inodes_delta, -1);
742 }
743
744 #[test]
745 fn orlov_prefers_group_with_fewer_dirs() {
746 let groups = vec![
748 mk_bgd(100, 500, 30, 0),
749 mk_bgd(100, 1000, 2, 0),
750 mk_bgd(100, 800, 10, 0),
751 ];
752 assert_eq!(orlov_select_group(&groups, 0), 1);
753 }
754
755 #[test]
756 fn apply_bitmap_write_sets_and_clears_bits() {
757 let mut buf = vec![0u8; 2];
758 apply_bitmap_write(
759 &mut buf,
760 &BitmapWrite {
761 bitmap_block: 0,
762 bit_start: 0,
763 count: 10,
764 set: true,
765 },
766 );
767 assert_eq!(buf, vec![0xFF, 0x03]);
768 apply_bitmap_write(
769 &mut buf,
770 &BitmapWrite {
771 bitmap_block: 0,
772 bit_start: 5,
773 count: 3,
774 set: false,
775 },
776 );
777 assert_eq!(buf, vec![0b0001_1111, 0x03]);
778 }
779
780 #[test]
783 fn blocks_in_group_full_groups_return_blocks_per_group() {
784 let sb = mk_sb(4096, 32768, 8192, 3 * 32768 + 1);
786 assert_eq!(blocks_in_group(&sb, 0), 32768);
788 assert_eq!(blocks_in_group(&sb, 1), 32768);
789 }
790
791 #[test]
792 fn blocks_in_group_last_group_exact_multiple_returns_full() {
793 let sb = mk_sb(4096, 32768, 8192, 2 * 32768 + 1);
796 assert_eq!(blocks_in_group(&sb, 1), 32768);
797 }
798
799 #[test]
800 fn blocks_in_group_short_last_group() {
801 let sb = mk_sb(4096, 32768, 8192, 32769 + 100);
803 assert_eq!(blocks_in_group(&sb, 0), 32768); assert_eq!(blocks_in_group(&sb, 1), 100); }
806}