Skip to main content

fxfs/object_store/journal/
super_block.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! We currently store two of these super-blocks (A/B) starting at offset 0 and 512kB.
6//!
7//! Immediately following the serialized `SuperBlockHeader` structure below is a stream of
8//! serialized operations that are replayed into the root parent `ObjectStore`. Note that the root
9//! parent object store exists entirely in RAM until serialized back into the super-block.
10//!
11//! Super-blocks are updated alternately with a monotonically increasing generation number.
12//! At mount time, the super-block used is the valid `SuperBlock` with the highest generation
13//! number.
14//!
15//! Note the asymmetry here regarding load/save:
16//!   * We load a superblock from a Device/SuperBlockInstance and return a
17//!     (SuperBlockHeader, ObjectStore) pair. The ObjectStore is populated directly from device.
18//!   * We save a superblock from a (SuperBlockHeader, Vec<ObjectItem>) pair to a WriteObjectHandle.
19//!
20//! This asymmetry is required for consistency.
21//! The Vec<ObjectItem> is produced by scanning the root_parent_store. This is the responsibility
22//! of the journal code, which must hold a lock to avoid concurrent updates. However, this lock
23//! must NOT be held when saving the superblock as additional extents may need to be allocated as
24//! part of the save process.
25use crate::errors::FxfsError;
26use crate::filesystem::{ApplyContext, ApplyMode, FxFilesystem, JournalingObject};
27use crate::log::*;
28use crate::lsm_tree::types::LayerIterator;
29use crate::lsm_tree::{LSMTree, LayerSet, Query};
30use crate::metrics;
31use crate::object_handle::ObjectHandle as _;
32use crate::object_store::allocator::Reservation;
33use crate::object_store::data_object_handle::{FileExtent, OverwriteOptions};
34use crate::object_store::journal::bootstrap_handle::BootstrapObjectHandle;
35use crate::object_store::journal::reader::{JournalReader, ReadResult};
36use crate::object_store::journal::writer::JournalWriter;
37use crate::object_store::journal::{BLOCK_SIZE, JournalCheckpoint, JournalCheckpointV32};
38use crate::object_store::object_record::{
39    ObjectItem, ObjectItemV40, ObjectItemV41, ObjectItemV43, ObjectItemV46, ObjectItemV47,
40    ObjectItemV49, ObjectItemV50, ObjectItemV55,
41};
42use crate::object_store::transaction::{AssocObj, Options};
43use crate::object_store::tree::MajorCompactable;
44use crate::object_store::{
45    DataObjectHandle, HandleOptions, HandleOwner, Mutation, ObjectKey, ObjectStore, ObjectValue,
46};
47use crate::range::RangeExt;
48use crate::serialized_types::{
49    EARLIEST_SUPPORTED_VERSION, FIRST_EXTENT_IN_SUPERBLOCK_VERSION, Migrate,
50    SMALL_SUPERBLOCK_VERSION, Version, Versioned, VersionedLatest, migrate_nodefault,
51    migrate_to_version,
52};
53use anyhow::{Context, Error, bail, ensure};
54use fprint::TypeFingerprint;
55use fuchsia_inspect::{Property as _, UintProperty};
56use fuchsia_sync::Mutex;
57use futures::FutureExt;
58use rustc_hash::FxHashMap as HashMap;
59use serde::{Deserialize, Serialize};
60use std::collections::VecDeque;
61use std::fmt;
62use std::io::{Read, Write};
63use std::ops::Range;
64use std::sync::Arc;
65use std::time::SystemTime;
66use storage_device::Device;
67use uuid::Uuid;
68
69// These only exist in the root store.
70const SUPER_BLOCK_A_OBJECT_ID: u64 = 1;
71const SUPER_BLOCK_B_OBJECT_ID: u64 = 2;
72
73/// The superblock is extended in units of `SUPER_BLOCK_CHUNK_SIZE` as required.
74pub const SUPER_BLOCK_CHUNK_SIZE: u64 = 65536;
75
76/// Each superblock is one block but may contain records that extend its own length.
77pub(crate) const MIN_SUPER_BLOCK_SIZE: u64 = 4096;
78/// The first 2 * 512 KiB on the disk used to be reserved for two A/B super-blocks.
79const LEGACY_MIN_SUPER_BLOCK_SIZE: u64 = 524_288;
80
81/// All superblocks start with the magic bytes "FxfsSupr".
82const SUPER_BLOCK_MAGIC: &[u8; 8] = b"FxfsSupr";
83
84/// An enum representing one of our super-block instances.
85///
86/// This provides hard-coded constants related to the location and properties of the super-blocks
87/// that are required to bootstrap the filesystem.
88#[derive(Copy, Clone, Debug)]
89pub enum SuperBlockInstance {
90    A,
91    B,
92}
93
94impl SuperBlockInstance {
95    /// Returns the next [SuperBlockInstance] for use in round-robining writes across super-blocks.
96    pub fn next(&self) -> SuperBlockInstance {
97        match self {
98            SuperBlockInstance::A => SuperBlockInstance::B,
99            SuperBlockInstance::B => SuperBlockInstance::A,
100        }
101    }
102
103    pub fn object_id(&self) -> u64 {
104        match self {
105            SuperBlockInstance::A => SUPER_BLOCK_A_OBJECT_ID,
106            SuperBlockInstance::B => SUPER_BLOCK_B_OBJECT_ID,
107        }
108    }
109
110    /// Returns the byte range where the first extent of the [SuperBlockInstance] is stored.
111    /// (Note that a [SuperBlockInstance] may still have multiple extents.)
112    pub fn first_extent(&self) -> Range<u64> {
113        match self {
114            SuperBlockInstance::A => 0..MIN_SUPER_BLOCK_SIZE,
115            SuperBlockInstance::B => 524288..524288 + MIN_SUPER_BLOCK_SIZE,
116        }
117    }
118
119    /// We used to allocate 512kB to superblocks but this was almost always more than needed.
120    pub fn legacy_first_extent(&self) -> Range<u64> {
121        match self {
122            SuperBlockInstance::A => 0..LEGACY_MIN_SUPER_BLOCK_SIZE,
123            SuperBlockInstance::B => LEGACY_MIN_SUPER_BLOCK_SIZE..2 * LEGACY_MIN_SUPER_BLOCK_SIZE,
124        }
125    }
126}
127
128pub type SuperBlockHeader = SuperBlockHeaderV32;
129
130#[derive(
131    Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, TypeFingerprint, Versioned,
132)]
133pub struct SuperBlockHeaderV32 {
134    /// The globally unique identifier for the filesystem.
135    pub guid: UuidWrapperV32,
136
137    /// There are two super-blocks which are used in an A/B configuration. The super-block with the
138    /// greatest generation number is what is used when mounting an Fxfs image; the other is
139    /// discarded.
140    pub generation: u64,
141
142    /// The root parent store is an in-memory only store and serves as the backing store for the
143    /// root store and the journal.  The records for this store are serialized into the super-block
144    /// and mutations are also recorded in the journal.
145    pub root_parent_store_object_id: u64,
146
147    /// The root parent needs a graveyard and there's nowhere else to store it other than in the
148    /// super-block.
149    pub root_parent_graveyard_directory_object_id: u64,
150
151    /// The root object store contains all other metadata objects (including the allocator, the
152    /// journal and the super-blocks) and is the parent for all other object stores.
153    pub root_store_object_id: u64,
154
155    /// This is in the root object store.
156    pub allocator_object_id: u64,
157
158    /// This is in the root parent object store.
159    pub journal_object_id: u64,
160
161    /// Start checkpoint for the journal file.
162    pub journal_checkpoint: JournalCheckpointV32,
163
164    /// Offset of the journal file when the super-block was written.  If no entry is present in
165    /// journal_file_offsets for a particular object, then an object might have dependencies on the
166    /// journal from super_block_journal_file_offset onwards, but not earlier.
167    pub super_block_journal_file_offset: u64,
168
169    /// object id -> journal file offset. Indicates where each object has been flushed to.
170    pub journal_file_offsets: HashMap<u64, u64>,
171
172    /// Records the amount of borrowed metadata space as applicable at
173    /// `super_block_journal_file_offset`.
174    pub borrowed_metadata_space: u64,
175
176    /// The earliest version of Fxfs used to create any still-existing struct in the filesystem.
177    ///
178    /// Note: structs in the filesystem may had been made with various different versions of Fxfs.
179    pub earliest_version: Version,
180}
181
182type UuidWrapper = UuidWrapperV32;
183#[derive(Clone, Default, Eq, PartialEq)]
184pub struct UuidWrapperV32(pub Uuid);
185
186impl UuidWrapper {
187    fn new() -> Self {
188        Self(Uuid::new_v4())
189    }
190    #[cfg(test)]
191    fn nil() -> Self {
192        Self(Uuid::nil())
193    }
194}
195
196impl fmt::Debug for UuidWrapper {
197    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
198        // The UUID uniquely identifies the filesystem, so we should redact it so that we don't leak
199        // it in logs.
200        f.write_str("<redacted>")
201    }
202}
203
204impl TypeFingerprint for UuidWrapper {
205    fn fingerprint() -> String {
206        "<[u8;16]>".to_owned()
207    }
208}
209
210// Uuid serializes like a slice, but SuperBlockHeader used to contain [u8; 16] and we want to remain
211// compatible.
212impl Serialize for UuidWrapper {
213    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
214        self.0.as_bytes().serialize(serializer)
215    }
216}
217
218impl<'de> Deserialize<'de> for UuidWrapper {
219    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
220        <[u8; 16]>::deserialize(deserializer).map(|bytes| UuidWrapperV32(Uuid::from_bytes(bytes)))
221    }
222}
223
224pub type SuperBlockRecord = SuperBlockRecordV55;
225
226#[allow(clippy::large_enum_variant)]
227#[derive(Debug, Serialize, Deserialize, TypeFingerprint, Versioned)]
228pub enum SuperBlockRecordV55 {
229    // When reading the super-block we know the initial extent, but not subsequent extents, so these
230    // records need to exist to allow us to completely read the super-block.
231    Extent(Range<u64>),
232
233    // Following the super-block header are ObjectItem records that are to be replayed into the root
234    // parent object store.
235    ObjectItem(ObjectItemV55),
236
237    // Marks the end of the full super-block.
238    End,
239}
240
241#[allow(clippy::large_enum_variant)]
242#[derive(Migrate, Debug, Serialize, Deserialize, TypeFingerprint, Versioned)]
243#[migrate_to_version(SuperBlockRecordV55)]
244#[migrate_nodefault]
245pub enum SuperBlockRecordV54 {
246    Extent(Range<u64>),
247    ObjectItem(crate::object_store::object_record::ObjectItemV54),
248    End,
249}
250
251#[allow(clippy::large_enum_variant)]
252#[derive(Migrate, Serialize, Deserialize, TypeFingerprint, Versioned)]
253#[migrate_to_version(SuperBlockRecordV54)]
254#[migrate_nodefault]
255pub enum SuperBlockRecordV50 {
256    Extent(Range<u64>),
257    ObjectItem(ObjectItemV50),
258    End,
259}
260
261#[allow(clippy::large_enum_variant)]
262#[derive(Migrate, Serialize, Deserialize, TypeFingerprint, Versioned)]
263#[migrate_to_version(SuperBlockRecordV50)]
264pub enum SuperBlockRecordV49 {
265    Extent(Range<u64>),
266    ObjectItem(ObjectItemV49),
267    End,
268}
269
270#[allow(clippy::large_enum_variant)]
271#[derive(Migrate, Serialize, Deserialize, TypeFingerprint, Versioned)]
272#[migrate_to_version(SuperBlockRecordV49)]
273pub enum SuperBlockRecordV47 {
274    Extent(Range<u64>),
275    ObjectItem(ObjectItemV47),
276    End,
277}
278
279#[allow(clippy::large_enum_variant)]
280#[derive(Migrate, Serialize, Deserialize, TypeFingerprint, Versioned)]
281#[migrate_to_version(SuperBlockRecordV47)]
282pub enum SuperBlockRecordV46 {
283    Extent(Range<u64>),
284    ObjectItem(ObjectItemV46),
285    End,
286}
287
288#[allow(clippy::large_enum_variant)]
289#[derive(Migrate, Serialize, Deserialize, TypeFingerprint, Versioned)]
290#[migrate_to_version(SuperBlockRecordV46)]
291pub enum SuperBlockRecordV43 {
292    Extent(Range<u64>),
293    ObjectItem(ObjectItemV43),
294    End,
295}
296
297#[derive(Migrate, Serialize, Deserialize, TypeFingerprint, Versioned)]
298#[migrate_to_version(SuperBlockRecordV43)]
299pub enum SuperBlockRecordV41 {
300    Extent(Range<u64>),
301    ObjectItem(ObjectItemV41),
302    End,
303}
304
305#[derive(Migrate, Serialize, Deserialize, TypeFingerprint, Versioned)]
306#[migrate_to_version(SuperBlockRecordV41)]
307pub enum SuperBlockRecordV40 {
308    Extent(Range<u64>),
309    ObjectItem(ObjectItemV40),
310    End,
311}
312
313struct SuperBlockMetrics {
314    /// Time we wrote the most recent superblock in milliseconds since [`std::time::UNIX_EPOCH`].
315    /// Uses [`std::time::SystemTime`] as the clock source.
316    last_super_block_update_time_ms: UintProperty,
317
318    /// Offset of the most recent superblock we wrote in the journal.
319    last_super_block_offset: UintProperty,
320}
321
322impl Default for SuperBlockMetrics {
323    fn default() -> Self {
324        SuperBlockMetrics {
325            last_super_block_update_time_ms: metrics::detail()
326                .create_uint("last_super_block_update_time_ms", 0),
327            last_super_block_offset: metrics::detail().create_uint("last_super_block_offset", 0),
328        }
329    }
330}
331
332/// Reads an individual (A/B) super-block instance and root_parent_store from device.
333/// Users should use SuperBlockManager::load() instead.
334async fn read(
335    device: Arc<dyn Device>,
336    block_size: u64,
337    instance: SuperBlockInstance,
338) -> Result<(SuperBlockHeader, SuperBlockInstance, ObjectStore), Error> {
339    let (super_block_header, mut reader) = SuperBlockHeader::read_header(device.clone(), instance)
340        .await
341        .context("failed to read superblock")?;
342    let root_parent = ObjectStore::new_root_parent(
343        device,
344        block_size,
345        super_block_header.root_parent_store_object_id,
346    );
347    root_parent.set_graveyard_directory_object_id(
348        super_block_header.root_parent_graveyard_directory_object_id,
349    );
350
351    loop {
352        // TODO: Flatten a layer and move reader here?
353        let mutation = match reader.next_item().await? {
354            // RecordReader should filter out extent records.
355            SuperBlockRecord::Extent(_) => bail!("Unexpected extent record"),
356            SuperBlockRecord::ObjectItem(item) => Mutation::insert_object(item.key, item.value),
357            SuperBlockRecord::End => break,
358        };
359        root_parent.apply_mutation(
360            mutation,
361            &ApplyContext {
362                mode: ApplyMode::Replay,
363                // A file offset of 0 is safe here because this is only used for the root parent
364                // store, which is completely reconstructed from the superblock at each mount,
365                // making the checkpoint offset irrelevant.
366                checkpoint: JournalCheckpoint { file_offset: 0, ..Default::default() },
367            },
368            AssocObj::None,
369        )?;
370    }
371    Ok((super_block_header, instance, root_parent))
372}
373
374/// Write a super-block to the given file handle.
375/// Requires that the filesystem is fully loaded and writable as this may require allocation.
376async fn write<S: HandleOwner>(
377    super_block_header: &SuperBlockHeader,
378    items: LayerSet<ObjectKey, ObjectValue>,
379    handle: DataObjectHandle<S>,
380) -> Result<(), Error> {
381    let object_manager = handle.store().filesystem().object_manager().clone();
382    // TODO(https://fxbug.dev/42177407): Don't use the same code here for Journal and SuperBlock. They
383    // aren't the same things and it is already getting convoluted. e.g of diff stream content:
384    //   Superblock:  (Magic, Ver, Header(Ver), Extent(Ver)*, SuperBlockRecord(Ver)*, ...)
385    //   Journal:     (Ver, JournalRecord(Ver)*, RESET, Ver2, JournalRecord(Ver2)*, ...)
386    // We should abstract away the checksum code and implement these separately.
387
388    let mut writer =
389        SuperBlockWriter::new(handle, super_block_header, object_manager.metadata_reservation())
390            .await?;
391    let mut merger = items.merger();
392    let mut iter = LSMTree::major_iter(merger.query(Query::FullScan).await?).await?;
393    while let Some(item) = iter.get() {
394        writer.write_root_parent_item(item.cloned()).await?;
395        iter.advance().await?;
396    }
397    writer.finalize().await
398}
399
400// Compacts and returns the *old* snapshot of the root_parent store.
401// Must be performed whilst holding a writer lock.
402pub fn compact_root_parent(
403    root_parent_store: &ObjectStore,
404) -> Result<LayerSet<ObjectKey, ObjectValue>, Error> {
405    // The root parent always uses in-memory layers which shouldn't be async, so we can use
406    // `now_or_never`.
407    let tree = root_parent_store.tree();
408    let layer_set = tree.layer_set();
409    {
410        let mut merger = layer_set.merger();
411        let mut iter = LSMTree::major_iter(merger.query(Query::FullScan).now_or_never().unwrap()?)
412            .now_or_never()
413            .unwrap()?;
414        let new_layer = LSMTree::new_mutable_layer();
415        while let Some(item_ref) = iter.get() {
416            new_layer.insert(item_ref.cloned())?;
417            iter.advance().now_or_never().unwrap()?;
418        }
419        tree.set_mutable_layer(new_layer);
420    }
421    Ok(layer_set)
422}
423
424/// This encapsulates the A/B alternating super-block logic.
425/// All super-block load/save operations should be via the methods on this type.
426pub(super) struct SuperBlockManager {
427    pub next_instance: Mutex<SuperBlockInstance>,
428    metrics: SuperBlockMetrics,
429}
430
431impl SuperBlockManager {
432    pub fn new() -> Self {
433        Self { next_instance: Mutex::new(SuperBlockInstance::A), metrics: Default::default() }
434    }
435
436    /// Loads both A/B super-blocks and root_parent ObjectStores and and returns the newest valid
437    /// pair. Also ensures the next superblock updated via |save| will be the other instance.
438    pub async fn load(
439        &self,
440        device: Arc<dyn Device>,
441        block_size: u64,
442    ) -> Result<(SuperBlockHeader, ObjectStore), Error> {
443        // Superblocks consume a minimum of one block. We currently hard code the length of
444        // this first extent. It should work with larger block sizes, but has not been tested.
445        // TODO(https://fxbug.dev/42063349): Consider relaxing this.
446        debug_assert!(MIN_SUPER_BLOCK_SIZE == block_size);
447
448        let (super_block, current_super_block, root_parent) = match futures::join!(
449            read(device.clone(), block_size, SuperBlockInstance::A),
450            read(device.clone(), block_size, SuperBlockInstance::B)
451        ) {
452            (Err(e1), Err(e2)) => {
453                bail!("Failed to load both superblocks due to {:?}\nand\n{:?}", e1, e2)
454            }
455            (Ok(result), Err(_)) => result,
456            (Err(_), Ok(result)) => result,
457            (Ok(result1), Ok(result2)) => {
458                // Break the tie by taking the super-block with the greatest generation.
459                if (result2.0.generation as i64).wrapping_sub(result1.0.generation as i64) > 0 {
460                    result2
461                } else {
462                    result1
463                }
464            }
465        };
466        info!(super_block:?, current_super_block:?; "loaded super-block");
467        *self.next_instance.lock() = current_super_block.next();
468        Ok((super_block, root_parent))
469    }
470
471    /// Writes the provided superblock and root_parent ObjectStore to the device.
472    /// Requires that the filesystem is fully loaded and writable as this may require allocation.
473    pub async fn save(
474        &self,
475        super_block_header: SuperBlockHeader,
476        filesystem: Arc<FxFilesystem>,
477        root_parent: LayerSet<ObjectKey, ObjectValue>,
478    ) -> Result<(), Error> {
479        let root_store = filesystem.root_store();
480        let object_id = {
481            let mut next_instance = self.next_instance.lock();
482            let object_id = next_instance.object_id();
483            *next_instance = next_instance.next();
484            object_id
485        };
486        let handle = ObjectStore::open_object(
487            &root_store,
488            object_id,
489            HandleOptions { skip_journal_checks: true, ..Default::default() },
490            None,
491        )
492        .await
493        .context("Failed to open superblock object")?;
494        write(&super_block_header, root_parent, handle).await?;
495        self.metrics
496            .last_super_block_offset
497            .set(super_block_header.super_block_journal_file_offset);
498        self.metrics.last_super_block_update_time_ms.set(
499            SystemTime::now()
500                .duration_since(SystemTime::UNIX_EPOCH)
501                .unwrap()
502                .as_millis()
503                .try_into()
504                .unwrap_or(0u64),
505        );
506        Ok(())
507    }
508}
509
510impl SuperBlockHeader {
511    /// Creates a new instance with random GUID.
512    pub fn new(
513        generation: u64,
514        root_parent_store_object_id: u64,
515        root_parent_graveyard_directory_object_id: u64,
516        root_store_object_id: u64,
517        allocator_object_id: u64,
518        journal_object_id: u64,
519        journal_checkpoint: JournalCheckpoint,
520        earliest_version: Version,
521    ) -> Self {
522        SuperBlockHeader {
523            guid: UuidWrapper::new(),
524            generation,
525            root_parent_store_object_id,
526            root_parent_graveyard_directory_object_id,
527            root_store_object_id,
528            allocator_object_id,
529            journal_object_id,
530            journal_checkpoint,
531            earliest_version,
532            ..Default::default()
533        }
534    }
535
536    /// Read the super-block header, and return it and a reader that produces the records that are
537    /// to be replayed in to the root parent object store.
538    async fn read_header(
539        device: Arc<dyn Device>,
540        target_super_block: SuperBlockInstance,
541    ) -> Result<(SuperBlockHeader, RecordReader), Error> {
542        let handle = BootstrapObjectHandle::new(
543            target_super_block.object_id(),
544            device,
545            target_super_block.first_extent(),
546        );
547        let mut reader = JournalReader::new(handle, &JournalCheckpoint::default());
548        reader.set_eof_ok();
549
550        reader.fill_buf().await?;
551
552        let mut super_block_header;
553        let super_block_version;
554        reader.consume({
555            let mut cursor = std::io::Cursor::new(reader.buffer());
556            // Validate magic bytes.
557            let mut magic_bytes: [u8; 8] = [0; 8];
558            cursor.read_exact(&mut magic_bytes)?;
559            if magic_bytes.as_slice() != SUPER_BLOCK_MAGIC.as_slice() {
560                bail!("Invalid magic: {:?}", magic_bytes);
561            }
562            (super_block_header, super_block_version) =
563                SuperBlockHeader::deserialize_with_version(&mut cursor)?;
564
565            if super_block_version < EARLIEST_SUPPORTED_VERSION {
566                bail!("Unsupported SuperBlock version: {:?}", super_block_version);
567            }
568
569            // NOTE: It is possible that data was written to the journal with an old version
570            // but no compaction ever happened, so the journal version could potentially be older
571            // than the layer file versions.
572            if super_block_header.journal_checkpoint.version < EARLIEST_SUPPORTED_VERSION {
573                bail!(
574                    "Unsupported JournalCheckpoint version: {:?}",
575                    super_block_header.journal_checkpoint.version
576                );
577            }
578
579            if super_block_header.earliest_version < EARLIEST_SUPPORTED_VERSION {
580                bail!(
581                    "Filesystem contains struct with unsupported version: {:?}",
582                    super_block_header.earliest_version
583                );
584            }
585
586            cursor.position() as usize
587        });
588
589        // From version 45 superblocks describe their own extents (a noop here).
590        // At version 44, superblocks assume a 4kb first extent.
591        // Prior to version 44, superblocks assume a 512kb first extent.
592        if super_block_version < SMALL_SUPERBLOCK_VERSION {
593            reader.handle().push_extent(0, target_super_block.legacy_first_extent());
594        } else if super_block_version < FIRST_EXTENT_IN_SUPERBLOCK_VERSION {
595            reader.handle().push_extent(0, target_super_block.first_extent())
596        }
597
598        // If guid is zeroed (e.g. in a newly imaged system), assign one randomly.
599        if super_block_header.guid.0.is_nil() {
600            super_block_header.guid = UuidWrapper::new();
601        }
602        reader.set_version(super_block_version);
603        Ok((super_block_header, RecordReader { reader }))
604    }
605}
606
607struct SuperBlockWriter<'a, S: HandleOwner> {
608    handle: DataObjectHandle<S>,
609    writer: JournalWriter,
610    existing_extents: VecDeque<FileExtent>,
611    size: u64,
612    reservation: &'a Reservation,
613}
614
615impl<'a, S: HandleOwner> SuperBlockWriter<'a, S> {
616    /// Create a new writer, outputs FXFS magic, version and SuperBlockHeader.
617    /// On success, the writer is ready to accept root parent store mutations.
618    pub async fn new(
619        handle: DataObjectHandle<S>,
620        super_block_header: &SuperBlockHeader,
621        reservation: &'a Reservation,
622    ) -> Result<Self, Error> {
623        let existing_extents = handle.device_extents().await?;
624        let mut this = Self {
625            handle,
626            writer: JournalWriter::new(BLOCK_SIZE as usize, 0),
627            existing_extents: existing_extents.into_iter().collect(),
628            size: 0,
629            reservation,
630        };
631        this.writer.write_all(SUPER_BLOCK_MAGIC)?;
632        super_block_header.serialize_with_version(&mut this.writer)?;
633        Ok(this)
634    }
635
636    /// Internal helper function to pull ranges from a list of existing extents and tack
637    /// corresponding extent records onto the journal.
638    fn try_extend_existing(&mut self, target_size: u64) -> Result<(), Error> {
639        while self.size < target_size {
640            if let Some(extent) = self.existing_extents.pop_front() {
641                ensure!(
642                    extent.logical_range().start == self.size,
643                    "superblock file contains a hole."
644                );
645                self.size += extent.length();
646                SuperBlockRecord::Extent(extent.device_range().clone())
647                    .serialize_into(&mut self.writer)?;
648            } else {
649                break;
650            }
651        }
652        Ok(())
653    }
654
655    pub async fn write_root_parent_item(&mut self, record: ObjectItem) -> Result<(), Error> {
656        let min_len = self.writer.journal_file_checkpoint().file_offset + SUPER_BLOCK_CHUNK_SIZE;
657        self.try_extend_existing(min_len)?;
658        if min_len > self.size {
659            // Need to allocate some more space.
660            let mut transaction = self
661                .handle
662                .new_transaction_with_options(Options {
663                    skip_journal_checks: true,
664                    borrow_metadata_space: true,
665                    allocator_reservation: Some(self.reservation),
666                    ..Default::default()
667                })
668                .await?;
669            let mut file_range = self.size..self.size + SUPER_BLOCK_CHUNK_SIZE;
670            let allocated = self
671                .handle
672                .preallocate_range(&mut transaction, &mut file_range)
673                .await
674                .context("preallocate superblock")?;
675            if file_range.start < file_range.end {
676                bail!("preallocate_range returned too little space");
677            }
678            transaction.commit().await?;
679            for device_range in allocated {
680                self.size += device_range.end - device_range.start;
681                SuperBlockRecord::Extent(device_range).serialize_into(&mut self.writer)?;
682            }
683        }
684        SuperBlockRecord::ObjectItem(record).serialize_into(&mut self.writer)?;
685        Ok(())
686    }
687
688    pub async fn finalize(mut self) -> Result<(), Error> {
689        SuperBlockRecord::End.serialize_into(&mut self.writer)?;
690        self.writer.pad_to_block()?;
691        let mut buf = self.handle.allocate_buffer(self.writer.flushable_bytes()).await;
692        let offset = self.writer.take_flushable(buf.as_mut());
693        self.handle.overwrite(offset, buf.as_mut(), OverwriteOptions::default()).await?;
694        let len =
695            std::cmp::max(MIN_SUPER_BLOCK_SIZE, self.writer.journal_file_checkpoint().file_offset)
696                + SUPER_BLOCK_CHUNK_SIZE;
697        self.handle
698            .truncate_with_options(
699                Options {
700                    skip_journal_checks: true,
701                    borrow_metadata_space: true,
702                    ..Default::default()
703                },
704                len,
705            )
706            .await?;
707        Ok(())
708    }
709}
710
711pub struct RecordReader {
712    reader: JournalReader,
713}
714
715impl RecordReader {
716    pub async fn next_item(&mut self) -> Result<SuperBlockRecord, Error> {
717        loop {
718            match self.reader.deserialize().await? {
719                ReadResult::Reset(_) => bail!("Unexpected reset"),
720                ReadResult::ChecksumMismatch => bail!("Checksum mismatch"),
721                ReadResult::Some(SuperBlockRecord::Extent(extent)) => {
722                    ensure!(extent.is_valid(), FxfsError::Inconsistent);
723                    self.reader.handle().push_extent(0, extent)
724                }
725                ReadResult::Some(x) => return Ok(x),
726            }
727        }
728    }
729}
730
731#[cfg(test)]
732mod tests {
733    use super::{
734        MIN_SUPER_BLOCK_SIZE, SUPER_BLOCK_CHUNK_SIZE, SUPER_BLOCK_MAGIC, SuperBlockHeader,
735        SuperBlockInstance, SuperBlockManager, SuperBlockRecord, UuidWrapper, compact_root_parent,
736        write,
737    };
738    use crate::filesystem::{FxFilesystem, OpenFxFilesystem, SyncOptions};
739    use crate::object_handle::ReadObjectHandle;
740    use crate::object_store::journal::JournalCheckpoint;
741    use crate::object_store::journal::writer::JournalWriter;
742    use crate::object_store::transaction::{Options, lock_keys};
743    use crate::object_store::{
744        DataObjectHandle, HandleOptions, ObjectHandle, ObjectKey, ObjectStore,
745    };
746    use crate::serialized_types::{LATEST_VERSION, Versioned, VersionedLatest};
747    use std::io::Write;
748    use storage_device::DeviceHolder;
749    use storage_device::fake_device::FakeDevice;
750
751    // We require 512kiB each for A/B super-blocks, 256kiB for the journal (128kiB before flush)
752    // and compactions require double the layer size to complete.
753    const TEST_DEVICE_BLOCK_SIZE: u32 = 512;
754    const TEST_DEVICE_BLOCK_COUNT: u64 = 16384;
755
756    async fn filesystem_and_super_block_handles()
757    -> (OpenFxFilesystem, DataObjectHandle<ObjectStore>, DataObjectHandle<ObjectStore>) {
758        let device =
759            DeviceHolder::new(FakeDevice::new(TEST_DEVICE_BLOCK_COUNT, TEST_DEVICE_BLOCK_SIZE));
760        let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
761        fs.close().await.expect("Close failed");
762        let device = fs.take_device().await;
763        device.reopen(false);
764        let fs = FxFilesystem::open(device).await.expect("open failed");
765
766        let handle_a = ObjectStore::open_object(
767            &fs.object_manager().root_store(),
768            SuperBlockInstance::A.object_id(),
769            HandleOptions::default(),
770            None,
771        )
772        .await
773        .expect("open superblock failed");
774
775        let handle_b = ObjectStore::open_object(
776            &fs.object_manager().root_store(),
777            SuperBlockInstance::B.object_id(),
778            HandleOptions::default(),
779            None,
780        )
781        .await
782        .expect("open superblock failed");
783        (fs, handle_a, handle_b)
784    }
785
786    #[fuchsia::test]
787    async fn test_read_written_super_block() {
788        let (fs, _handle_a, _handle_b) = filesystem_and_super_block_handles().await;
789        const JOURNAL_OBJECT_ID: u64 = 5;
790
791        // Confirm that the (first) super-block is expected size.
792        // It should be MIN_SUPER_BLOCK_SIZE + SUPER_BLOCK_CHUNK_SIZE.
793        assert_eq!(
794            ObjectStore::open_object(
795                &fs.root_store(),
796                SuperBlockInstance::A.object_id(),
797                HandleOptions::default(),
798                None,
799            )
800            .await
801            .expect("open_object failed")
802            .get_size(),
803            MIN_SUPER_BLOCK_SIZE + SUPER_BLOCK_CHUNK_SIZE
804        );
805
806        // Create a large number of objects in the root parent store so that we test growing
807        // of the super-block file, requiring us to add extents.
808        let mut created_object_ids = vec![];
809        const NUM_ENTRIES: u64 = 16384;
810        for _ in 0..NUM_ENTRIES {
811            let mut transaction = fs
812                .clone()
813                .new_transaction(lock_keys![], Options::default())
814                .await
815                .expect("new_transaction failed");
816            created_object_ids.push(
817                ObjectStore::create_object(
818                    &fs.object_manager().root_parent_store(),
819                    &mut transaction,
820                    HandleOptions::default(),
821                    None,
822                )
823                .await
824                .expect("create_object failed")
825                .object_id(),
826            );
827            transaction.commit().await.expect("commit failed");
828        }
829
830        // Note here that DataObjectHandle caches the size given to it at construction.
831        // If we want to know the true size after a super-block has been written, we need
832        // a new handle.
833        assert!(
834            ObjectStore::open_object(
835                &fs.root_store(),
836                SuperBlockInstance::A.object_id(),
837                HandleOptions::default(),
838                None,
839            )
840            .await
841            .expect("open_object failed")
842            .get_size()
843                > MIN_SUPER_BLOCK_SIZE + SUPER_BLOCK_CHUNK_SIZE
844        );
845
846        let written_super_block_a =
847            SuperBlockHeader::read_header(fs.device(), SuperBlockInstance::A)
848                .await
849                .expect("read failed");
850        let written_super_block_b =
851            SuperBlockHeader::read_header(fs.device(), SuperBlockInstance::B)
852                .await
853                .expect("read failed");
854
855        // Check that a non-zero GUID has been assigned.
856        assert!(!written_super_block_a.0.guid.0.is_nil());
857
858        // Depending on specific offsets is fragile so we just validate the fields we believe
859        // to be stable.
860        assert_eq!(written_super_block_a.0.guid, written_super_block_b.0.guid);
861        assert_eq!(written_super_block_a.0.guid, written_super_block_b.0.guid);
862        assert!(written_super_block_a.0.generation != written_super_block_b.0.generation);
863        assert_eq!(
864            written_super_block_a.0.root_parent_store_object_id,
865            written_super_block_b.0.root_parent_store_object_id
866        );
867        assert_eq!(
868            written_super_block_a.0.root_parent_graveyard_directory_object_id,
869            written_super_block_b.0.root_parent_graveyard_directory_object_id
870        );
871        assert_eq!(written_super_block_a.0.root_store_object_id, fs.root_store().store_object_id());
872        assert_eq!(
873            written_super_block_a.0.root_store_object_id,
874            written_super_block_b.0.root_store_object_id
875        );
876        assert_eq!(written_super_block_a.0.allocator_object_id, fs.allocator().object_id());
877        assert_eq!(
878            written_super_block_a.0.allocator_object_id,
879            written_super_block_b.0.allocator_object_id
880        );
881        assert_eq!(written_super_block_a.0.journal_object_id, JOURNAL_OBJECT_ID);
882        assert_eq!(
883            written_super_block_a.0.journal_object_id,
884            written_super_block_b.0.journal_object_id
885        );
886        assert!(
887            written_super_block_a.0.journal_checkpoint.file_offset
888                != written_super_block_b.0.journal_checkpoint.file_offset
889        );
890        assert!(
891            written_super_block_a.0.super_block_journal_file_offset
892                != written_super_block_b.0.super_block_journal_file_offset
893        );
894        // Nb: We skip journal_file_offsets and borrowed metadata space checks.
895        assert_eq!(written_super_block_a.0.earliest_version, LATEST_VERSION);
896        assert_eq!(
897            written_super_block_a.0.earliest_version,
898            written_super_block_b.0.earliest_version
899        );
900
901        // Nb: Skip comparison of root_parent store contents because we have no way of anticipating
902        // the extent offsets and it is reasonable that a/b differ.
903
904        // Delete all the objects we just made.
905        for object_id in created_object_ids {
906            let mut transaction = fs
907                .clone()
908                .new_transaction(lock_keys![], Options::default())
909                .await
910                .expect("new_transaction failed");
911            fs.object_manager()
912                .root_parent_store()
913                .adjust_refs(&mut transaction, object_id, -1)
914                .await
915                .expect("adjust_refs failed");
916            transaction.commit().await.expect("commit failed");
917            fs.object_manager()
918                .root_parent_store()
919                .tombstone_object(object_id, Options::default())
920                .await
921                .expect("tombstone failed");
922        }
923        // Write some stuff to the root store to ensure we rotate the journal and produce new
924        // super blocks.
925        for _ in 0..NUM_ENTRIES {
926            let mut transaction = fs
927                .clone()
928                .new_transaction(lock_keys![], Options::default())
929                .await
930                .expect("new_transaction failed");
931            ObjectStore::create_object(
932                &fs.object_manager().root_store(),
933                &mut transaction,
934                HandleOptions::default(),
935                None,
936            )
937            .await
938            .expect("create_object failed");
939            transaction.commit().await.expect("commit failed");
940        }
941
942        assert_eq!(
943            ObjectStore::open_object(
944                &fs.root_store(),
945                SuperBlockInstance::A.object_id(),
946                HandleOptions::default(),
947                None,
948            )
949            .await
950            .expect("open_object failed")
951            .get_size(),
952            MIN_SUPER_BLOCK_SIZE + SUPER_BLOCK_CHUNK_SIZE
953        );
954    }
955
956    #[fuchsia::test]
957    async fn test_generation_comparison_wrapping() {
958        let device = DeviceHolder::new(FakeDevice::new(
959            TEST_DEVICE_BLOCK_COUNT,
960            MIN_SUPER_BLOCK_SIZE as u32,
961        ));
962        let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
963        fs.close().await.expect("close");
964        let device = fs.take_device().await;
965        device.reopen(false);
966
967        // Helper to write a superblock with a specific generation to a specific instance.
968        // We need to clone the inner Arc to pass to the closure.
969        let device_arc = (*device).clone();
970        let write_sb = |instance: SuperBlockInstance, generation: u64| {
971            let device = device_arc.clone();
972            async move {
973                let mut super_block_header = SuperBlockHeader::new(
974                    1, // generation
975                    3, // root_parent_store_object_id
976                    4, // root_parent_graveyard_directory_object_id
977                    5, // root_store_object_id
978                    6, // allocator_object_id
979                    7, // journal_object_id
980                    JournalCheckpoint::default(),
981                    LATEST_VERSION,
982                );
983                super_block_header.generation = generation;
984                super_block_header.journal_checkpoint.version = LATEST_VERSION;
985
986                let mut writer = JournalWriter::new(MIN_SUPER_BLOCK_SIZE as usize, 0);
987                writer.write_all(SUPER_BLOCK_MAGIC).unwrap();
988                super_block_header.serialize_with_version(&mut writer).unwrap();
989                SuperBlockRecord::End.serialize_into(&mut writer).unwrap();
990                writer.pad_to_block().unwrap();
991
992                let mut buf = device.allocate_buffer(writer.flushable_bytes()).await;
993                writer.take_flushable(buf.as_mut());
994                device
995                    .write(instance.first_extent().start, buf.as_ref())
996                    .await
997                    .expect("write failed");
998            }
999        };
1000
1001        // Case 1: A has MAX, B has 0. B should be selected.
1002        write_sb(SuperBlockInstance::A, u64::MAX).await;
1003        write_sb(SuperBlockInstance::B, 0).await;
1004        let manager = SuperBlockManager::new();
1005        let (header, _) = manager
1006            .load((*device).clone(), MIN_SUPER_BLOCK_SIZE as u64)
1007            .await
1008            .expect("load failed");
1009        assert_eq!(header.generation, 0);
1010
1011        // Case 2: A has 0, B has MAX. A should be selected.
1012        write_sb(SuperBlockInstance::A, 0).await;
1013        write_sb(SuperBlockInstance::B, u64::MAX).await;
1014        let manager = SuperBlockManager::new();
1015        let (header, _) = manager
1016            .load((*device).clone(), MIN_SUPER_BLOCK_SIZE as u64)
1017            .await
1018            .expect("load failed");
1019        assert_eq!(header.generation, 0);
1020
1021        // Case 3: A has 100, B has 200. B should be selected.
1022        write_sb(SuperBlockInstance::A, 100).await;
1023        write_sb(SuperBlockInstance::B, 200).await;
1024        let manager = SuperBlockManager::new();
1025        let (header, _) = manager
1026            .load((*device).clone(), MIN_SUPER_BLOCK_SIZE as u64)
1027            .await
1028            .expect("load failed");
1029        assert_eq!(header.generation, 200);
1030    }
1031
1032    #[fuchsia::test]
1033    async fn test_generation_wrapping_on_flush() {
1034        let block_size = 4096;
1035        let mut device =
1036            DeviceHolder::new(FakeDevice::new(TEST_DEVICE_BLOCK_COUNT, block_size as u32));
1037        {
1038            let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
1039            let root_store = fs.root_store();
1040            let mut transaction = fs
1041                .clone()
1042                .new_transaction(lock_keys![], Options::default())
1043                .await
1044                .expect("new_transaction failed");
1045            ObjectStore::create_object(
1046                &root_store,
1047                &mut transaction,
1048                HandleOptions::default(),
1049                None,
1050            )
1051            .await
1052            .expect("create_object failed");
1053            transaction.commit().await.expect("commit failed");
1054            fs.sync(SyncOptions::default()).await.expect("sync failed");
1055            fs.close().await.expect("close failed");
1056            device = fs.take_device().await;
1057        }
1058        device.reopen(false);
1059
1060        let manager = SuperBlockManager::new();
1061        let (mut header, _) =
1062            manager.load((*device).clone(), block_size as u64).await.expect("load failed");
1063
1064        {
1065            let fs = FxFilesystem::open(device).await.expect("open failed");
1066            // To test wrapping, we need to get into a state where the current generation is
1067            // u64::MAX. Since we have A and B, and wrapping comparison is used, we need to set them
1068            // up carefully. We will set A to u64::MAX - 1, and B to u64::MAX.
1069            // Then the next write will be to A, and should be 0.
1070            header.generation = u64::MAX - 1;
1071            manager
1072                .save(header.clone(), (*fs).clone(), fs.root_parent_store().tree().layer_set())
1073                .await
1074                .expect("save 1 failed");
1075            header.generation = u64::MAX;
1076            manager
1077                .save(header, (*fs).clone(), fs.root_parent_store().tree().layer_set())
1078                .await
1079                .expect("save 2 failed");
1080            fs.close().await.expect("close failed");
1081            device = fs.take_device().await;
1082            device.reopen(false);
1083
1084            let fs = FxFilesystem::open(device).await.expect("open failed");
1085
1086            let root_store = fs.root_store();
1087            for _ in 0..6000 {
1088                let mut transaction = fs
1089                    .clone()
1090                    .new_transaction(lock_keys![], Options::default())
1091                    .await
1092                    .expect("new_transaction failed");
1093                ObjectStore::create_object(
1094                    &root_store,
1095                    &mut transaction,
1096                    HandleOptions::default(),
1097                    None,
1098                )
1099                .await
1100                .expect("create_object failed");
1101                transaction.commit().await.expect("commit failed");
1102            }
1103            fs.sync(SyncOptions::default()).await.expect("sync failed");
1104            fs.close().await.expect("close failed");
1105            device = fs.take_device().await;
1106        }
1107        device.reopen(false);
1108
1109        let (header, _) =
1110            manager.load((*device).clone(), block_size as u64).await.expect("load failed");
1111        assert!(header.generation < 10);
1112    }
1113
1114    #[fuchsia::test]
1115    async fn test_guid_assign_on_read() {
1116        let (fs, handle_a, _handle_b) = filesystem_and_super_block_handles().await;
1117        const JOURNAL_OBJECT_ID: u64 = 5;
1118        let mut super_block_header_a = SuperBlockHeader::new(
1119            1,
1120            fs.object_manager().root_parent_store().store_object_id(),
1121            /* root_parent_graveyard_directory_object_id: */ 1000,
1122            fs.root_store().store_object_id(),
1123            fs.allocator().object_id(),
1124            JOURNAL_OBJECT_ID,
1125            JournalCheckpoint { file_offset: 1234, checksum: 5678, version: LATEST_VERSION },
1126            /* earliest_version: */ LATEST_VERSION,
1127        );
1128        // Ensure the superblock has no set GUID.
1129        super_block_header_a.guid = UuidWrapper::nil();
1130        write(
1131            &super_block_header_a,
1132            compact_root_parent(fs.object_manager().root_parent_store().as_ref())
1133                .expect("scan failed"),
1134            handle_a,
1135        )
1136        .await
1137        .expect("write failed");
1138        let super_block_header = SuperBlockHeader::read_header(fs.device(), SuperBlockInstance::A)
1139            .await
1140            .expect("read failed");
1141        // Ensure a GUID has been assigned.
1142        assert!(!super_block_header.0.guid.0.is_nil());
1143    }
1144
1145    #[fuchsia::test]
1146    async fn test_init_wipes_superblocks() {
1147        let device = DeviceHolder::new(FakeDevice::new(8192, TEST_DEVICE_BLOCK_SIZE));
1148
1149        let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
1150        let root_store = fs.root_store();
1151        // Generate enough work to induce a journal flush and thus a new superblock being written.
1152        for _ in 0..6000 {
1153            let mut transaction = fs
1154                .clone()
1155                .new_transaction(lock_keys![], Options::default())
1156                .await
1157                .expect("new_transaction failed");
1158            ObjectStore::create_object(
1159                &root_store,
1160                &mut transaction,
1161                HandleOptions::default(),
1162                None,
1163            )
1164            .await
1165            .expect("create_object failed");
1166            transaction.commit().await.expect("commit failed");
1167        }
1168        fs.close().await.expect("Close failed");
1169        let device = fs.take_device().await;
1170        device.reopen(false);
1171
1172        SuperBlockHeader::read_header(device.clone(), SuperBlockInstance::A)
1173            .await
1174            .expect("read failed");
1175        let header = SuperBlockHeader::read_header(device.clone(), SuperBlockInstance::B)
1176            .await
1177            .expect("read failed");
1178
1179        let old_guid = header.0.guid;
1180
1181        // Re-initialize the filesystem.  The A and B blocks should be for the new FS.
1182        let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
1183        fs.close().await.expect("Close failed");
1184        let device = fs.take_device().await;
1185        device.reopen(false);
1186
1187        let a = SuperBlockHeader::read_header(device.clone(), SuperBlockInstance::A)
1188            .await
1189            .expect("read failed");
1190        let b = SuperBlockHeader::read_header(device.clone(), SuperBlockInstance::B)
1191            .await
1192            .expect("read failed");
1193
1194        assert_eq!(a.0.guid, b.0.guid);
1195        assert_ne!(old_guid, a.0.guid);
1196    }
1197
1198    #[fuchsia::test]
1199    async fn test_alternating_super_blocks() {
1200        let device = DeviceHolder::new(FakeDevice::new(8192, TEST_DEVICE_BLOCK_SIZE));
1201
1202        let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
1203        fs.close().await.expect("Close failed");
1204        let device = fs.take_device().await;
1205        device.reopen(false);
1206
1207        let (super_block_header_a, _) =
1208            SuperBlockHeader::read_header(device.clone(), SuperBlockInstance::A)
1209                .await
1210                .expect("read failed");
1211
1212        // The second super-block won't be valid at this time so there's no point reading it.
1213
1214        let fs = FxFilesystem::open(device).await.expect("open failed");
1215        let root_store = fs.root_store();
1216        // Generate enough work to induce a journal flush.
1217        for _ in 0..6000 {
1218            let mut transaction = fs
1219                .clone()
1220                .new_transaction(lock_keys![], Options::default())
1221                .await
1222                .expect("new_transaction failed");
1223            ObjectStore::create_object(
1224                &root_store,
1225                &mut transaction,
1226                HandleOptions::default(),
1227                None,
1228            )
1229            .await
1230            .expect("create_object failed");
1231            transaction.commit().await.expect("commit failed");
1232        }
1233        fs.close().await.expect("Close failed");
1234        let device = fs.take_device().await;
1235        device.reopen(false);
1236
1237        let (super_block_header_a_after, _) =
1238            SuperBlockHeader::read_header(device.clone(), SuperBlockInstance::A)
1239                .await
1240                .expect("read failed");
1241        let (super_block_header_b_after, _) =
1242            SuperBlockHeader::read_header(device.clone(), SuperBlockInstance::B)
1243                .await
1244                .expect("read failed");
1245
1246        // It's possible that multiple super-blocks were written, so cater for that.
1247
1248        // The generations should be one apart.
1249        assert_eq!(
1250            (super_block_header_b_after.generation as i64
1251                - super_block_header_a_after.generation as i64)
1252                .abs(),
1253            1
1254        );
1255
1256        // At least one super-block should have been written.
1257        assert!(
1258            std::cmp::max(
1259                super_block_header_a_after.generation,
1260                super_block_header_b_after.generation
1261            ) > super_block_header_a.generation
1262        );
1263
1264        // They should have the same oddness.
1265        assert_eq!(super_block_header_a_after.generation & 1, super_block_header_a.generation & 1);
1266    }
1267
1268    #[fuchsia::test]
1269    async fn test_root_parent_is_compacted() {
1270        let device = DeviceHolder::new(FakeDevice::new(8192, TEST_DEVICE_BLOCK_SIZE));
1271
1272        let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
1273
1274        let mut transaction = fs
1275            .clone()
1276            .new_transaction(lock_keys![], Options::default())
1277            .await
1278            .expect("new_transaction failed");
1279        let store = fs.root_parent_store();
1280        let handle =
1281            ObjectStore::create_object(&store, &mut transaction, HandleOptions::default(), None)
1282                .await
1283                .expect("create_object failed");
1284        transaction.commit().await.expect("commit failed");
1285
1286        store
1287            .tombstone_object(handle.object_id(), Options::default())
1288            .await
1289            .expect("tombstone failed");
1290
1291        // Generate enough work to induce a journal flush.
1292        let root_store = fs.root_store();
1293        for _ in 0..6000 {
1294            let mut transaction = fs
1295                .clone()
1296                .new_transaction(lock_keys![], Options::default())
1297                .await
1298                .expect("new_transaction failed");
1299            ObjectStore::create_object(
1300                &root_store,
1301                &mut transaction,
1302                HandleOptions::default(),
1303                None,
1304            )
1305            .await
1306            .expect("create_object failed");
1307            transaction.commit().await.expect("commit failed");
1308        }
1309
1310        // The root parent store should have been compacted, so we shouldn't be able to find any
1311        // record referring to the object we tombstoned.
1312        assert_eq!(
1313            store.tree().find(&ObjectKey::object(handle.object_id())).await.expect("find failed"),
1314            None
1315        );
1316    }
1317}