Skip to main content

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}