Skip to main content

btrfs_transaction/
filesystem.rs

1//! # In-memory filesystem state for a transaction session
2//!
3//! `Filesystem` is the central state object for modifying a btrfs filesystem. It
4//! wraps a `BlockReader` (from `btrfs-disk`), holds the parsed superblock, all
5//! tree root pointers, and tracks which blocks have been modified during the
6//! current transaction.
7//!
8//! Open a device or image with [`Filesystem::open`], then use the read/write
9//! methods to access tree blocks through `ExtentBuffer`.
10
11use crate::buffer::ExtentBuffer;
12use btrfs_disk::{
13    chunk::ChunkTreeCache,
14    items::DeviceItem,
15    reader::{self, BlockReader, OpenFilesystem},
16    superblock::{self, Superblock},
17};
18use std::{
19    collections::{BTreeMap, BTreeSet},
20    fs::File,
21    io::{self, Read, Seek, Write},
22};
23
24/// In-memory filesystem state for a transaction session.
25///
26/// Holds everything needed to read and write tree blocks: the block reader
27/// (with chunk cache for logical-to-physical resolution), the superblock,
28/// all tree root pointers, the current transaction generation, and the set
29/// of dirty (modified) block addresses.
30pub struct Filesystem<R> {
31    /// Block reader with fully populated chunk cache.
32    reader: BlockReader<R>,
33    /// Parsed superblock (updated in-memory during transactions).
34    pub superblock: Superblock,
35    /// Map of tree ID to root block logical address.
36    roots: BTreeMap<u64, u64>,
37    /// Snapshot of root bytenrs at transaction start. Used to detect which
38    /// trees had their root block change during the transaction.
39    original_roots: BTreeMap<u64, u64>,
40    /// Logical addresses of blocks modified in the current transaction.
41    /// `BTreeSet` gives sorted iteration in `flush_dirty` for sequential I/O.
42    dirty: BTreeSet<u64>,
43    /// Current transaction generation (superblock.generation + 1 during a
44    /// transaction, or superblock.generation when idle).
45    pub generation: u64,
46    /// Tree block size in bytes.
47    pub nodesize: u32,
48    /// Minimum I/O unit in bytes.
49    pub sectorsize: u32,
50    /// In-memory cache of extent buffers read or created during the transaction.
51    /// Keyed by logical address. This avoids re-reading blocks from disk and
52    /// ensures modifications are visible within the same transaction.
53    block_cache: BTreeMap<u64, ExtentBuffer>,
54    /// Logical addresses of blocks that have been written to stable storage
55    /// (via `flush_dirty` or `write_block`). A block in this set must be
56    /// COW'd before modification even if its generation matches the current
57    /// transaction, because the on-disk copy is now part of the committed
58    /// state and overwriting it would break crash consistency.
59    written: BTreeSet<u64>,
60    /// Override for the block-group-tree id used by
61    /// [`block_group_tree_id`](Self::block_group_tree_id). When `Some`,
62    /// callers see this id instead of the auto-detected one. Used by
63    /// the `convert-to-block-group-tree` path to pin allocator
64    /// metadata to the extent tree (id 2) while the new BGT (id 11)
65    /// is being built and is therefore only partially populated.
66    /// Should always be cleared via [`BgTreeOverrideGuard`] (RAII)
67    /// rather than written directly, so panics or early returns
68    /// cannot leak the override into normal operation.
69    bg_tree_override: Option<u64>,
70    /// Per-device superblock `dev_item` snapshots taken at open time.
71    /// The key is `devid`; the value is that device's local `dev_item`
72    /// (which encodes the device's identity: `devid`, `dev_uuid`,
73    /// per-device `bytes_used`, etc.). On commit, each device's
74    /// superblock is rewritten with these per-device fields preserved
75    /// so a multi-device filesystem doesn't get clobbered with the
76    /// primary device's identity.
77    per_device_dev_items: BTreeMap<u64, DeviceItem>,
78}
79
80impl<R: Read + Write + Seek> Filesystem<R> {
81    /// Open a btrfs filesystem from a readable+writable+seekable handle.
82    ///
83    /// Performs the full bootstrap sequence (superblock, chunk cache, root
84    /// tree), then wraps the result into an `Filesystem` ready for transactions.
85    ///
86    /// # Errors
87    ///
88    /// Returns an error if any I/O operation fails during bootstrap.
89    pub fn open(handle: R) -> io::Result<Self> {
90        let OpenFilesystem {
91            reader,
92            superblock,
93            tree_roots,
94            per_device_dev_items,
95        } = reader::filesystem_open(handle)?;
96
97        let generation = superblock.generation;
98        let nodesize = superblock.nodesize;
99        let sectorsize = superblock.sectorsize;
100
101        // Convert BTreeMap<u64, (u64, u64)> to BTreeMap<u64, u64> (tree_id -> root bytenr)
102        let mut roots: BTreeMap<u64, u64> = tree_roots
103            .into_iter()
104            .map(|(id, (bytenr, _offset))| (id, bytenr))
105            .collect();
106
107        // The root tree and chunk tree roots live in the superblock, not in
108        // ROOT_ITEM entries. Add them explicitly.
109        roots.insert(1, superblock.root);
110        roots.insert(3, superblock.chunk_root);
111
112        let original_roots = roots.clone();
113
114        Ok(Self {
115            reader,
116            superblock,
117            roots,
118            original_roots,
119            dirty: BTreeSet::new(),
120            generation,
121            nodesize,
122            sectorsize,
123            block_cache: BTreeMap::new(),
124            written: BTreeSet::new(),
125            bg_tree_override: None,
126            per_device_dev_items,
127        })
128    }
129
130    /// Open a btrfs filesystem using a specific superblock mirror.
131    ///
132    /// # Errors
133    ///
134    /// Returns an error if any I/O operation fails during bootstrap.
135    pub fn open_mirror(handle: R, mirror: u32) -> io::Result<Self> {
136        let OpenFilesystem {
137            reader,
138            superblock,
139            tree_roots,
140            per_device_dev_items,
141        } = reader::filesystem_open_mirror(handle, mirror)?;
142
143        let generation = superblock.generation;
144        let nodesize = superblock.nodesize;
145        let sectorsize = superblock.sectorsize;
146
147        let mut roots: BTreeMap<u64, u64> = tree_roots
148            .into_iter()
149            .map(|(id, (bytenr, _offset))| (id, bytenr))
150            .collect();
151
152        roots.insert(1, superblock.root);
153        roots.insert(3, superblock.chunk_root);
154
155        let original_roots = roots.clone();
156
157        Ok(Self {
158            reader,
159            superblock,
160            roots,
161            original_roots,
162            dirty: BTreeSet::new(),
163            generation,
164            nodesize,
165            sectorsize,
166            block_cache: BTreeMap::new(),
167            written: BTreeSet::new(),
168            bg_tree_override: None,
169            per_device_dev_items,
170        })
171    }
172
173    /// Open a multi-device btrfs filesystem from a `devid -> handle` map.
174    ///
175    /// Every device referenced by the filesystem's chunk tree must be
176    /// present in `devices`. Each device's superblock is read and
177    /// validated against the map key; all devices must share the same
178    /// `fsid`. The bootstrap fails with a clear error if any of these
179    /// invariants is violated.
180    ///
181    /// # Errors
182    ///
183    /// Returns an error if the device map is empty, any device's
184    /// superblock disagrees with its key or the primary's `fsid`, the
185    /// chunk tree references a devid not in the map, or any I/O fails.
186    pub fn open_multi(devices: BTreeMap<u64, R>) -> io::Result<Self> {
187        let OpenFilesystem {
188            reader,
189            superblock,
190            tree_roots,
191            per_device_dev_items,
192        } = reader::filesystem_open_multi(devices)?;
193
194        let generation = superblock.generation;
195        let nodesize = superblock.nodesize;
196        let sectorsize = superblock.sectorsize;
197
198        let mut roots: BTreeMap<u64, u64> = tree_roots
199            .into_iter()
200            .map(|(id, (bytenr, _offset))| (id, bytenr))
201            .collect();
202
203        roots.insert(1, superblock.root);
204        roots.insert(3, superblock.chunk_root);
205
206        let original_roots = roots.clone();
207
208        Ok(Self {
209            reader,
210            superblock,
211            roots,
212            original_roots,
213            dirty: BTreeSet::new(),
214            generation,
215            nodesize,
216            sectorsize,
217            block_cache: BTreeMap::new(),
218            written: BTreeSet::new(),
219            bg_tree_override: None,
220            per_device_dev_items,
221        })
222    }
223
224    /// Open a btrfs filesystem using a pre-built chunk cache.
225    ///
226    /// Skips the chunk tree walk entirely, using the provided cache for
227    /// logical-to-physical address resolution. This is the entry point
228    /// for recovery tools like `rescue chunk-recover --apply`, where the
229    /// on-disk chunk tree is damaged and the cache has been reconstructed
230    /// from a raw device scan.
231    ///
232    /// # Errors
233    ///
234    /// Returns an error if any I/O operation fails during bootstrap.
235    pub fn open_with_chunk_cache(
236        handle: R,
237        mirror: u32,
238        chunk_cache: ChunkTreeCache,
239    ) -> io::Result<Self> {
240        let OpenFilesystem {
241            reader,
242            superblock,
243            tree_roots,
244            per_device_dev_items,
245        } = reader::filesystem_open_with_cache(handle, mirror, chunk_cache)?;
246
247        let generation = superblock.generation;
248        let nodesize = superblock.nodesize;
249        let sectorsize = superblock.sectorsize;
250
251        let mut roots: BTreeMap<u64, u64> = tree_roots
252            .into_iter()
253            .map(|(id, (bytenr, _offset))| (id, bytenr))
254            .collect();
255
256        roots.insert(1, superblock.root);
257        roots.insert(3, superblock.chunk_root);
258
259        let original_roots = roots.clone();
260
261        Ok(Self {
262            reader,
263            superblock,
264            roots,
265            original_roots,
266            dirty: BTreeSet::new(),
267            generation,
268            nodesize,
269            sectorsize,
270            block_cache: BTreeMap::new(),
271            written: BTreeSet::new(),
272            bg_tree_override: None,
273            per_device_dev_items,
274        })
275    }
276
277    /// Read a tree block at the given logical address, returning an `ExtentBuffer`.
278    ///
279    /// If the block is already in the in-memory cache (e.g. it was COW'd or
280    /// previously read in this transaction), the cached version is returned
281    /// without hitting disk.
282    ///
283    /// # Errors
284    ///
285    /// Returns an error if the block cannot be read from disk.
286    pub fn read_block(&mut self, logical: u64) -> io::Result<ExtentBuffer> {
287        if let Some(eb) = self.block_cache.get(&logical) {
288            return Ok(eb.clone());
289        }
290        let data = self.reader.read_block(logical)?;
291        let eb = ExtentBuffer::from_raw(data, logical);
292        self.block_cache.insert(logical, eb.clone());
293        Ok(eb)
294    }
295
296    /// Write an extent buffer to disk and mark it dirty.
297    ///
298    /// The buffer's checksum is updated before writing. The block is also
299    /// stored in the in-memory cache so subsequent reads see the modification.
300    ///
301    /// # Errors
302    ///
303    /// Returns an error if the write fails.
304    pub fn write_block(&mut self, eb: &mut ExtentBuffer) -> io::Result<()> {
305        eb.update_checksum(self.superblock.csum_type);
306        self.reader.write_block(eb.logical(), eb.as_bytes())?;
307        self.dirty.insert(eb.logical());
308        self.written.insert(eb.logical());
309        self.block_cache.insert(eb.logical(), eb.clone());
310        Ok(())
311    }
312
313    /// Store an extent buffer in the cache and mark it dirty, without writing
314    /// to disk yet. The actual disk write happens at commit time.
315    pub fn mark_dirty(&mut self, eb: &ExtentBuffer) {
316        // Every modified block passes through here. Validate structural
317        // invariants in debug builds to catch corruption early.
318        debug_assert!(
319            eb.check().is_ok(),
320            "mark_dirty: block at {} failed validation: {}",
321            eb.logical(),
322            eb.check().unwrap_err(),
323        );
324        // The block's generation should match the current transaction.
325        // A dirty block from an older generation means COW was skipped.
326        debug_assert_eq!(
327            eb.generation(),
328            self.generation,
329            "mark_dirty: block at {} has generation {} but filesystem \
330             generation is {} (was COW skipped?)",
331            eb.logical(),
332            eb.generation(),
333            self.generation,
334        );
335        self.dirty.insert(eb.logical());
336        self.block_cache.insert(eb.logical(), eb.clone());
337    }
338
339    /// Insert a block into the in-memory cache without marking it dirty.
340    ///
341    /// This is for test code that needs to simulate blocks already present
342    /// on disk. Production code should use [`mark_dirty`](Self::mark_dirty)
343    /// (for newly modified blocks) or [`read_block`](Self::read_block)
344    /// (to read from disk).
345    #[doc(hidden)]
346    pub fn seed_cache(&mut self, eb: &ExtentBuffer) {
347        self.block_cache.insert(eb.logical(), eb.clone());
348    }
349
350    /// Return the root block logical address for the given tree ID.
351    #[must_use]
352    pub fn root_bytenr(&self, tree_id: u64) -> Option<u64> {
353        self.roots.get(&tree_id).copied()
354    }
355
356    /// Update the root block logical address for a tree.
357    pub fn set_root_bytenr(&mut self, tree_id: u64, bytenr: u64) {
358        self.roots.insert(tree_id, bytenr);
359    }
360
361    /// Read the root block of the given tree as an `ExtentBuffer`.
362    ///
363    /// # Errors
364    ///
365    /// Returns an error if the tree ID is unknown or the block cannot be read.
366    pub fn root_node(&mut self, tree_id: u64) -> io::Result<ExtentBuffer> {
367        let bytenr = self.root_bytenr(tree_id).ok_or_else(|| {
368            io::Error::new(
369                io::ErrorKind::NotFound,
370                format!("unknown tree ID {tree_id}"),
371            )
372        })?;
373        self.read_block(bytenr)
374    }
375
376    /// Return an iterator over all dirty block logical addresses.
377    pub fn dirty_blocks(&self) -> impl Iterator<Item = u64> + '_ {
378        self.dirty.iter().copied()
379    }
380
381    /// Return the number of dirty blocks.
382    #[must_use]
383    pub fn dirty_count(&self) -> usize {
384        self.dirty.len()
385    }
386
387    /// Check whether a block has been written to stable storage during
388    /// this transaction. Such blocks must be COW'd before modification
389    /// even if their generation matches the current transaction.
390    #[must_use]
391    pub fn is_written(&self, logical: u64) -> bool {
392        self.written.contains(&logical)
393    }
394
395    /// Clear the dirty and written sets (used after commit or abort).
396    pub fn clear_dirty(&mut self) {
397        self.dirty.clear();
398        self.written.clear();
399    }
400
401    /// Clear the block cache (used after commit or abort to free memory).
402    pub fn clear_cache(&mut self) {
403        self.block_cache.clear();
404    }
405
406    /// Return all tree root entries as `(tree_id, root_bytenr)` pairs.
407    pub fn tree_roots(&self) -> impl Iterator<Item = (u64, u64)> + '_ {
408        self.roots.iter().map(|(&id, &bytenr)| (id, bytenr))
409    }
410
411    /// Flush all dirty blocks to disk.
412    ///
413    /// Iterates the dirty set, checksums each cached block, and writes it.
414    /// Blocks that are dirty but not in the cache are skipped (they were
415    /// already written by `write_block`).
416    ///
417    /// # Errors
418    ///
419    /// Returns an error if any write fails.
420    pub fn flush_dirty(&mut self) -> io::Result<()> {
421        /// `BTRFS_HEADER_FLAG_WRITTEN` (bit 0): the kernel requires this
422        /// flag on all tree blocks that have been committed to stable
423        /// storage. Must be set before computing the checksum.
424        const HEADER_FLAG_WRITTEN: u64 = 1 << 0;
425
426        let dirty: Vec<u64> = self.dirty.iter().copied().collect();
427        for logical in dirty {
428            if let Some(eb) = self.block_cache.get(&logical).cloned() {
429                let mut eb = eb;
430                // Validate before writing to stable storage. This is
431                // the last chance to catch corruption before it lands
432                // on disk.
433                debug_assert!(
434                    eb.check().is_ok(),
435                    "flush_dirty: block at {} failed validation before \
436                     write: {}",
437                    eb.logical(),
438                    eb.check().unwrap_err(),
439                );
440                eb.set_flags(eb.flags() | HEADER_FLAG_WRITTEN);
441                eb.update_checksum(self.superblock.csum_type);
442                self.reader.write_block(eb.logical(), eb.as_bytes())?;
443                self.written.insert(eb.logical());
444            }
445        }
446        Ok(())
447    }
448
449    /// Return a mutable reference to the underlying block reader.
450    pub fn reader_mut(&mut self) -> &mut BlockReader<R> {
451        &mut self.reader
452    }
453
454    /// Return a reference to the underlying block reader.
455    #[must_use]
456    pub fn reader(&self) -> &BlockReader<R> {
457        &self.reader
458    }
459
460    /// Remove a tree root entry.
461    pub fn remove_root(&mut self, tree_id: u64) -> Option<u64> {
462        self.roots.remove(&tree_id)
463    }
464
465    /// Evict a block from the cache (e.g. after freeing it).
466    pub fn evict_block(&mut self, logical: u64) {
467        self.block_cache.remove(&logical);
468        self.dirty.remove(&logical);
469    }
470
471    /// Snapshot the current roots so we can detect changes at commit time.
472    ///
473    /// Called at transaction start to record the baseline state.
474    pub fn snapshot_roots(&mut self) {
475        self.original_roots = self.roots.clone();
476    }
477
478    /// Restore the roots map to the last snapshot. Used by
479    /// `Transaction::abort` to roll back in-memory `set_root_bytenr`
480    /// changes that pointed at COWed-but-never-written bytenrs.
481    pub fn restore_roots_from_snapshot(&mut self) {
482        self.roots = self.original_roots.clone();
483    }
484
485    /// Write the in-memory superblock to all 3 mirrors of every open
486    /// device, with each device's per-device `dev_item` spliced in.
487    ///
488    /// On a multi-device filesystem each device's superblock has its
489    /// own `dev_item` (`devid`, `dev_uuid`, per-device `bytes_used`, etc.).
490    /// Writing the primary device's superblock verbatim to a secondary
491    /// would corrupt the secondary's identity. This helper preserves
492    /// that per-device state by splicing the matching `dev_item` from
493    /// the snapshot taken at open time before serializing each
494    /// device's variant.
495    ///
496    /// # Errors
497    ///
498    /// Returns an error if any device referenced by the dev-item
499    /// snapshot is not open, or if any underlying write fails.
500    pub fn write_superblock_all_devices(&mut self) -> io::Result<()> {
501        // Collect the devids first so we don't hold a borrow on
502        // `self.reader` while iterating per-device serializations.
503        let devids: Vec<u64> = self.reader.devices().keys().copied().collect();
504        for devid in devids {
505            let mut sb_for_dev = self.superblock.clone();
506            if let Some(dev_item) = self.per_device_dev_items.get(&devid) {
507                sb_for_dev.dev_item = dev_item.clone();
508            }
509            let bytes = sb_for_dev.to_bytes();
510            let dev = self
511                .reader
512                .devices_mut()
513                .get_mut(&devid)
514                .expect("devid present in iteration but missing from map");
515            superblock::write_superblock_all_mirrors(dev, &bytes)?;
516        }
517        Ok(())
518    }
519
520    /// Flush pending writes via `Write::flush()` on every device.
521    ///
522    /// Flushes userspace write buffers on every open device. For
523    /// file-backed storage, use [`Filesystem<File>::sync`] instead,
524    /// which also calls fsync per device.
525    pub fn flush_writes(&mut self) -> io::Result<()> {
526        for dev in self.reader.devices_mut().values_mut() {
527            dev.flush()?;
528        }
529        Ok(())
530    }
531
532    /// Return tree IDs whose root block changed since the last snapshot.
533    ///
534    /// Compares current roots against the snapshot taken at transaction start.
535    /// Excludes tree IDs 1 (root tree) and 3 (chunk tree) since their root
536    /// pointers live in the superblock, not in root items.
537    #[must_use]
538    pub fn changed_roots(&self) -> Vec<(u64, u64, u8)> {
539        let mut changed = Vec::new();
540        for (&tree_id, &current_bytenr) in &self.roots {
541            // Root tree and chunk tree are updated via superblock, not root items
542            if tree_id == 1 || tree_id == 3 {
543                continue;
544            }
545            let original = self.original_roots.get(&tree_id).copied();
546            if original != Some(current_bytenr) {
547                // Look up the level from the cached block if available
548                let level = self
549                    .block_cache
550                    .get(&current_bytenr)
551                    .map_or(0, ExtentBuffer::level);
552                changed.push((tree_id, current_bytenr, level));
553            }
554        }
555        changed
556    }
557
558    /// Return the tree id that holds `BLOCK_GROUP_ITEM` records.
559    ///
560    /// When [`bg_tree_override`](Self::bg_tree_override_for_test) is
561    /// set (typically by the `convert-to-block-group-tree` path),
562    /// returns it verbatim. Otherwise auto-detects: returns 11
563    /// (`BLOCK_GROUP_TREE`) if a root for tree 11 is registered,
564    /// else 2 (`EXTENT_TREE`).
565    ///
566    /// All allocator and block-group-update code paths must consult
567    /// this accessor instead of duplicating the routing logic, so
568    /// that the override mechanism actually works for everything
569    /// that touches block-group state.
570    #[must_use]
571    pub fn block_group_tree_id(&self) -> u64 {
572        if let Some(id) = self.bg_tree_override {
573            return id;
574        }
575        if self.root_bytenr(11).is_some() {
576            11
577        } else {
578            2
579        }
580    }
581
582    /// Set the block-group-tree id override. Prefer
583    /// [`pin_block_group_tree`](Self::pin_block_group_tree) which
584    /// returns an RAII guard that clears the override on drop.
585    ///
586    /// Exposed primarily for unit tests of the routing primitive.
587    #[doc(hidden)]
588    pub fn bg_tree_override_for_test(&mut self, id: Option<u64>) {
589        self.bg_tree_override = id;
590    }
591
592    /// Pin [`block_group_tree_id`](Self::block_group_tree_id) to
593    /// the given tree id and return a guard that restores the
594    /// previous override (typically `None`) when dropped.
595    ///
596    /// Use this in conversion paths so that panics or `?`
597    /// early-returns cannot leave the override stuck on the wrong
598    /// value.
599    pub fn pin_block_group_tree(
600        &mut self,
601        id: u64,
602    ) -> BgTreeOverrideGuard<'_, R> {
603        let prev = self.bg_tree_override;
604        self.bg_tree_override = Some(id);
605        BgTreeOverrideGuard { fs: self, prev }
606    }
607}
608
609/// RAII guard that restores the previous block-group-tree
610/// override on drop. Created by
611/// [`Filesystem::pin_block_group_tree`].
612pub struct BgTreeOverrideGuard<'a, R> {
613    fs: &'a mut Filesystem<R>,
614    prev: Option<u64>,
615}
616
617impl<R> BgTreeOverrideGuard<'_, R> {
618    /// Borrow the underlying filesystem mutably for the duration
619    /// of the guard.
620    pub fn fs_mut(&mut self) -> &mut Filesystem<R> {
621        self.fs
622    }
623}
624
625impl<R> Drop for BgTreeOverrideGuard<'_, R> {
626    fn drop(&mut self) {
627        self.fs.bg_tree_override = self.prev;
628    }
629}
630
631impl Filesystem<File> {
632    /// Sync all data to stable storage on every device (fsync).
633    ///
634    /// Calls `File::sync_all()` on every open device handle, ensuring
635    /// all written data reaches stable storage. This should be called
636    /// after commit to guarantee durability.
637    pub fn sync(&mut self) -> io::Result<()> {
638        for dev in self.reader.devices_mut().values_mut() {
639            dev.sync_all()?;
640        }
641        Ok(())
642    }
643}
644
645#[cfg(test)]
646mod tests {
647    use super::*;
648
649    // Filesystem requires a real filesystem image to test meaningfully.
650    // These are basic structural tests; full integration tests will use
651    // temporary images created by btrfs-mkfs.
652
653    #[test]
654    fn dirty_tracking() {
655        // We can test the dirty set logic without a real filesystem
656        let mut dirty = BTreeSet::new();
657        dirty.insert(65536u64);
658        dirty.insert(131072);
659        assert_eq!(dirty.len(), 2);
660        assert!(dirty.contains(&65536));
661        dirty.clear();
662        assert!(dirty.is_empty());
663    }
664
665    #[test]
666    fn roots_map() {
667        let mut roots = BTreeMap::new();
668        roots.insert(1u64, 65536u64);
669        roots.insert(5, 131072);
670        assert_eq!(roots.get(&1), Some(&65536));
671        assert_eq!(roots.get(&5), Some(&131072));
672        assert_eq!(roots.get(&99), None);
673    }
674}