fs_ext4/alloc.rs
1//! Block + inode bitmap allocator — planning layer.
2//!
3//! Phase 4 write path scaffolding. This module produces typed
4//! [`AllocationPlan`] values describing what bits to flip in which bitmap
5//! block + the updated free-counter deltas. It does NOT write to disk;
6//! E11 (journaled writes) will apply the plans atomically under a
7//! JBD2 transaction.
8//!
9//! Rationale: separating allocation (pure function over bitmap bytes) from
10//! commit (journaled block write) makes tests trivial and keeps the
11//! read-only mount path untouched. Block device traits stay read-only in
12//! Phase 1; the write trait lives at the commit boundary.
13//!
14//! ### Block bitmap layout
15//! One bit per block in the group. Bit `i` = block `group_start + i`.
16//! A 4 KiB block bitmap covers `32768` blocks (one block group on a
17//! 4 KiB-block fs). Bits are packed LSB-first within each byte: bit 0 of
18//! byte 0 represents the first block in the group.
19//!
20//! ### Inode bitmap layout
21//! Same LSB-first packing. Bit `i` = inode `(group_idx * inodes_per_group) + i + 1`
22//! (inode numbers are 1-based).
23//!
24//! ### Orlov allocator (directories)
25//! Linux ext4 chooses a group for new directories using the Orlov heuristic:
26//! prefer groups whose `(free_blocks, free_inodes, used_dirs)` triple is
27//! "below average" — distributing directories evenly across groups so sibling
28//! files end up near their parent dir. We implement a simplified variant:
29//! iterate groups starting from `hint`, prefer one whose used_dirs is below
30//! the fleet average and has the most free_inodes.
31
32use crate::bgd::{BgdFlags, BlockGroupDescriptor};
33use crate::error::{Error, Result};
34use crate::superblock::Superblock;
35
36/// A change to one bitmap block: flip bits `bit_start .. bit_start + count`
37/// from 0 (free) to 1 (used). The new bitmap bytes are NOT materialised here —
38/// only the semantic description is.
39#[derive(Debug, Clone, PartialEq, Eq)]
40pub struct BitmapWrite {
41 /// Physical block number of the bitmap (from `bg_block_bitmap` or
42 /// `bg_inode_bitmap`).
43 pub bitmap_block: u64,
44 /// First bit index within this bitmap to flip.
45 pub bit_start: u32,
46 /// Number of consecutive bits to flip.
47 pub count: u32,
48 /// `true` if marking used, `false` if freeing.
49 pub set: bool,
50}
51
52/// A change to one block-group descriptor's free-counter and/or
53/// used_dirs_count. Applied together with the matching [`BitmapWrite`].
54#[derive(Debug, Clone, PartialEq, Eq)]
55pub struct BgdCounterUpdate {
56 pub group_idx: u32,
57 /// Delta to apply to `bg_free_blocks_count` (+free, -allocated).
58 pub free_blocks_delta: i32,
59 /// Delta to apply to `bg_free_inodes_count`.
60 pub free_inodes_delta: i32,
61 /// Delta to apply to `bg_used_dirs_count`.
62 pub used_dirs_delta: i32,
63}
64
65/// A change to the superblock free-counter totals.
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub struct SuperblockCounterUpdate {
68 pub free_blocks_delta: i64,
69 pub free_inodes_delta: i32,
70}
71
72/// Complete plan for one block-allocation request.
73#[derive(Debug, Clone, PartialEq, Eq)]
74pub struct BlockAllocationPlan {
75 /// First allocated fs block (absolute, not group-relative).
76 pub first_block: u64,
77 /// Number of contiguous blocks allocated.
78 pub count: u32,
79 pub bitmap: BitmapWrite,
80 pub bgd: BgdCounterUpdate,
81 pub sb: SuperblockCounterUpdate,
82}
83
84/// Complete plan for one inode-allocation request.
85#[derive(Debug, Clone, PartialEq, Eq)]
86pub struct InodeAllocationPlan {
87 /// Allocated inode number (1-based).
88 pub inode: u32,
89 /// True if this was a directory allocation (also bumps used_dirs).
90 pub is_dir: bool,
91 pub bitmap: BitmapWrite,
92 pub bgd: BgdCounterUpdate,
93 pub sb: SuperblockCounterUpdate,
94}
95
96// ---------------------------------------------------------------------------
97// Pure bit-manipulation helpers — unit-testable without a device
98// ---------------------------------------------------------------------------
99
100/// Test bit `idx` in a bitmap (LSB-first within each byte).
101#[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
108/// Find the first free (0-valued) bit at or after `start`, searching up to
109/// `max_bits` total. Returns `None` if none found.
110///
111/// Fast path: once `start` is aligned to an 8-byte word, we scan the bitmap
112/// as `u64`s and skip any word of all-ones in a single branch. On sparse
113/// bitmaps (typical after mkfs) the scan is effectively memory-bandwidth
114/// bound and ~8–16× faster than per-bit `bit_is_set`.
115pub fn find_first_free(bitmap: &[u8], start: u32, max_bits: u32) -> Option<u32> {
116 // A BIT THAT IS NOT IN THE BITMAP IS NOT A FREE BIT.
117 //
118 // `bit_is_set` answers "not set" for an index past the end of the
119 // buffer, which reads as free, and `max_bits` comes from
120 // `s_blocks_per_group` / `s_inodes_per_group` -- superblock fields
121 // that nothing bounds to what a bitmap block can hold. So a group
122 // claiming 2^31 inodes per group returned bit indices that are not
123 // in its bitmap at all: the plan named a real inode or block, and
124 // the write that was supposed to mark it used silently did nothing
125 // while the counters were debited anyway. The next allocation then
126 // returned the same one.
127 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 // 1) Scan to the next 64-bit-aligned bit boundary with the per-bit path.
134 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 // 2) Word-at-a-time scan. Every word that is not `u64::MAX` has at least
142 // one zero bit; `trailing_ones` pinpoints the first one in LSB order
143 // (matching ext4's LSB-first within-byte convention).
144 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 // Guard against spurious max_bits boundary within the word.
154 if cand < max_bits {
155 return Some(cand);
156 }
157 return None;
158 }
159 i += 64;
160 }
161
162 // 3) Tail — any remaining bits below `max_bits` go through the per-bit path.
163 while i < max_bits {
164 if !bit_is_set(bitmap, i) {
165 return Some(i);
166 }
167 i += 1;
168 }
169 None
170}
171
172/// Find the first run of `count` consecutive free bits at or after `start`,
173/// within the first `max_bits` bits of the bitmap. Returns the starting bit
174/// index of the run, or `None` if no such run exists.
175///
176/// Phase 8.3 vectorization: the outer "find a starting candidate" step
177/// uses [`find_first_free`] (u64-stride skip over fully-used regions),
178/// then the run-length verification walks bit-at-a-time. On sparse
179/// bitmaps (typical post-mkfs) this is effectively memory-bandwidth
180/// bound; on densely-packed bitmaps it skips fully-used 64-bit words in
181/// one branch instead of 64.
182pub fn find_free_run(bitmap: &[u8], start: u32, max_bits: u32, count: u32) -> Option<u32> {
183 if count == 0 {
184 return None;
185 }
186 // THE RUN ENDS INSIDE THE BITMAP (#321). `bit_is_set` reports a bit
187 // past the end as free, so a `max_bits` beyond the bitmap -- a
188 // `blocks_per_group` over `8 * block_size` -- let a free tail run on
189 // into bits that do not exist, and the plan into the next group's
190 // blocks. `find_first_free` clamps the same way; the run's end must
191 // be judged against that clamp, not the raw value.
192 let max_bits = max_bits.min(u32::try_from(bitmap.len().saturating_mul(8)).unwrap_or(u32::MAX));
193 let mut i = start;
194 while i.checked_add(count).is_some_and(|end| end <= max_bits) {
195 // Vectorized: jump straight to the next free bit at-or-after `i`,
196 // skipping all-ones words 64 bits at a time.
197 let run_start = find_first_free(bitmap, i, max_bits)?;
198 let run_end = run_start
199 .checked_add(count)
200 .filter(|&end| end <= max_bits)?;
201 // Verify `count` contiguous free bits — bit-at-a-time, since the
202 // blocker (if any) almost always sits within the first few bits.
203 let mut j = run_start + 1;
204 while j < run_end && !bit_is_set(bitmap, j) {
205 j += 1;
206 }
207 if j == run_end {
208 return Some(run_start);
209 }
210 // Hit a used bit before reaching `count`; skip past it and retry.
211 i = j.checked_add(1)?;
212 }
213 None
214}
215
216// ---------------------------------------------------------------------------
217// Block allocator (E5)
218// ---------------------------------------------------------------------------
219
220/// Plan allocation of `count` contiguous blocks.
221///
222/// `bitmap_reader` is called with a BGD's `bg_block_bitmap` and must return
223/// the full bitmap block (block_size bytes). Groups are tried in order:
224/// hint_group first, then wrapping forward. For groups flagged
225/// `BLOCK_UNINIT`, the bitmap is treated as all-free without reading.
226pub fn plan_block_allocation<F>(
227 sb: &Superblock,
228 groups: &[BlockGroupDescriptor],
229 count: u32,
230 hint_group: u32,
231 bitmap_reader: F,
232) -> Result<BlockAllocationPlan>
233where
234 F: FnMut(u64) -> Result<Vec<u8>>,
235{
236 plan_block_allocation_excluding(sb, groups, count, hint_group, &[], bitmap_reader)
237}
238
239/// Set the bits of `reserved` that fall inside group `gi`.
240///
241/// The blocks are absolute; a group's bit `n` is block
242/// `gi * blocks_per_group + s_first_data_block + n`. Anything outside
243/// this group, or past the bits the group actually has, is skipped
244/// rather than wrapped -- a reservation in another group is not this
245/// group's business, and `blocks_in_group` is the ceiling the scan
246/// already honours.
247fn mark_reserved_in_group(
248 sb: &Superblock,
249 gi: u32,
250 max_bits: u32,
251 reserved: &[u64],
252 bitmap: &mut [u8],
253) {
254 if reserved.is_empty() {
255 return;
256 }
257 let group_first_block = (gi as u64) * (sb.blocks_per_group as u64) + sb.first_data_block as u64;
258 for &block in reserved {
259 let Some(offset) = block.checked_sub(group_first_block) else {
260 continue;
261 };
262 if offset >= max_bits as u64 {
263 continue;
264 }
265 let bit = offset as usize;
266 let byte = bit / 8;
267 if byte < bitmap.len() {
268 bitmap[byte] |= 1u8 << (bit % 8);
269 }
270 }
271}
272
273/// Every block a transaction has already spoken for: the data plan's
274/// whole run, plus the whole run of every meta plan handed out so far.
275///
276/// THE LIST IS A FUNCTION BECAUSE THE INLINE VERSION WAS UNWITNESSED.
277/// Three separate reverts of the inline code — dropping the data run,
278/// no-oping the meta push, and keeping only `first_block` instead of the
279/// whole `count` run — each left 278 library tests green and EXIT=0.
280/// At the time nothing in the suite reached `buffer_extend_dir_deep`,
281/// so the three lines that make the fix were held by nothing. Extracted
282/// here they are three assertions instead. Since 3973ce0 (#215),
283/// `tests/deep_dir_extent_nodes_are_distinct.rs` also drives a directory
284/// through the deep path and checks the result end to end, in the tier
285/// that runs e2fsck; these assertions remain the unit-tier witness.
286///
287/// A PLAN IS A RUN, NOT A BLOCK. `count` is 1 for every caller today,
288/// which is exactly why `first_block` alone passed every test that
289/// existed: the day a directory data page is planned as two contiguous
290/// blocks, a `first_block`-only reservation hands the second one out
291/// again as a tree node.
292pub(crate) fn reserved_blocks(
293 data: &BlockAllocationPlan,
294 handed_out: &[BlockAllocationPlan],
295) -> Vec<u64> {
296 std::iter::once(data)
297 .chain(handed_out)
298 .flat_map(|plan| (0..u64::from(plan.count)).map(move |i| plan.first_block + i))
299 .collect()
300}
301
302/// [`plan_block_allocation`], with blocks that are SPOKEN FOR BUT NOT YET
303/// COMMITTED treated as used.
304///
305/// # WHY THE PLANNER HAS TO BE TOLD, RATHER THAN ITS ANSWER FILTERED
306///
307/// A caller that plans several allocations before committing any of
308/// them -- which is the late-commit ordering the write paths use so a
309/// failure half way through leaks no blocks -- shows this function the
310/// same unchanged bitmap every time, so it returns the same block every
311/// time. Measured on a fresh 64 MiB image, three consecutive plans
312/// against an uncommitted bitmap: `517 517 517`.
313///
314/// The caller that hit this defended itself with one equality test
315/// against one reserved block, which is wrong twice: it refuses with
316/// `NoSpaceLeftOnDevice` on a nearly empty filesystem when the block
317/// does match, and when it does not it lets the SECOND meta block alias
318/// the first, because the test never looked at the ones it had already
319/// handed out. Two extent-tree nodes on one physical block is silent
320/// corruption; the refusal is at least loud.
321///
322/// EXTENDING THE EQUALITY TEST TO THE WHOLE LIST IS NOT THE FIX. The
323/// planner would go on returning the same block, so the list would just
324/// turn the aliasing into a second spurious refusal. The reservations
325/// have to be in the bitmap the scan reads.
326///
327/// # THE OVERLAY CANNOT LIVE IN THE CALLER'S READER
328///
329/// The obvious smaller change -- have the caller's `bitmap_reader`
330/// return bytes with the reserved bits already set -- is not enough,
331/// because a `BLOCK_UNINIT` group never calls the reader at all: the
332/// bitmap is fabricated as all-free right here. That is exactly the
333/// state a freshly formatted image is in, which is where this is most
334/// likely to bite. So the marking happens after the bitmap is obtained,
335/// on both paths.
336pub fn plan_block_allocation_excluding<F>(
337 sb: &Superblock,
338 groups: &[BlockGroupDescriptor],
339 count: u32,
340 hint_group: u32,
341 reserved: &[u64],
342 mut bitmap_reader: F,
343) -> Result<BlockAllocationPlan>
344where
345 F: FnMut(u64) -> Result<Vec<u8>>,
346{
347 if count == 0 {
348 return Err(Error::Corrupt("plan_block_allocation: count == 0"));
349 }
350 let blocks_per_group = sb.blocks_per_group;
351 let ngroups = groups.len() as u32;
352 if ngroups == 0 {
353 return Err(Error::Corrupt("no block groups"));
354 }
355 let hint = hint_group.min(ngroups.saturating_sub(1));
356
357 for step in 0..ngroups {
358 let gi = (hint + step) % ngroups;
359 let bgd = &groups[gi as usize];
360 if bgd.free_blocks_count < count {
361 continue;
362 }
363 // Compute how many blocks are actually valid in this group (last group
364 // may be short).
365 let max_bits = blocks_in_group(sb, gi);
366
367 let mut bitmap_bytes: Vec<u8> = if bgd.flags().contains(BgdFlags::BLOCK_UNINIT) {
368 uninit_block_bitmap(sb, groups, gi)
369 } else {
370 bitmap_reader(bgd.block_bitmap)?
371 };
372 mark_reserved_in_group(sb, gi, max_bits, reserved, &mut bitmap_bytes);
373
374 let Some(bit_start) = find_free_run(&bitmap_bytes, 0, max_bits, count) else {
375 continue;
376 };
377
378 let group_first_block =
379 (gi as u64) * (blocks_per_group as u64) + sb.first_data_block as u64;
380 let first_block = group_first_block + bit_start as u64;
381
382 return Ok(BlockAllocationPlan {
383 first_block,
384 count,
385 bitmap: BitmapWrite {
386 bitmap_block: bgd.block_bitmap,
387 bit_start,
388 count,
389 set: true,
390 },
391 bgd: BgdCounterUpdate {
392 group_idx: gi,
393 free_blocks_delta: -(count as i32),
394 free_inodes_delta: 0,
395 used_dirs_delta: 0,
396 },
397 sb: SuperblockCounterUpdate {
398 free_blocks_delta: -(count as i64),
399 free_inodes_delta: 0,
400 },
401 });
402 }
403
404 Err(Error::Corrupt(
405 "no group has a contiguous free run of this size",
406 ))
407}
408
409// ---------------------------------------------------------------------------
410// The bitmap a BLOCK_UNINIT group stands for
411// ---------------------------------------------------------------------------
412//
413// A group flagged BLOCK_UNINIT has no block bitmap on disk worth reading
414// (blockgroup.html, "Lazy Block Group Initialization": the bitmap "can be
415// calculated"). bitmaps.html warns that uninitialised is not the same as
416// empty. What is in use is the group's fixed-location metadata:
417//
418// - at its head, when it carries a superblock copy or a META_BG descriptor
419// copy, those blocks and (outside META_BG) the reserved GDT blocks --
420// `Superblock::group_head_metadata_blocks`;
421// - its own block bitmap, inode bitmap and inode table, but only where the
422// descriptor puts them inside this group. Under flex_bg they usually sit
423// in another group, and blocks this group holds for *other* groups'
424// bitmaps are not counted: `dumpe2fs` on a flex group leader forced to
425// BLOCK_UNINIT lists them as free.
426//
427// Bits past the group's last block (a bitmap block has 8 * block_size of
428// them) are set, as every bitmap the tools write sets them; e2fsck
429// reports "Padding at end of block bitmap is not set" otherwise.
430//
431// tests in `needs_host` below compare this against `dumpe2fs` for every
432// BLOCK_UNINIT group of mkfs.ext4 volumes with and without flex_bg and
433// META_BG, at 1 KiB and 4 KiB.
434
435/// Group-relative block ranges that a `BLOCK_UNINIT` group has in use.
436pub(crate) fn uninit_group_used_ranges(
437 sb: &Superblock,
438 groups: &[BlockGroupDescriptor],
439 gi: usize,
440) -> Vec<std::ops::Range<u64>> {
441 let group_first = sb.first_data_block as u64 + gi as u64 * sb.blocks_per_group as u64;
442 let group_len = u64::from(blocks_in_group(sb, gi as u32));
443 let mut used = Vec::new();
444
445 let head = sb.group_head_metadata_blocks(gi as u64);
446 if head > 0 {
447 used.push(0..head.min(group_len));
448 }
449
450 if let Some(desc) = groups.get(gi) {
451 let table_len = (u64::from(sb.inodes_per_group) * u64::from(sb.inode_size))
452 .div_ceil(u64::from(sb.block_size()));
453 for (start, len) in [
454 (desc.block_bitmap, 1),
455 (desc.inode_bitmap, 1),
456 (desc.inode_table, table_len),
457 ] {
458 let Some(relative) = start.checked_sub(group_first) else {
459 continue;
460 };
461 if relative < group_len {
462 used.push(relative..(relative + len).min(group_len));
463 }
464 }
465 }
466 used
467}
468
469/// The whole block bitmap a `BLOCK_UNINIT` group stands for: the ranges
470/// of [`uninit_group_used_ranges`] and the padding past the group's end
471/// set, everything else clear. The planner and fsck read this instead of
472/// the unwritten bitmap block (#391), and the first allocation into the
473/// group writes it out.
474pub(crate) fn uninit_block_bitmap(
475 sb: &Superblock,
476 groups: &[BlockGroupDescriptor],
477 gi: u32,
478) -> Vec<u8> {
479 let mut bitmap = vec![0u8; sb.block_size() as usize];
480 let capacity = bitmap.len() as u64 * 8;
481 let padding = u64::from(blocks_in_group(sb, gi)).min(capacity)..capacity;
482 for range in uninit_group_used_ranges(sb, groups, gi as usize)
483 .into_iter()
484 .chain(std::iter::once(padding))
485 {
486 for bit in range.start.min(capacity)..range.end.min(capacity) {
487 bitmap[(bit / 8) as usize] |= 1 << (bit % 8);
488 }
489 }
490 bitmap
491}
492
493/// Returns the number of blocks that actually exist in group `gi` (the last
494/// group may be shorter than `blocks_per_group`).
495pub(crate) fn blocks_in_group(sb: &Superblock, gi: u32) -> u32 {
496 let ngroups = sb.block_group_count() as u32;
497 if gi + 1 < ngroups {
498 return sb.blocks_per_group;
499 }
500 // Saturating, though `Superblock::parse` now refuses a
501 // `s_first_data_block` at or past `blocks_count` (#181). Before that
502 // bound a larger value wrapped this subtraction, which made the last
503 // group's bit ceiling far larger than the group, so the allocator
504 // handed out blocks outside the filesystem. A `Superblock` built some
505 // other way than `parse` still gets the safe answer.
506 let remainder =
507 sb.blocks_count.saturating_sub(sb.first_data_block as u64) % sb.blocks_per_group as u64;
508 if remainder == 0 {
509 sb.blocks_per_group
510 } else {
511 remainder as u32
512 }
513}
514
515// ---------------------------------------------------------------------------
516// Inode allocator (E6)
517// ---------------------------------------------------------------------------
518
519/// Plan allocation of a single inode. For directories, uses a simplified
520/// Orlov heuristic to spread dirs across groups; for regular files, prefers
521/// the `hint_group` (typically the parent directory's group).
522pub fn plan_inode_allocation<F>(
523 sb: &Superblock,
524 groups: &[BlockGroupDescriptor],
525 is_dir: bool,
526 hint_group: u32,
527 mut bitmap_reader: F,
528) -> Result<InodeAllocationPlan>
529where
530 F: FnMut(u64) -> Result<Vec<u8>>,
531{
532 let ngroups = groups.len() as u32;
533 if ngroups == 0 {
534 return Err(Error::Corrupt("no block groups"));
535 }
536
537 let start_group = if is_dir {
538 orlov_select_group(groups, hint_group)
539 } else {
540 hint_group.min(ngroups.saturating_sub(1))
541 };
542
543 for step in 0..ngroups {
544 let gi = (start_group + step) % ngroups;
545 let bgd = &groups[gi as usize];
546 if bgd.free_inodes_count == 0 {
547 continue;
548 }
549
550 let max_bits = sb.inodes_per_group;
551 let bitmap_bytes: Vec<u8> = if bgd.flags().contains(BgdFlags::INODE_UNINIT) {
552 vec![0u8; sb.block_size() as usize]
553 } else {
554 bitmap_reader(bgd.inode_bitmap)?
555 };
556
557 // THE RESERVED INODES ARE NOT AVAILABLE.
558 //
559 // Inodes below `s_first_ino` are the filesystem's own: 2 is the
560 // root directory, 8 the journal. On a filesystem mke2fs wrote
561 // their bits are set, so scanning from zero skipped them by
562 // accident -- but the bitmap is bytes off the disk, and one
563 // with those bits clear (or a group 0 flagged INODE_UNINIT,
564 // which is read as an all-zero bitmap without being read at
565 // all) handed out inode 2. `apply_create` then writes the new
566 // file's inode image over the root directory's.
567 //
568 // The reserved range is inode NUMBERS, so it reaches past group 0
569 // whenever `s_first_ino` exceeds `inodes_per_group + 1`: with eight
570 // inodes a group, 9 and 10 are group 1's first bits (#327). The
571 // floor is the reserved range less the inodes before this group,
572 // in u64 because `gi * inodes_per_group` can pass u32.
573 let before_group = u64::from(gi) * u64::from(sb.inodes_per_group);
574 let floor = u64::from(sb.first_inode.saturating_sub(1)).saturating_sub(before_group);
575 let floor = u32::try_from(floor).unwrap_or(u32::MAX);
576 let Some(bit_start) = find_first_free(&bitmap_bytes, floor, max_bits) else {
577 continue;
578 };
579
580 // Inode numbers are 1-based: first inode in group 0 is inode 1.
581 // Checked, because `inodes_per_group` is a superblock field and
582 // the product wrapped in release -- a group-2 allocation came
583 // back as inode 2 and was written over the root inode while
584 // group 2's counters were debited.
585 let inode = u64::from(gi)
586 .checked_mul(u64::from(sb.inodes_per_group))
587 .and_then(|base| base.checked_add(u64::from(bit_start) + 1))
588 .filter(|n| *n <= u64::from(sb.inodes_count))
589 .ok_or(Error::Corrupt(
590 "the group's inode range does not fit in the filesystem",
591 ))? as u32;
592
593 return Ok(InodeAllocationPlan {
594 inode,
595 is_dir,
596 bitmap: BitmapWrite {
597 bitmap_block: bgd.inode_bitmap,
598 bit_start,
599 count: 1,
600 set: true,
601 },
602 bgd: BgdCounterUpdate {
603 group_idx: gi,
604 free_blocks_delta: 0,
605 free_inodes_delta: -1,
606 used_dirs_delta: if is_dir { 1 } else { 0 },
607 },
608 sb: SuperblockCounterUpdate {
609 free_blocks_delta: 0,
610 free_inodes_delta: -1,
611 },
612 });
613 }
614
615 Err(Error::Corrupt("no group has a free inode"))
616}
617
618/// Orlov group selection (simplified). Chooses the group among the ngroups
619/// starting at `hint` that currently has the fewest directories AND at least
620/// average free inodes. If no group is clearly "good", falls back to `hint`.
621fn orlov_select_group(groups: &[BlockGroupDescriptor], hint: u32) -> u32 {
622 let ngroups = groups.len() as u32;
623 if ngroups == 0 {
624 return 0;
625 }
626 let hint = hint.min(ngroups.saturating_sub(1));
627
628 let total_free_inodes: u64 = groups.iter().map(|g| g.free_inodes_count as u64).sum();
629 let total_used_dirs: u64 = groups.iter().map(|g| g.used_dirs_count as u64).sum();
630 let avg_free_inodes = total_free_inodes / ngroups as u64;
631 let avg_used_dirs = total_used_dirs / ngroups as u64;
632
633 // Walk all groups from hint and pick the first that has more free inodes
634 // than the average AND fewer used dirs than the average. Fall back to the
635 // group with the most free inodes overall.
636 let mut best: Option<u32> = None;
637 let mut best_score: i64 = i64::MIN;
638 for step in 0..ngroups {
639 let gi = (hint + step) % ngroups;
640 let g = &groups[gi as usize];
641 let fi = g.free_inodes_count as i64;
642 let ud = g.used_dirs_count as i64;
643 // Score: bonus if above avg inodes and below avg dirs.
644 let mut score = fi - ud;
645 if fi >= avg_free_inodes as i64 {
646 score += 1000;
647 }
648 if ud <= avg_used_dirs as i64 {
649 score += 500;
650 }
651 if score > best_score && g.free_inodes_count > 0 {
652 best_score = score;
653 best = Some(gi);
654 }
655 }
656 best.unwrap_or(hint)
657}
658
659// ---------------------------------------------------------------------------
660// Plan application helpers — pure functions that mutate caller-owned buffers.
661// The actual disk writes happen in E11 (journaled writes).
662// ---------------------------------------------------------------------------
663
664/// Apply a [`BitmapWrite`] to a bitmap buffer in place.
665pub fn apply_bitmap_write(buf: &mut [u8], w: &BitmapWrite) {
666 for b in 0..w.count {
667 let idx = (w.bit_start + b) as usize;
668 let byte = idx / 8;
669 let mask = 1u8 << (idx % 8);
670 if byte >= buf.len() {
671 break;
672 }
673 if w.set {
674 buf[byte] |= mask;
675 } else {
676 buf[byte] &= !mask;
677 }
678 }
679}
680
681#[cfg(test)]
682mod tests {
683 use super::*;
684
685 // --- reserved_blocks -------------------------------------------
686 //
687 // THESE THREE ASSERTIONS ARE THE FIX. Before the extraction the
688 // same three decisions were inline in
689 // `Filesystem::buffer_extend_dir_deep`, and each could be
690 // reverted with 278 library tests green, EXIT=0, 0 compile errors:
691 // dropping the data run, no-oping the meta push, and reserving only
692 // `first_block` instead of the whole `count` run. Nothing in the
693 // suite reached that function then; since 3973ce0 (#215)
694 // `tests/deep_dir_extent_nodes_are_distinct.rs` does, in the e2fsck
695 // tier, and these stay as the unit-tier witness.
696
697 fn mk_plan(first_block: u64, count: u32) -> BlockAllocationPlan {
698 BlockAllocationPlan {
699 first_block,
700 count,
701 bitmap: BitmapWrite {
702 bitmap_block: 0,
703 bit_start: 0,
704 count,
705 set: true,
706 },
707 bgd: BgdCounterUpdate {
708 group_idx: 0,
709 free_blocks_delta: -(count as i32),
710 free_inodes_delta: 0,
711 used_dirs_delta: 0,
712 },
713 sb: SuperblockCounterUpdate {
714 free_blocks_delta: -(count as i64),
715 free_inodes_delta: 0,
716 },
717 }
718 }
719
720 /// THE DATA PAGE IS SPOKEN FOR. Nothing has reached the bitmap yet,
721 /// so a planner told nothing returns the data block again — and on
722 /// the first call it does exactly that, because that is the block it
723 /// returned for the data page moments earlier.
724 #[test]
725 fn the_data_plan_is_reserved() {
726 assert_eq!(reserved_blocks(&mk_plan(517, 1), &[]), vec![517]);
727 }
728
729 /// A PLAN IS A RUN, NOT A BLOCK. `count` is 1 for every caller
730 /// today, which is precisely why reserving `first_block` alone
731 /// passed every test that existed. The day a directory data page is
732 /// planned as two contiguous blocks, that version hands the second
733 /// one out again as a tree node.
734 #[test]
735 fn a_multi_block_plan_reserves_its_whole_run() {
736 assert_eq!(
737 reserved_blocks(&mk_plan(100, 3), &[]),
738 vec![100, 101, 102],
739 "reserving only first_block leaves 101 and 102 free to be handed out again"
740 );
741 assert_eq!(
742 reserved_blocks(&mk_plan(100, 1), &[mk_plan(200, 4)]),
743 vec![100, 200, 201, 202, 203],
744 "a handed-out meta plan contributes its whole run too"
745 );
746 }
747
748 /// THE SECOND CALL MUST SEE THE FIRST CALL'S ANSWER. Without this
749 /// the planner reads the same unchanged bitmap and returns the same
750 /// block, it passes into `pending_meta` a second time, and two
751 /// extent-tree nodes share one physical block — silent corruption,
752 /// which is the worse of the two consequences.
753 #[test]
754 fn every_meta_plan_handed_out_so_far_is_reserved() {
755 let data = mk_plan(517, 1);
756 let first = reserved_blocks(&data, &[]);
757 assert_eq!(first, vec![517]);
758
759 let handed_out = vec![mk_plan(518, 1)];
760 let second = reserved_blocks(&data, &handed_out);
761 assert!(
762 second.contains(&518),
763 "the block the first call was given must not be offered again: {second:?}"
764 );
765 assert!(
766 second.contains(&517),
767 "and the data block stays reserved across calls: {second:?}"
768 );
769
770 let handed_out = vec![mk_plan(518, 1), mk_plan(519, 1)];
771 assert_eq!(reserved_blocks(&data, &handed_out), vec![517, 518, 519]);
772 }
773
774 fn mk_sb(
775 block_size: u32,
776 blocks_per_group: u32,
777 inodes_per_group: u32,
778 total_blocks: u64,
779 ) -> Superblock {
780 // Minimal superblock constructed from a synthetic buffer.
781 let mut raw = vec![0u8; crate::superblock::SUPERBLOCK_SIZE];
782 // magic
783 raw[0x38..0x3A].copy_from_slice(&(crate::superblock::EXT4_MAGIC).to_le_bytes());
784 raw[0x00..0x04].copy_from_slice(&(inodes_per_group * 4).to_le_bytes());
785 raw[0x04..0x08].copy_from_slice(&(total_blocks as u32).to_le_bytes());
786 raw[0x14..0x18].copy_from_slice(&1u32.to_le_bytes()); // first_data_block
787 raw[0x18..0x1C]
788 .copy_from_slice(&(block_size.trailing_zeros().saturating_sub(10)).to_le_bytes());
789 raw[0x20..0x24].copy_from_slice(&blocks_per_group.to_le_bytes());
790 raw[0x28..0x2C].copy_from_slice(&inodes_per_group.to_le_bytes());
791 raw[0x4C..0x50].copy_from_slice(&1u32.to_le_bytes()); // rev_level=1 so inode_size read
792 raw[0x58..0x5A].copy_from_slice(&256u16.to_le_bytes()); // inode_size
793 raw[0xFE..0x100].copy_from_slice(&64u16.to_le_bytes()); // desc_size
794 crate::superblock::Superblock::parse(raw).unwrap()
795 }
796
797 fn mk_bgd(
798 free_blocks: u32,
799 free_inodes: u32,
800 used_dirs: u32,
801 flags: u16,
802 ) -> BlockGroupDescriptor {
803 BlockGroupDescriptor {
804 block_bitmap: 100,
805 inode_bitmap: 200,
806 inode_table: 300,
807 free_blocks_count: free_blocks,
808 free_inodes_count: free_inodes,
809 used_dirs_count: used_dirs,
810 flags,
811 itable_unused: 0,
812 block_bitmap_csum: 0,
813 inode_bitmap_csum: 0,
814 checksum: 0,
815 }
816 }
817
818 /// `bit_is_set` answers "not set" for an index past the end of the
819 /// buffer, which reads as free -- and `max_bits` comes from
820 /// `s_blocks_per_group` / `s_inodes_per_group`, superblock fields
821 /// that nothing bounds to what a bitmap block can hold.
822 ///
823 /// The plan then names a real inode or block while the write that
824 /// marks it used silently does nothing (the byte is past the
825 /// buffer), and the counters are debited anyway. The next
826 /// allocation returns the same one: every file on the same block,
827 /// every new inode over the last.
828 #[test]
829 fn a_bit_outside_the_bitmap_is_not_a_free_bit() {
830 // One byte of bitmap, every bit used, but a group claiming
831 // 2^31 bits.
832 let full = vec![0xFFu8];
833 assert_eq!(find_first_free(&full, 0, 1 << 31), None);
834 // And with room in the byte, only the bits that are there.
835 let partly = vec![0x0Fu8];
836 assert_eq!(find_first_free(&partly, 0, 1 << 31), Some(4));
837 assert_eq!(find_first_free(&partly, 8, 1 << 31), None);
838 // An empty bitmap has no free bits, however many are claimed.
839 assert_eq!(find_first_free(&[], 0, 1 << 31), None);
840 }
841
842 /// Inodes below `s_first_ino` are the filesystem's own: 2 is the
843 /// root directory, 8 the journal. On a filesystem mke2fs wrote,
844 /// their bits are set, so scanning from zero skipped them by
845 /// accident -- but the bitmap is bytes off the disk, and a group 0
846 /// flagged INODE_UNINIT is read as an all-zero bitmap without being
847 /// read at all.
848 #[test]
849 fn a_reserved_inode_is_never_allocated() {
850 let sb = mk_sb(1024, 8192, 128, 1024);
851 // Group 0 declared uninitialised, so its bitmap is taken as
852 // all-free without a device read.
853 let groups = vec![mk_bgd(100, 128, 0, BgdFlags::INODE_UNINIT.bits())];
854 assert_eq!(
855 sb.first_inode,
856 crate::superblock::GOOD_OLD_FIRST_INODE,
857 "the fixture superblock leaves s_first_ino zero; the floor comes from the parser"
858 );
859 let plan = plan_inode_allocation(&sb, &groups, false, 0, |_| {
860 panic!("an uninitialised group is not read")
861 })
862 .expect("a group with free inodes");
863 assert!(
864 plan.inode >= sb.first_inode,
865 "allocated inode {} , where the first non-reserved one is {} -- \
866 writing it puts the new file's inode over the root directory's",
867 plan.inode,
868 sb.first_inode
869 );
870
871 // Group 1 has no reserved inodes, so it starts at its first bit.
872 let groups = vec![
873 mk_bgd(100, 0, 0, 0),
874 mk_bgd(100, 128, 0, BgdFlags::INODE_UNINIT.bits()),
875 ];
876 let plan = plan_inode_allocation(&sb, &groups, false, 1, |_| unreachable!()).unwrap();
877 assert_eq!(plan.inode, 128 + 1);
878 }
879
880 /// The reserved range is inode numbers, not group 0's bits: with
881 /// eight inodes a group and `s_first_ino` 11, group 0 is all reserved
882 /// and group 1's first two (9 and 10) are too. The floor applied to
883 /// group 0 only, so group 1 handed out inode 9 (#327).
884 #[test]
885 fn the_reserved_range_reaches_past_group_0() {
886 let sb = mk_sb(1024, 8192, 8, 1024);
887 assert_eq!((sb.inodes_per_group, sb.first_inode), (8, 11));
888 let groups = vec![
889 mk_bgd(100, 0, 0, 0),
890 mk_bgd(100, 8, 0, BgdFlags::INODE_UNINIT.bits()),
891 ];
892 let plan = plan_inode_allocation(&sb, &groups, false, 0, |_| {
893 panic!("group 0 is full and group 1 uninitialised: nothing is read")
894 })
895 .expect("group 1 has free inodes");
896 assert_eq!(
897 plan.inode, 11,
898 "inodes 9 and 10 are below s_first_ino, in group 1"
899 );
900 assert_eq!((plan.bgd.group_idx, plan.bitmap.bit_start), (1, 2));
901
902 // A group wholly below s_first_ino has nothing to give.
903 let groups = vec![
904 mk_bgd(100, 0, 0, 0),
905 mk_bgd(100, 8, 0, BgdFlags::INODE_UNINIT.bits()),
906 ];
907 let mut sb = sb;
908 sb.first_inode = 17;
909 let plan = plan_inode_allocation(&sb, &groups, false, 1, |_| unreachable!());
910 assert!(plan.is_err(), "inodes 9..=16 are all reserved: {plan:?}");
911 }
912
913 #[test]
914 fn find_first_free_walks_bits() {
915 let buf = vec![0xFF, 0x0F, 0x00]; // bits 0..11 set, 12..23 free
916 assert_eq!(find_first_free(&buf, 0, 24), Some(12));
917 assert_eq!(find_first_free(&buf, 20, 24), Some(20));
918 }
919
920 #[test]
921 fn find_first_free_word_aligned_fast_path() {
922 // 16 bytes = 128 bits. First 64 bits all set; bit 80 is the first free.
923 let mut buf = vec![0xFFu8; 8];
924 buf.extend_from_slice(&[0xFFu8; 2]); // bits 64..79 set
925 buf.push(0x00); // bit 80..87 free → first free is 80
926 buf.extend_from_slice(&[0xFFu8; 5]);
927 assert_eq!(find_first_free(&buf, 0, 128), Some(80));
928 }
929
930 #[test]
931 fn find_first_free_all_ones_in_range() {
932 // Whole range is allocated; must return None without overflow.
933 let buf = vec![0xFFu8; 32]; // 256 bits, all set
934 assert_eq!(find_first_free(&buf, 0, 256), None);
935 assert_eq!(find_first_free(&buf, 63, 256), None);
936 assert_eq!(find_first_free(&buf, 64, 256), None);
937 }
938
939 #[test]
940 fn find_first_free_respects_max_bits_mid_word() {
941 // Two zero bits starting at 64; max_bits caps at 65 → bit 64 valid, 65 out.
942 let mut buf = vec![0xFFu8; 8]; // bits 0..63 set
943 buf.push(0x00); // bits 64..71 free
944 buf.extend_from_slice(&[0xFFu8; 7]);
945 assert_eq!(find_first_free(&buf, 0, 65), Some(64));
946 assert_eq!(find_first_free(&buf, 0, 64), None);
947 }
948
949 #[test]
950 fn find_first_free_unaligned_start_matches_per_bit() {
951 // Reference: every result must agree with the simple per-bit implementation.
952 let buf: Vec<u8> = (0..128u8).collect(); // mixed pattern
953 let max = (buf.len() as u32) * 8;
954 for start in [0u32, 1, 7, 8, 63, 64, 65, 127, 200, 511] {
955 let fast = find_first_free(&buf, start, max);
956 let slow = {
957 let mut i = start;
958 loop {
959 if i >= max {
960 break None;
961 }
962 if !bit_is_set(&buf, i) {
963 break Some(i);
964 }
965 i += 1;
966 }
967 };
968 assert_eq!(fast, slow, "start={start}");
969 }
970 }
971
972 #[test]
973 fn find_free_run_handles_gaps() {
974 // byte 0: bits 0,1 set; bits 2..=7 free. Byte 1: all set. Byte 2: all free.
975 let buf = vec![0b0000_0011, 0xFF, 0x00];
976 // Shortest run: bits 2..=7 = run of 6. A request for 5 fits at bit 2.
977 assert_eq!(find_free_run(&buf, 0, 24, 5), Some(2));
978 // A request for 7 cannot fit in bits 2..7 (only 6 free) — next run is byte 2.
979 assert_eq!(find_free_run(&buf, 0, 24, 7), Some(16));
980 }
981
982 #[test]
983 fn find_free_run_exact_fit() {
984 let buf = vec![0x00];
985 assert_eq!(find_free_run(&buf, 0, 8, 8), Some(0));
986 }
987
988 #[test]
989 fn find_free_run_rejects_too_short() {
990 let buf = vec![0xFE]; // bit 0 free, bits 1..7 used
991 assert_eq!(find_free_run(&buf, 0, 8, 2), None);
992 }
993
994 /// A run is never found past the end of the bitmap (#321).
995 /// `bit_is_set` reports a bit past the end as free, so a `max_bits`
996 /// larger than the bitmap -- a `blocks_per_group` over
997 /// `8 * block_size` -- let a free tail extend into bits that do not
998 /// exist, and the plan's blocks into the next group's.
999 #[test]
1000 fn find_free_run_stops_at_the_end_of_the_bitmap() {
1001 assert_eq!(find_free_run(&[0], 0, 100, 9), None);
1002 assert_eq!(find_free_run(&[0], 0, 100, 8), Some(0));
1003 assert_eq!(find_free_run(&[0x0F], 0, u32::MAX, 5), None);
1004 assert_eq!(find_free_run(&[0], u32::MAX - 1, u32::MAX, 4), None);
1005 }
1006
1007 #[test]
1008 fn block_allocation_uses_first_group_with_room() {
1009 let sb = mk_sb(4096, 32768, 8192, 65536);
1010 let g0 = mk_bgd(100, 8000, 0, 0);
1011 let g1 = mk_bgd(20000, 8000, 0, 0);
1012 let groups = vec![g0, g1];
1013 let read = |_block: u64| -> Result<Vec<u8>> { Ok(vec![0u8; 4096]) };
1014 let plan = plan_block_allocation(&sb, &groups, 10, 0, read).unwrap();
1015 assert_eq!(plan.first_block, 1); // first_data_block=1, group 0 bit 0
1016 assert_eq!(plan.count, 10);
1017 assert_eq!(plan.bgd.group_idx, 0);
1018 assert_eq!(plan.bgd.free_blocks_delta, -10);
1019 assert_eq!(plan.sb.free_blocks_delta, -10);
1020 }
1021
1022 #[test]
1023 fn block_allocation_skips_full_group() {
1024 let sb = mk_sb(4096, 32768, 8192, 65536);
1025 let g0 = mk_bgd(5, 8000, 0, 0); // not enough for 10-block run
1026 let g1 = mk_bgd(20000, 8000, 0, 0);
1027 let groups = vec![g0, g1];
1028 let read = |_b| Ok(vec![0u8; 4096]);
1029 let plan = plan_block_allocation(&sb, &groups, 10, 0, read).unwrap();
1030 assert_eq!(plan.bgd.group_idx, 1);
1031 // Block 1 + group1_offset
1032 assert_eq!(plan.first_block, 1 + 32768);
1033 }
1034
1035 /// THE DEFECT, AND ITS CONTROL IN THE SAME TEST.
1036 ///
1037 /// A caller that plans several allocations before committing any of
1038 /// them shows the planner the same bitmap every time. Without
1039 /// reservations the planner is right to return the same block every
1040 /// time -- the bitmap says it is free -- which is why the fix is to
1041 /// tell it, not to filter its answer.
1042 #[test]
1043 fn consecutive_plans_repeat_without_reservations_and_advance_with_them() {
1044 let sb = mk_sb(4096, 32768, 8192, 65536);
1045 let groups = vec![mk_bgd(32768, 8000, 0, 0)];
1046 let read = |_b| Ok(vec![0u8; 4096]);
1047
1048 // The uncommitted bitmap, three times over: identical answers.
1049 let unreserved: Vec<u64> = (0..3)
1050 .map(|_| {
1051 plan_block_allocation(&sb, &groups, 1, 0, read)
1052 .unwrap()
1053 .first_block
1054 })
1055 .collect();
1056 assert_eq!(
1057 unreserved,
1058 vec![1, 1, 1],
1059 "nothing was committed, so the scan sees the same bytes each time"
1060 );
1061
1062 // The same three, each told what the previous ones took.
1063 let mut reserved: Vec<u64> = Vec::new();
1064 let mut handed_out: Vec<u64> = Vec::new();
1065 for _ in 0..3 {
1066 let plan =
1067 plan_block_allocation_excluding(&sb, &groups, 1, 0, &reserved, read).unwrap();
1068 reserved.push(plan.first_block);
1069 handed_out.push(plan.first_block);
1070 }
1071 assert_eq!(
1072 handed_out,
1073 vec![1, 2, 3],
1074 "each plan must avoid the blocks the ones before it took"
1075 );
1076 }
1077
1078 /// A MULTI-BLOCK RESERVATION IS RESERVED WHOLE.
1079 ///
1080 /// `count` is not always 1: the data-page plan this was written for
1081 /// can cover a run, and reserving only its first block would let the
1082 /// next plan land inside it.
1083 #[test]
1084 fn a_reserved_run_is_skipped_entirely() {
1085 let sb = mk_sb(4096, 32768, 8192, 65536);
1086 let groups = vec![mk_bgd(32768, 8000, 0, 0)];
1087 let read = |_b| Ok(vec![0u8; 4096]);
1088 let reserved: Vec<u64> = (1..=4).collect();
1089 let plan = plan_block_allocation_excluding(&sb, &groups, 1, 0, &reserved, read).unwrap();
1090 assert_eq!(plan.first_block, 5, "blocks 1..=4 are spoken for");
1091 }
1092
1093 /// THE CASE AN OVERLAY IN THE CALLER'S READER CANNOT REACH.
1094 ///
1095 /// A `BLOCK_UNINIT` group never calls the bitmap reader -- the
1096 /// bitmap is fabricated as all-free -- so a caller that set the bits
1097 /// on the bytes it returns would have no effect here at all. That is
1098 /// the state a freshly formatted image is in.
1099 #[test]
1100 fn a_reservation_is_honoured_in_an_uninit_group() {
1101 let sb = mk_sb(4096, 32768, 8192, 65536);
1102 let groups = vec![mk_bgd(32768, 8000, 0, BgdFlags::BLOCK_UNINIT.bits())];
1103 let mut call_count = 0;
1104 let read = |_b| {
1105 call_count += 1;
1106 Ok(vec![0u8; 4096])
1107 };
1108 // Group 0's superblock and one GDT block are blocks 1 and 2, so 3
1109 // is the first block the implied bitmap leaves free.
1110 let plan = plan_block_allocation_excluding(&sb, &groups, 1, 0, &[3], read).unwrap();
1111 assert_eq!(plan.first_block, 4, "block 3 is reserved");
1112 assert_eq!(call_count, 0, "an UNINIT group still reads no bitmap");
1113 }
1114
1115 /// An uninit group is not an empty one. Its backup superblock, GDT and
1116 /// reserved GDT blocks, and the bitmaps and inode table the descriptor
1117 /// places inside it, are all in use in the bitmap the flag implies.
1118 /// Planning against all zeroes handed the backup superblock out first.
1119 #[test]
1120 fn an_uninit_group_keeps_its_own_metadata() {
1121 let mut sb = mk_sb(4096, 32768, 8192, 65536);
1122 sb.reserved_gdt_blocks = 3;
1123 let g0 = mk_bgd(0, 8000, 0, 0);
1124 let mut g1 = mk_bgd(32768, 8000, 0, BgdFlags::BLOCK_UNINIT.bits());
1125 // Group 1 starts at block 32769. Superblock, one GDT block and three
1126 // reserved blocks take 32769..=32773; put the bitmaps right after and
1127 // the inode table (8192 * 256 / 4096 = 512 blocks) after those.
1128 g1.block_bitmap = 32774;
1129 g1.inode_bitmap = 32775;
1130 g1.inode_table = 32776;
1131 let groups = vec![g0, g1];
1132 let read = |_b| Ok(vec![0u8; 4096]);
1133 let plan = plan_block_allocation(&sb, &groups, 1, 1, read).unwrap();
1134 assert_eq!(
1135 plan.first_block,
1136 32776 + 512,
1137 "first block past the group's metadata"
1138 );
1139
1140 // The ranges themselves, relative to the group start.
1141 let used = uninit_group_used_ranges(&sb, &groups, 1);
1142 assert_eq!(used, vec![0..5, 5..6, 6..7, 7..519]);
1143 }
1144
1145 /// THE ACCEPTANCE HALF: a reservation is a reservation of one block
1146 /// in one group, not a blanket refusal.
1147 ///
1148 /// A reservation belonging to another group, or past the bits the
1149 /// group actually has, must not narrow this group's scan -- an
1150 /// over-eager mask would refuse allocations on a filesystem with
1151 /// room, which is the failure the old equality test already had.
1152 #[test]
1153 fn a_reservation_outside_this_group_does_not_narrow_it() {
1154 let sb = mk_sb(4096, 32768, 8192, 65536);
1155 let groups = vec![mk_bgd(32768, 8000, 0, 0), mk_bgd(32768, 8000, 0, 0)];
1156 let read = |_b| Ok(vec![0u8; 4096]);
1157 // Group 1's first block, and a block past the end of the volume.
1158 let reserved = [1 + 32768, 999_999];
1159 let plan = plan_block_allocation_excluding(&sb, &groups, 1, 0, &reserved, read).unwrap();
1160 assert_eq!(
1161 plan.first_block, 1,
1162 "neither reservation is in group 0, so group 0 is untouched"
1163 );
1164 // And the one that IS in group 1 still applies there.
1165 let full_g0 = vec![mk_bgd(0, 8000, 0, 0), mk_bgd(32768, 8000, 0, 0)];
1166 let plan = plan_block_allocation_excluding(&sb, &full_g0, 1, 0, &reserved, read).unwrap();
1167 assert_eq!(
1168 plan.first_block,
1169 2 + 32768,
1170 "group 0 has no room, and group 1's first block is reserved"
1171 );
1172 }
1173
1174 /// `plan_block_allocation` is `plan_block_allocation_excluding` with
1175 /// nothing reserved, and must stay exactly that.
1176 #[test]
1177 fn the_unreserved_wrapper_agrees_with_the_general_form() {
1178 let sb = mk_sb(4096, 32768, 8192, 65536);
1179 let groups = vec![mk_bgd(5, 8000, 0, 0), mk_bgd(20000, 8000, 0, 0)];
1180 let read = |_b| Ok(vec![0u8; 4096]);
1181 let a = plan_block_allocation(&sb, &groups, 10, 0, read).unwrap();
1182 let b = plan_block_allocation_excluding(&sb, &groups, 10, 0, &[], read).unwrap();
1183 assert_eq!(a, b);
1184 }
1185
1186 #[test]
1187 fn block_allocation_honours_block_uninit_flag() {
1188 let sb = mk_sb(4096, 32768, 8192, 65536);
1189 // group 0 is UNINIT → treated as all-free without reading bitmap
1190 let g0 = mk_bgd(32768, 8000, 0, BgdFlags::BLOCK_UNINIT.bits());
1191 let groups = vec![g0];
1192 let mut call_count = 0;
1193 let read = |_b| {
1194 call_count += 1;
1195 Ok(vec![0xFFu8; 4096])
1196 };
1197 let plan = plan_block_allocation(&sb, &groups, 4, 0, read).unwrap();
1198 assert_eq!(plan.count, 4);
1199 assert_eq!(call_count, 0, "UNINIT group should not read bitmap");
1200 }
1201
1202 /// Inode numbers are one-based, and group 0's first *available*
1203 /// one is `s_first_ino`.
1204 ///
1205 /// This used to assert inode 1, on an all-free bitmap that no real
1206 /// filesystem has: mke2fs sets the reserved bits, so scanning from
1207 /// zero returned 11 anyway and the assertion only held for the
1208 /// synthetic bitmap here. It is the reserved range the allocator
1209 /// must not enter, not the bits that happen to be set.
1210 #[test]
1211 fn inode_allocation_returns_one_based_number() {
1212 let sb = mk_sb(4096, 32768, 8192, 65536);
1213 let g0 = mk_bgd(1000, 8000, 0, 0);
1214 let groups = vec![g0];
1215 let read = |_b| Ok(vec![0u8; 4096]);
1216 let plan = plan_inode_allocation(&sb, &groups, false, 0, read).unwrap();
1217 assert_eq!(
1218 plan.inode, sb.first_inode,
1219 "group 0's first available inode is s_first_ino, not inode 1"
1220 );
1221 assert_eq!(plan.bitmap.bit_start, sb.first_inode - 1, "one-based");
1222 assert!(!plan.is_dir);
1223 assert_eq!(plan.bgd.used_dirs_delta, 0);
1224 }
1225
1226 #[test]
1227 fn inode_allocation_dir_bumps_used_dirs() {
1228 let sb = mk_sb(4096, 32768, 8192, 65536);
1229 let g0 = mk_bgd(1000, 8000, 0, 0);
1230 let groups = vec![g0];
1231 let read = |_b| Ok(vec![0u8; 4096]);
1232 let plan = plan_inode_allocation(&sb, &groups, true, 0, read).unwrap();
1233 assert!(plan.is_dir);
1234 assert_eq!(plan.bgd.used_dirs_delta, 1);
1235 assert_eq!(plan.bgd.free_inodes_delta, -1);
1236 }
1237
1238 #[test]
1239 fn orlov_prefers_group_with_fewer_dirs() {
1240 // g1 has fewer dirs and more free inodes — should win the Orlov beauty contest.
1241 let groups = vec![
1242 mk_bgd(100, 500, 30, 0),
1243 mk_bgd(100, 1000, 2, 0),
1244 mk_bgd(100, 800, 10, 0),
1245 ];
1246 assert_eq!(orlov_select_group(&groups, 0), 1);
1247 }
1248
1249 #[test]
1250 fn apply_bitmap_write_sets_and_clears_bits() {
1251 let mut buf = vec![0u8; 2];
1252 apply_bitmap_write(
1253 &mut buf,
1254 &BitmapWrite {
1255 bitmap_block: 0,
1256 bit_start: 0,
1257 count: 10,
1258 set: true,
1259 },
1260 );
1261 assert_eq!(buf, vec![0xFF, 0x03]);
1262 apply_bitmap_write(
1263 &mut buf,
1264 &BitmapWrite {
1265 bitmap_block: 0,
1266 bit_start: 5,
1267 count: 3,
1268 set: false,
1269 },
1270 );
1271 assert_eq!(buf, vec![0b0001_1111, 0x03]);
1272 }
1273
1274 // --- blocks_in_group ---
1275
1276 #[test]
1277 fn blocks_in_group_full_groups_return_blocks_per_group() {
1278 // 3 full groups of 32768 each: total = 3*32768 + 1 (first_data_block=1).
1279 let sb = mk_sb(4096, 32768, 8192, 3 * 32768 + 1);
1280 // Groups 0 and 1 are not the last group, so they return blocks_per_group.
1281 assert_eq!(blocks_in_group(&sb, 0), 32768);
1282 assert_eq!(blocks_in_group(&sb, 1), 32768);
1283 }
1284
1285 #[test]
1286 fn blocks_in_group_last_group_exact_multiple_returns_full() {
1287 // usable = 2*32768, first_data_block=1 → blocks_count = 2*32768+1
1288 // remainder = (2*32768+1-1) % 32768 = 65536 % 32768 = 0 → full group
1289 let sb = mk_sb(4096, 32768, 8192, 2 * 32768 + 1);
1290 assert_eq!(blocks_in_group(&sb, 1), 32768);
1291 }
1292
1293 #[test]
1294 fn blocks_in_group_short_last_group() {
1295 // 1 full group + 100 extra blocks: total = 32768 + 100 + 1 = 32869
1296 let sb = mk_sb(4096, 32768, 8192, 32769 + 100);
1297 assert_eq!(blocks_in_group(&sb, 0), 32768); // full
1298 assert_eq!(blocks_in_group(&sb, 1), 100); // short last group
1299 }
1300}
1301
1302/// [`uninit_block_bitmap`] against `dumpe2fs`, which lists every group's
1303/// free blocks, BLOCK_UNINIT ones included, from its own reconstruction.
1304#[cfg(test)]
1305mod uninit_bitmap_oracle {
1306 mod needs_host {
1307 use crate::alloc::{blocks_in_group, uninit_block_bitmap};
1308 use crate::bgd::BgdFlags;
1309 use crate::block_io::FileDevice;
1310 use crate::fs::Filesystem;
1311 use std::collections::BTreeMap;
1312 use std::sync::Arc;
1313
1314 /// Group number -> its "Free blocks:" list, as absolute ranges.
1315 fn dumpe2fs_free(image: &str) -> BTreeMap<usize, Vec<(u64, u64)>> {
1316 let out = fs_ext4_test_support::oracle("dumpe2fs")
1317 .arg(image)
1318 .judged()
1319 .clean("dumpe2fs");
1320 let text = String::from_utf8_lossy(&out.stdout).into_owned();
1321 let mut free = BTreeMap::new();
1322 let mut group = None;
1323 for line in text.lines() {
1324 if let Some(rest) = line.strip_prefix("Group ") {
1325 group = rest.split(':').next().and_then(|g| g.parse().ok());
1326 } else if let (Some(g), Some(list)) =
1327 (group, line.trim().strip_prefix("Free blocks:"))
1328 {
1329 let ranges = list
1330 .split(',')
1331 .map(str::trim)
1332 .filter(|r| !r.is_empty())
1333 .map(|r| match r.split_once('-') {
1334 Some((a, b)) => (a.parse().unwrap(), b.parse().unwrap()),
1335 None => (r.parse().unwrap(), r.parse().unwrap()),
1336 })
1337 .collect();
1338 free.insert(g, ranges);
1339 }
1340 }
1341 free
1342 }
1343
1344 fn check(
1345 tag: &str,
1346 block_size: u32,
1347 blocks_per_group: u32,
1348 kib: u64,
1349 features: &str,
1350 force_last_group: bool,
1351 ) {
1352 let image =
1353 fs_ext4_test_support::temp_path!("fs_ext4_uninit_{tag}_{}.img", std::process::id());
1354 std::fs::File::create(&image)
1355 .and_then(|f| f.set_len(kib << 10))
1356 .unwrap();
1357 let mut mkfs = fs_ext4_test_support::oracle("mkfs.ext4").args([
1358 "-q",
1359 "-F",
1360 "-b",
1361 &block_size.to_string(),
1362 "-g",
1363 &blocks_per_group.to_string(),
1364 ]);
1365 if !features.is_empty() {
1366 mkfs = mkfs.args(["-O", features]);
1367 }
1368 let out = mkfs.arg(&image).output();
1369 assert!(
1370 out.status.success(),
1371 "[{tag}] {}",
1372 String::from_utf8_lossy(&out.stderr)
1373 );
1374
1375 if force_last_group {
1376 // mkfs.ext4 never leaves the last group BLOCK_UNINIT, so the
1377 // short group's padding is exercised by setting the flag
1378 // with debugfs, as a resize could leave it. (dumpe2fs then
1379 // lists the group as if it were full length, free blocks
1380 // past the end of the volume included; only the blocks
1381 // that exist are compared.)
1382 let last = {
1383 let fs =
1384 Filesystem::mount(Arc::new(FileDevice::open(&image).unwrap())).unwrap();
1385 let last = fs.groups.len() - 1;
1386 (last, fs.groups[last].flags | BgdFlags::BLOCK_UNINIT.bits())
1387 };
1388 let script = format!(
1389 "set_bg {0} flags {1}\nset_bg {0} checksum calc\n",
1390 last.0, last.1
1391 );
1392 let out = fs_ext4_test_support::oracle("debugfs")
1393 .args(["-w", "-f", "-", &image])
1394 .stdin(script)
1395 .output();
1396 assert!(out.status.success(), "[{tag}] debugfs set_bg");
1397 }
1398 let free = dumpe2fs_free(&image);
1399 let fs = Filesystem::mount(Arc::new(FileDevice::open(&image).unwrap())).unwrap();
1400 let bpg = u64::from(fs.sb.blocks_per_group);
1401 let mut checked = 0;
1402 for (gi, desc) in fs.groups.iter().enumerate() {
1403 if !desc.flags().contains(BgdFlags::BLOCK_UNINIT) {
1404 continue;
1405 }
1406 checked += 1;
1407 let first = u64::from(fs.sb.first_data_block) + gi as u64 * bpg;
1408 let len = u64::from(blocks_in_group(&fs.sb, gi as u32));
1409 let bitmap = uninit_block_bitmap(&fs.sb, &fs.groups, gi as u32);
1410 let is_free = |block: u64| free[&gi].iter().any(|&(a, b)| (a..=b).contains(&block));
1411 for bit in 0..bitmap.len() as u64 * 8 {
1412 let set = bitmap[(bit / 8) as usize] & (1 << (bit % 8)) != 0;
1413 if bit >= len {
1414 assert!(set, "[{tag}] group {gi}: padding bit {bit} is clear");
1415 } else {
1416 assert_eq!(
1417 !set,
1418 is_free(first + bit),
1419 "[{tag}] group {gi}, block {}",
1420 first + bit
1421 );
1422 }
1423 }
1424 assert_eq!(
1425 free[&gi]
1426 .iter()
1427 .filter(|&&(a, _)| a < first + len)
1428 .map(|&(a, b)| b.min(first + len - 1) - a + 1)
1429 .sum::<u64>(),
1430 u64::from(desc.free_blocks_count),
1431 "[{tag}] group {gi}: dumpe2fs's list and the descriptor"
1432 );
1433 }
1434 assert!(checked >= 3, "[{tag}] only {checked} BLOCK_UNINIT groups");
1435 drop(fs);
1436 let _ = std::fs::remove_file(&image);
1437 }
1438
1439 #[test]
1440 fn a_1k_flex_bg_volume() {
1441 check("1k_flex", 1024, 1024, 64 << 10, "", false);
1442 }
1443
1444 #[test]
1445 fn a_1k_volume_without_flex_bg() {
1446 check("1k_noflex", 1024, 1024, 64 << 10, "^flex_bg", false);
1447 }
1448
1449 #[test]
1450 fn a_4k_flex_bg_volume() {
1451 check("4k_flex", 4096, 2048, 256 << 10, "", false);
1452 }
1453
1454 #[test]
1455 fn a_4k_volume_without_flex_bg() {
1456 check(
1457 "4k_noflex",
1458 4096,
1459 2048,
1460 256 << 10,
1461 "^flex_bg,^resize_inode",
1462 false,
1463 );
1464 }
1465
1466 #[test]
1467 fn a_1k_meta_bg_volume_without_flex_bg() {
1468 check(
1469 "1k_meta",
1470 1024,
1471 1024,
1472 64 << 10,
1473 "meta_bg,^resize_inode,^flex_bg",
1474 false,
1475 );
1476 }
1477
1478 #[test]
1479 fn a_short_last_group() {
1480 // 64 MiB + 300 KiB: the last group has 300 blocks.
1481 check("1k_short", 1024, 1024, (64 << 10) + 300, "^flex_bg", true);
1482 }
1483 }
1484}