ferrosys/btrfs/btree.rs
1//! The B-tree engine: one descent, one iteration, and the bounds that make both terminate on
2//! an image that was crafted rather than formatted.
3//!
4//! Every tree in a btrfs is the same shape — internal nodes of `(key, child address)` pairs
5//! over leaves of `(key, data)` items, sorted by the key tuple — so there is one engine and
6//! every tree is read through it. What an item *means* is decided by whoever asked for it;
7//! this module knows only that items have keys and that keys are ordered.
8//!
9//! # What bounds a walk
10//!
11//! An untrusted image can describe a tree that is not one, and each way of doing so has its
12//! own guard:
13//!
14//! - **A count larger than the block holds.** Checked against the room the block has, in the
15//! units the block's own level says it holds — 25 bytes per item, 33 per child pointer.
16//! - **An item whose data escapes its leaf.** Checked in two directions, because a leaf fills
17//! from both ends: the data must begin past the array describing it and end within the
18//! block. The arithmetic is 64-bit whatever the target's pointer width is, so a crafted
19//! offset and length behave the same on a 32-bit machine as on the one this is developed
20//! on.
21//! - **A leaf whose data is not packed.** Every item's data abuts its neighbour's, so a leaf
22//! whose data has been moved with its offsets moved to match — every item still inside the
23//! block, every item pointing at bytes that are not its own — is refused. Nothing about a
24//! bound sees that one, and what a reader would otherwise hand back is one record's bytes
25//! under another record's key.
26//! - **A child pointer that leads back up.** Every address a descent visits is remembered, and
27//! meeting one twice is a refusal rather than a loop.
28//! - **A child at the wrong height.** A child must be exactly one level below its parent, so a
29//! descent has a decreasing measure independent of the visited set and terminates whatever
30//! the addresses say.
31//! - **Keys out of order.** A tree that is not sorted is not a tree, and a search over one
32//! silently misses items rather than failing. Every key a walk visits is held against the
33//! one before it.
34//! - **A tree larger than the caller will hold.** [`Limits::max_walk_entries`](crate::Limits::max_walk_entries) caps the items
35//! one walk visits.
36//!
37//! This module does I/O, through the volume it borrows.
38
39use std::collections::BTreeSet;
40use std::io::{Read, Seek};
41
42use super::ondisk::DiskKey;
43use super::volume::{ReadError, TreeBlock, TreeRoot, Volume};
44
45/// One item, taken out of the tree that held it.
46#[derive(Clone, PartialEq, Eq, Debug)]
47pub struct Located {
48 /// The key it was found under.
49 pub key: DiskKey,
50 /// Its bytes, which are as long as the item said and no longer.
51 pub data: Vec<u8>,
52}
53
54/// How many blocks stand above `leaves`, level by level, in a tree of this fan-out.
55///
56/// The first entry is the level directly above the leaves and the last is always one — the
57/// root. A tree whose leaves are one block has nothing above them, and the answer is empty:
58/// that leaf *is* the root.
59///
60/// The arithmetic is the same whether it is being asked in advance as a bound
61/// ([`geometry`](super::geometry)) or exactly of records already packed
62/// ([`materialize`](super::materialize)), so it is asked in one place. `fan_out` is floored at
63/// two, because a level whose blocks hold one child each never narrows and the stack would not
64/// end — a floor rather than an error, since the only caller that could reach it derives the
65/// fan-out from a node size the format has already accepted.
66pub(super) fn levels_above(leaves: u64, fan_out: u64) -> Vec<u64> {
67 let fan_out = fan_out.max(2);
68 let mut levels = Vec::new();
69 let mut width = leaves;
70 while width > 1 {
71 width = width.div_ceil(fan_out);
72 levels.push(width);
73 }
74 levels
75}
76
77/// A handle on one tree of a volume, for searching or iterating it.
78///
79/// Borrowed from the [`Volume`] rather than owning anything, so a caller moves between trees
80/// without reopening the filesystem — and so that every block either reads goes through the
81/// one chunk map and the one checksum check.
82pub struct Tree<'a, R> {
83 volume: &'a mut Volume<R>,
84 root: TreeRoot,
85}
86
87impl<'a, R: Read + Seek> Tree<'a, R> {
88 /// A handle on the tree `root` names.
89 pub(super) fn new(volume: &'a mut Volume<R>, root: TreeRoot) -> Self {
90 Self { volume, root }
91 }
92
93 /// Which tree this is, and where it begins.
94 #[must_use]
95 pub fn root(&self) -> TreeRoot {
96 self.root
97 }
98
99 /// Visit every item, in key order.
100 ///
101 /// The closure answers whether to keep going, so a caller looking for one thing stops
102 /// where it finds it rather than reading the rest of the tree.
103 ///
104 /// # Errors
105 ///
106 /// Whatever reading a block does, and the tree-shape refusals this module's documentation
107 /// lists.
108 pub fn for_each_item<F>(&mut self, mut visit: F) -> Result<(), ReadError>
109 where
110 F: FnMut(&DiskKey, &[u8]) -> bool,
111 {
112 self.drive(DiskKey::MIN, &mut |_| true, &mut visit)
113 }
114
115 /// Visit every item at or after `from`, in key order.
116 ///
117 /// The descent goes straight to the first item at or after the key rather than walking the
118 /// tree from its start, which is what makes "the entries of this directory" one descent
119 /// instead of a scan.
120 ///
121 /// # Errors
122 ///
123 /// As [`for_each_item`](Self::for_each_item).
124 pub fn for_each_item_from<F>(&mut self, from: DiskKey, mut visit: F) -> Result<(), ReadError>
125 where
126 F: FnMut(&DiskKey, &[u8]) -> bool,
127 {
128 self.drive(from, &mut |_| true, &mut visit)
129 }
130
131 /// Visit every block of the tree, in the order a depth-first descent meets them.
132 ///
133 /// Every block is fetched through the chunk map and its checksum verified before the
134 /// closure sees it, so a walk that completes is a statement that every block of the tree
135 /// verified. The items are bounds-checked on the way past whether or not the closure looks
136 /// at them, which is what makes this a verification pass rather than a header read.
137 ///
138 /// # Errors
139 ///
140 /// As [`for_each_item`](Self::for_each_item).
141 pub fn for_each_block<F>(&mut self, mut visit: F) -> Result<(), ReadError>
142 where
143 F: FnMut(&TreeBlock) -> bool,
144 {
145 self.drive(DiskKey::MIN, &mut visit, &mut |_, _| true)
146 }
147
148 /// The first item at or after `key`, or [`None`] where the tree holds none.
149 ///
150 /// # Errors
151 ///
152 /// As [`for_each_item`](Self::for_each_item).
153 pub fn find_first(&mut self, key: DiskKey) -> Result<Option<Located>, ReadError> {
154 let mut found = None;
155 self.for_each_item_from(key, |key, data| {
156 found = Some(Located {
157 key: *key,
158 data: data.to_vec(),
159 });
160 false
161 })?;
162 Ok(found)
163 }
164
165 /// The item stored under exactly `key`, or [`None`] where there is none.
166 ///
167 /// # Errors
168 ///
169 /// As [`for_each_item`](Self::for_each_item).
170 pub fn find_exact(&mut self, key: DiskKey) -> Result<Option<Located>, ReadError> {
171 Ok(self.find_first(key)?.filter(|found| found.key == key))
172 }
173
174 /// The last item at or before `key`, or [`None`] where every item in the tree is above it.
175 ///
176 /// The search a *range* needs, where [`find_first`](Self::find_first) is the one a *point*
177 /// needs. A record keyed by where a run begins covers everything up to the next one, so the
178 /// record covering a position is the last one at or before it — and asking for the first at
179 /// or after would find the record covering the *next* position and skip the one wanted.
180 /// That is how a file's extents are keyed, and reading from an offset in the middle of one
181 /// is the case that makes the difference visible.
182 ///
183 /// One descent, so it costs the height of the tree rather than a scan.
184 ///
185 /// # Errors
186 ///
187 /// As [`for_each_item`](Self::for_each_item).
188 pub fn find_at_or_before(&mut self, key: DiskKey) -> Result<Option<Located>, ReadError> {
189 let mut visited = BTreeSet::new();
190 visited.insert(self.root.bytenr);
191 let mut block = self.read_root()?;
192 block.check_leaf_packing()?;
193 loop {
194 if block.header().is_leaf() {
195 let at = partition(&block, &key, true)?;
196 let Some(index) = at.checked_sub(1) else {
197 return Ok(None);
198 };
199 return Ok(Some(Located {
200 key: block.item(index)?.key,
201 data: block.item_data(index)?.to_vec(),
202 }));
203 }
204 let index = start_index(&block, &key)?;
205 block = self.child_of(&block, index, &mut visited)?;
206 }
207 }
208
209 /// The child at `index` of `parent`, with every guard a descent applies to one.
210 ///
211 /// Three of them, and each catches something the others cannot: an address already visited
212 /// is a tree that is not one, a child that is not exactly one level below its parent is a
213 /// descent with no decreasing measure, and a leaf whose items are not packed hands back one
214 /// record's bytes under another record's key. Every descent in this module goes through
215 /// here so that none of the three can be forgotten in one of them.
216 fn child_of(
217 &mut self,
218 parent: &TreeBlock,
219 index: usize,
220 visited: &mut BTreeSet<u64>,
221 ) -> Result<TreeBlock, ReadError> {
222 let level = parent.header().level;
223 let child_at = parent.key_ptr(index)?.blockptr;
224 if !visited.insert(child_at) {
225 return Err(ReadError::TreeCycle { logical: child_at });
226 }
227 let child = self.volume.read_block(child_at)?;
228 // A child exactly one level below its parent is what makes a descent terminate
229 // whatever the addresses say, and it is a separate guard from the visited set rather
230 // than a cheaper version of it: a crafted tree can point at a fresh block at every step
231 // and still never reach a leaf.
232 if u16::from(child.header().level) + 1 != u16::from(level) {
233 return Err(ReadError::BadTreeLevel {
234 logical: child_at,
235 level: child.header().level,
236 parent: level,
237 });
238 }
239 child.check_leaf_packing()?;
240 Ok(child)
241 }
242
243 /// How many items the tree holds, having read and verified every block on the way.
244 ///
245 /// # Errors
246 ///
247 /// As [`for_each_item`](Self::for_each_item).
248 pub fn count_items(&mut self) -> Result<u64, ReadError> {
249 let mut items = 0u64;
250 self.for_each_item(|_, _| {
251 items += 1;
252 true
253 })?;
254 Ok(items)
255 }
256
257 /// The tree's top block, checked against what the root item said it would be.
258 ///
259 /// The level is recorded in two places — the root item and the block's own header — and
260 /// holding them against each other is what catches a root item pointing at a block that is
261 /// not the one it describes.
262 fn read_root(&mut self) -> Result<TreeBlock, ReadError> {
263 let block = self.volume.read_block(self.root.bytenr)?;
264 if block.header().level != self.root.level {
265 return Err(ReadError::BadTreeLevel {
266 logical: self.root.bytenr,
267 level: block.header().level,
268 parent: self.root.level,
269 });
270 }
271 Ok(block)
272 }
273
274 /// The one descent, which every public form above is a wrapper over.
275 ///
276 /// A depth-first walk with an explicit stack: each frame is a block and how far through it
277 /// the walk has got. A block enters the stack at the entry `from` selects, so a walk from
278 /// a key descends straight to it and a walk from [`DiskKey::MIN`] starts every block at
279 /// zero.
280 fn drive(
281 &mut self,
282 from: DiskKey,
283 on_block: &mut dyn FnMut(&TreeBlock) -> bool,
284 on_item: &mut dyn FnMut(&DiskKey, &[u8]) -> bool,
285 ) -> Result<(), ReadError> {
286 let limit = self.volume.walk_limit();
287 let mut visited = BTreeSet::new();
288 visited.insert(self.root.bytenr);
289
290 let root = self.read_root()?;
291 root.check_leaf_packing()?;
292 if !on_block(&root) {
293 return Ok(());
294 }
295 let start = start_index(&root, &from)?;
296 let mut stack = vec![(root, start)];
297 let mut visits = 0usize;
298 let mut previous: Option<DiskKey> = None;
299
300 while let Some(top) = stack.len().checked_sub(1) {
301 let count = stack[top].0.count()?;
302 let index = stack[top].1;
303 if index >= count {
304 stack.pop();
305 continue;
306 }
307 stack[top].1 += 1;
308
309 if stack[top].0.header().is_leaf() {
310 visits += 1;
311 if visits > limit {
312 return Err(ReadError::TooManyEntries {
313 objectid: self.root.objectid,
314 limit,
315 });
316 }
317 let key = stack[top].0.item(index)?.key;
318 if previous.is_some_and(|last| key <= last) {
319 return Err(ReadError::BadTreeBlock {
320 logical: stack[top].0.header().bytenr,
321 fault: "an item's key is not above the one before it",
322 });
323 }
324 previous = Some(key);
325 let data = stack[top].0.item_data(index)?;
326 if !on_item(&key, data) {
327 return Ok(());
328 }
329 continue;
330 }
331
332 let child = {
333 let (parent, _) = &stack[top];
334 self.child_of(parent, index, &mut visited)?
335 };
336 if !on_block(&child) {
337 return Ok(());
338 }
339 let start = start_index(&child, &from)?;
340 stack.push((child, start));
341 }
342 Ok(())
343 }
344}
345
346/// Where in `block` a walk from `from` begins.
347///
348/// For a **leaf** it is the first item at or after the key, so items below it are skipped. For
349/// a **node** it is the last child whose key is at or below it, since that child's subtree is
350/// where the key would be — and a node whose every key is above `from` starts at its first
351/// child.
352///
353/// Computed for every block rather than only for the leftmost path, which is the same answer
354/// and one fewer piece of state: a block entirely above `from` has no key at or below it, so
355/// the binary search lands on zero on its own.
356///
357/// The search is bounded whatever the block holds, so a block whose keys are not sorted
358/// answers with a wrong index and never with one outside the block. That a tree is sorted is
359/// checked where a walk passes each key, which is where the check is free.
360fn start_index(block: &TreeBlock, from: &DiskKey) -> Result<usize, ReadError> {
361 if *from == DiskKey::MIN {
362 return Ok(0);
363 }
364 // A leaf counts the entries strictly below the key, since that is the first one to visit.
365 // A node counts those at or below it and steps back one, to the child whose subtree is
366 // where the key would be — and a node whose every key is above `from` steps back from
367 // zero, which is its first child.
368 let leaf = block.header().is_leaf();
369 let at = partition(block, from, !leaf)?;
370 Ok(if leaf { at } else { at.saturating_sub(1) })
371}
372
373/// How many of `block`'s entries sort below `from`, counting one equal to it where `inclusive`.
374///
375/// The one binary search over a block, in the two readings a descent needs of it. It is bounded
376/// whatever the block holds, so a block whose keys are not sorted answers with a wrong index
377/// and never with one outside the block. That a tree is sorted is checked where a walk passes
378/// each key, which is where the check is free.
379fn partition(block: &TreeBlock, from: &DiskKey, inclusive: bool) -> Result<usize, ReadError> {
380 let leaf = block.header().is_leaf();
381 let (mut lo, mut hi) = (0usize, block.count()?);
382 while lo < hi {
383 let mid = lo + (hi - lo) / 2;
384 let key = if leaf {
385 block.item(mid)?.key
386 } else {
387 block.key_ptr(mid)?.key
388 };
389 let below = if inclusive { key <= *from } else { key < *from };
390 if below { lo = mid + 1 } else { hi = mid }
391 }
392 Ok(lo)
393}
394
395#[cfg(test)]
396mod tests {
397 use super::*;
398 use crate::btrfs::forge::{
399 CHUNK_LENGTH, CHUNK_LOGICAL, FIRST_FREE_AT, Forge, NODE_SIZE, ROOT_TREE_AT, leaf, node,
400 seal,
401 };
402 use crate::btrfs::ondisk::{Header, Item, ItemType, objectid};
403 use crate::{Limits, OpenOptions};
404
405 /// A key in the root tree's own namespace, so a forged tree sorts the way a real one does.
406 fn key(n: u64) -> DiskKey {
407 DiskKey::new(n, ItemType::ROOT_ITEM, 0)
408 }
409
410 /// A leaf of `n` items, each carrying its own number as a byte.
411 fn items(range: std::ops::Range<u64>) -> Vec<(DiskKey, Vec<u8>)> {
412 range.map(|n| (key(n), vec![n as u8; 8])).collect()
413 }
414
415 /// Every key a walk of `forge`'s root tree visits.
416 fn walk(forge: &Forge) -> Result<Vec<DiskKey>, ReadError> {
417 let mut volume = Volume::open(forge.source())?;
418 let root = volume.root_tree();
419 let mut keys = Vec::new();
420 volume.tree(root).for_each_item(|k, _| {
421 keys.push(*k);
422 true
423 })?;
424 Ok(keys)
425 }
426
427 #[test]
428 fn a_walk_visits_every_item_of_a_tree_in_key_order() {
429 let mut forge = Forge::new();
430 forge.root_leaf(&items(1..40));
431 assert_eq!(
432 walk(&forge).expect("a well-formed tree"),
433 items(1..40).iter().map(|(k, _)| *k).collect::<Vec<_>>()
434 );
435 }
436
437 #[test]
438 fn a_walk_descends_through_a_node_and_reaches_every_leaf_below_it() {
439 // Two leaves under one node, which is the smallest tree that exercises a descent at
440 // all — a one-leaf tree never reads a child pointer.
441 let mut forge = Forge::new();
442 let (left, right) = (FIRST_FREE_AT, FIRST_FREE_AT + NODE_SIZE as u64);
443 forge
444 .block(left, &leaf(left, objectid::ROOT_TREE, &items(1..10)))
445 .block(right, &leaf(right, objectid::ROOT_TREE, &items(10..20)))
446 .root_node(1, &[(key(1), left), (key(10), right)]);
447 assert_eq!(walk(&forge).expect("a well-formed tree").len(), 19);
448 }
449
450 #[test]
451 fn a_seek_lands_where_a_walk_of_the_whole_tree_would_have_reached() {
452 // The property the descent's start index exists for, over a tree deep enough that the
453 // choice of child matters. Every key present is probed, and so is one below and one
454 // above each — the three places an off-by-one in the binary search would show.
455 let mut forge = Forge::new();
456 let (left, right) = (FIRST_FREE_AT, FIRST_FREE_AT + NODE_SIZE as u64);
457 forge
458 .block(left, &leaf(left, objectid::ROOT_TREE, &items(10..20)))
459 .block(right, &leaf(right, objectid::ROOT_TREE, &items(20..30)))
460 .root_node(1, &[(key(10), left), (key(20), right)]);
461
462 let mut volume = Volume::open(forge.source()).expect("a well-formed filesystem");
463 let root = volume.root_tree();
464 let all: Vec<DiskKey> = (10..30).map(key).collect();
465 for probe in (5..35).map(key) {
466 let expected: Vec<DiskKey> = all.iter().copied().filter(|k| *k >= probe).collect();
467 let mut got = Vec::new();
468 volume
469 .tree(root)
470 .for_each_item_from(probe, |k, _| {
471 got.push(*k);
472 true
473 })
474 .expect("a well-formed tree");
475 assert_eq!(got, expected, "seeking to {probe:?}");
476 assert_eq!(
477 volume
478 .tree(root)
479 .find_first(probe)
480 .expect("search")
481 .map(|f| f.key),
482 expected.first().copied()
483 );
484 }
485 // And the exact form answers only where the key is genuinely there.
486 assert!(
487 volume
488 .tree(root)
489 .find_exact(key(15))
490 .expect("search")
491 .is_some()
492 );
493 assert!(
494 volume
495 .tree(root)
496 .find_exact(key(35))
497 .expect("search")
498 .is_none()
499 );
500 }
501
502 #[test]
503 fn a_block_reached_twice_is_a_tree_that_is_not_one() {
504 // Two child pointers naming one leaf. Not a cycle in the strict sense and the same
505 // defect: a walk would visit its items twice, and the key-order check would then fire
506 // for a reason that says nothing about what is wrong.
507 let mut forge = Forge::new();
508 let leaf_at = FIRST_FREE_AT;
509 forge
510 .block(leaf_at, &leaf(leaf_at, objectid::ROOT_TREE, &items(1..10)))
511 .root_node(1, &[(key(1), leaf_at), (key(10), leaf_at)]);
512 assert!(matches!(
513 walk(&forge),
514 Err(ReadError::TreeCycle { logical }) if logical == leaf_at
515 ));
516
517 // And a node naming itself, which the same guard catches before the block is read at
518 // all — the root's own address is in the visited set from the start.
519 let mut forge = Forge::new();
520 forge.root_node(1, &[(key(1), ROOT_TREE_AT)]);
521 assert!(matches!(
522 walk(&forge),
523 Err(ReadError::TreeCycle { logical }) if logical == ROOT_TREE_AT
524 ));
525 }
526
527 #[test]
528 fn a_child_that_is_not_one_level_below_its_parent_is_refused() {
529 // The guard that makes a descent terminate whatever the addresses say: a crafted tree
530 // can name a fresh block at every step, so the visited set alone is not a bound.
531 for child_level in [0u8, 2, 3] {
532 if child_level == 1 {
533 continue;
534 }
535 let mut forge = Forge::new();
536 let child = FIRST_FREE_AT;
537 let block = if child_level == 0 {
538 leaf(child, objectid::ROOT_TREE, &items(1..4))
539 } else {
540 node(
541 child,
542 objectid::ROOT_TREE,
543 child_level,
544 &[(key(1), FIRST_FREE_AT + NODE_SIZE as u64)],
545 )
546 };
547 forge.block(child, &block).root_node(2, &[(key(1), child)]);
548 match walk(&forge) {
549 Err(ReadError::BadTreeLevel { level, parent, .. }) => {
550 assert_eq!((level, parent), (child_level, 2));
551 }
552 other => panic!("a level-{child_level} child under a level-2 node: {other:?}"),
553 }
554 }
555 }
556
557 #[test]
558 fn a_root_block_that_is_not_the_height_its_root_item_claimed_is_refused() {
559 // The level is recorded twice — in the root item and in the block's own header — and
560 // holding them against each other is what catches a root item pointing at a block
561 // that is not the one it describes.
562 let mut forge = Forge::new();
563 forge.root_leaf(&items(1..4));
564 forge.amend_superblock(0, |sb| sb.root_level = 1);
565 assert!(matches!(
566 walk(&forge),
567 Err(ReadError::BadTreeLevel {
568 level: 0,
569 parent: 1,
570 ..
571 })
572 ));
573 }
574
575 #[test]
576 fn a_header_that_does_not_describe_the_block_it_sits_in_is_refused() {
577 // Three claims a header makes about its own block, each checked against something
578 // outside the header. None is a fault a checksum can catch: every one of these fields
579 // is inside what the checksum covers, so a block written correctly somewhere else, or
580 // for another filesystem, verifies perfectly.
581 //
582 // They are one gate because the damage differs and nothing else does — the crafted
583 // block, the walk, and the shape of the refusal are the same three lines in each.
584 /// What a row damages about a header, and the word its refusal must carry.
585 type Fault = (&'static str, Box<dyn Fn(&mut Header)>);
586
587 let faults: [Fault; 3] = [
588 (
589 "room",
590 Box::new(|header: &mut Header| {
591 header.nritems = (NODE_SIZE / Item::SIZE + 1) as u32;
592 }),
593 ),
594 (
595 "logical address",
596 Box::new(|header: &mut Header| header.bytenr = CHUNK_LOGICAL),
597 ),
598 (
599 "another filesystem",
600 Box::new(|header: &mut Header| header.fsid = [0x99; 16]),
601 ),
602 ];
603 for (expected, damage) in faults {
604 let mut forge = Forge::new();
605 forge.root_leaf(&items(1..4));
606 forge.amend(ROOT_TREE_AT, |block| {
607 let mut header = Header::read_from(block).expect("a header");
608 damage(&mut header);
609 header.write_to(block);
610 });
611 match walk(&forge) {
612 Err(ReadError::BadTreeBlock { fault, .. }) => {
613 assert!(fault.contains(expected), "{fault}");
614 }
615 other => panic!("a header claiming the wrong {expected}: {other:?}"),
616 }
617 }
618 }
619
620 #[test]
621 fn an_item_whose_data_escapes_its_leaf_is_refused_in_both_directions() {
622 // Asked of the block directly rather than through a walk, and deliberately: a walk
623 // checks the leaf's packing before it reads an item, and packing already implies an
624 // item cannot end past the block. What it does not imply is that the data stays out
625 // of the array describing it — and a caller holding a block from `read_block` has had
626 // neither check made for it, which is the path these two guards are on.
627 /// What a row damages about an item, and the word its refusal must carry.
628 type Escape = (&'static str, Box<dyn Fn(&mut Item)>);
629
630 let cases: [Escape; 3] = [
631 ("array", Box::new(|item: &mut Item| item.offset = 0)),
632 (
633 "block",
634 Box::new(|item: &mut Item| item.size = NODE_SIZE as u32),
635 ),
636 (
637 "block",
638 Box::new(|item: &mut Item| {
639 item.offset = u32::MAX;
640 item.size = u32::MAX;
641 }),
642 ),
643 ];
644 for (expected, damage) in cases {
645 let mut forge = Forge::new();
646 forge.root_leaf(&items(1..4));
647 forge.amend(ROOT_TREE_AT, |block| {
648 let mut item = Item::read_from(&block[Header::SIZE..]).expect("an item");
649 damage(&mut item);
650 item.write_to(&mut block[Header::SIZE..]);
651 });
652 let mut volume = Volume::open(forge.source()).expect("a well-formed filesystem");
653 let block = volume.read_block(ROOT_TREE_AT).expect("the block verifies");
654 match block.item_data(0) {
655 Err(ReadError::BadItem {
656 fault, index: 0, ..
657 }) => {
658 assert!(fault.contains(expected), "{fault}");
659 }
660 other => panic!("an item escaping its leaf toward the {expected}: {other:?}"),
661 }
662 }
663 }
664
665 #[test]
666 fn a_leaf_whose_data_has_been_moved_with_its_offsets_is_refused() {
667 // Every item stays inside the block and every item points at bytes that are not its
668 // own, so no bound notices — what does is the format's own packing rule, that one
669 // item's data ends where the next one's begins. The baseline's corruptor has a switch
670 // for exactly this shape, and it is what found the check missing.
671 let mut forge = Forge::new();
672 forge.root_leaf(&items(1..6));
673 forge.amend(ROOT_TREE_AT, |block| {
674 let at = Header::SIZE + 2 * Item::SIZE;
675 let mut third = Item::read_from(&block[at..]).expect("an item");
676 third.offset -= 16;
677 third.write_to(&mut block[at..]);
678 });
679 assert!(matches!(
680 walk(&forge),
681 Err(ReadError::BadItem { fault, index: 2, .. })
682 if fault.contains("does not end where the item before it begins")
683 ));
684
685 // And the first item is bounded by the end of the block rather than by a neighbour,
686 // which is the case a rule written only about neighbours would miss.
687 let mut forge = Forge::new();
688 forge.root_leaf(&items(1..6));
689 forge.amend(ROOT_TREE_AT, |block| {
690 let mut first = Item::read_from(&block[Header::SIZE..]).expect("an item");
691 first.size -= 1;
692 first.write_to(&mut block[Header::SIZE..]);
693 });
694 assert!(matches!(
695 walk(&forge),
696 Err(ReadError::BadItem { index: 0, .. })
697 ));
698 }
699
700 #[test]
701 fn a_tree_whose_keys_are_not_in_order_is_refused_rather_than_searched_wrongly() {
702 // A binary search over unsorted keys silently misses items instead of failing, so a
703 // tree that is not sorted is refused where a walk passes each key — which is the one
704 // place the check is free.
705 let mut forge = Forge::new();
706 forge.root_leaf(&items(1..6));
707 forge.amend(ROOT_TREE_AT, |block| {
708 let mut second = Item::read_from(&block[Header::SIZE + Item::SIZE..]).expect("an item");
709 second.key = key(0);
710 second.write_to(&mut block[Header::SIZE + Item::SIZE..]);
711 });
712 assert!(matches!(
713 walk(&forge),
714 Err(ReadError::BadTreeBlock { fault, .. }) if fault.contains("above the one before")
715 ));
716
717 // Two items under one key is the same defect at its boundary: a tree's keys are
718 // unique, and a search that found one of them would never see the other.
719 let mut forge = Forge::new();
720 forge.root_leaf(&[(key(1), vec![1; 8]), (key(1), vec![2; 8])]);
721 assert!(matches!(walk(&forge), Err(ReadError::BadTreeBlock { .. })));
722 }
723
724 #[test]
725 fn a_block_whose_checksum_no_longer_covers_it_is_refused_wherever_it_sits() {
726 for at in [ROOT_TREE_AT, CHUNK_LOGICAL] {
727 let mut forge = Forge::new();
728 forge.root_leaf(&items(1..4));
729 forge.break_checksum(at);
730 let opened = Volume::open(forge.source()).and_then(|mut v| {
731 let root = v.root_tree();
732 v.tree(root).count_items()
733 });
734 assert!(
735 matches!(
736 &opened,
737 Err(ReadError::BadChecksum {
738 object: "tree block",
739 ..
740 })
741 ),
742 "a damaged block at {at}: {opened:?}"
743 );
744 }
745 }
746
747 #[test]
748 fn a_block_at_an_address_no_chunk_maps_is_refused_rather_than_read_from_nowhere() {
749 let mut forge = Forge::new();
750 forge.root_leaf(&items(1..4));
751 // Past the end of the one chunk, which is the address space this filesystem has.
752 forge.amend_superblock(0, |sb| sb.root = CHUNK_LOGICAL + CHUNK_LENGTH);
753 assert!(matches!(
754 walk(&forge),
755 Err(ReadError::UnmappedLogical { .. })
756 ));
757 }
758
759 #[test]
760 fn a_walk_stops_at_the_cap_the_caller_set_rather_than_gathering_past_it() {
761 let mut forge = Forge::new();
762 forge.root_leaf(&items(1..40));
763 let options = OpenOptions::new().limits(Limits::new().max_walk_entries(10));
764 let mut volume =
765 Volume::open_with(forge.source(), options).expect("a well-formed filesystem");
766 let root = volume.root_tree();
767 assert!(matches!(
768 volume.tree(root).count_items(),
769 Err(ReadError::TooManyEntries { limit: 10, .. })
770 ));
771 // Exactly at the cap is inside it: a bound that refused the last entry it allowed
772 // would be off by one in the direction that refuses healthy filesystems.
773 let options = OpenOptions::new().limits(Limits::new().max_walk_entries(39));
774 let mut volume =
775 Volume::open_with(forge.source(), options).expect("a well-formed filesystem");
776 let root = volume.root_tree();
777 assert_eq!(volume.tree(root).count_items().expect("within the cap"), 39);
778 }
779
780 #[test]
781 fn a_visit_that_says_to_stop_stops_where_it_said() {
782 let mut forge = Forge::new();
783 forge.root_leaf(&items(1..40));
784 let mut volume = Volume::open(forge.source()).expect("a well-formed filesystem");
785 let root = volume.root_tree();
786 let mut seen = 0;
787 volume
788 .tree(root)
789 .for_each_item(|_, _| {
790 seen += 1;
791 seen < 3
792 })
793 .expect("a well-formed tree");
794 assert_eq!(seen, 3);
795 }
796
797 #[test]
798 fn every_block_of_a_tree_is_offered_to_a_block_walk_including_its_nodes() {
799 let mut forge = Forge::new();
800 let (left, right) = (FIRST_FREE_AT, FIRST_FREE_AT + NODE_SIZE as u64);
801 forge
802 .block(left, &leaf(left, objectid::ROOT_TREE, &items(1..10)))
803 .block(right, &leaf(right, objectid::ROOT_TREE, &items(10..20)))
804 .root_node(1, &[(key(1), left), (key(10), right)]);
805 let mut volume = Volume::open(forge.source()).expect("a well-formed filesystem");
806 let root = volume.root_tree();
807 let mut blocks = Vec::new();
808 volume
809 .tree(root)
810 .for_each_block(|block| {
811 blocks.push((block.header().bytenr, block.header().level));
812 true
813 })
814 .expect("a well-formed tree");
815 assert_eq!(blocks, vec![(ROOT_TREE_AT, 1), (left, 0), (right, 0)]);
816 }
817
818 #[test]
819 fn a_forged_filesystem_is_one_the_reader_accepts_before_it_is_damaged() {
820 // What makes every gate above a negative control rather than a test of a broken
821 // fixture: the same filesystem, undamaged, opens and reads.
822 let mut forge = Forge::new();
823 forge.root_leaf(&items(1..40));
824 let mut volume = Volume::open(forge.source()).expect("a well-formed filesystem");
825 assert_eq!(volume.superblock().nodesize as usize, NODE_SIZE);
826 assert_eq!(volume.chunk_map().len(), 1);
827 let root = volume.root_tree();
828 assert_eq!(
829 volume.tree(root).count_items().expect("a well-formed tree"),
830 39
831 );
832 // And the seal the forge applies is the format's own recipe, not a second spelling of
833 // it: a block resealed by hand verifies exactly as one the forge wrote does.
834 let mut block = leaf(FIRST_FREE_AT, objectid::ROOT_TREE, &items(1..4));
835 block[Header::SIZE] ^= 0xff;
836 seal(&mut block);
837 forge.block(FIRST_FREE_AT, &block);
838 let mut volume = Volume::open(forge.source()).expect("a well-formed filesystem");
839 assert!(volume.read_block(FIRST_FREE_AT).is_ok());
840 }
841}