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, ObjectItemV56,
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::{HashSet, 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 = SuperBlockRecordV56;
225
226#[allow(clippy::large_enum_variant)]
227#[derive(Debug, Serialize, Deserialize, TypeFingerprint, Versioned)]
228pub enum SuperBlockRecordV56 {
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(ObjectItemV56),
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(SuperBlockRecordV56)]
244#[migrate_nodefault]
245pub enum SuperBlockRecordV55 {
246    Extent(Range<u64>),
247    ObjectItem(ObjectItemV55),
248    End,
249}
250
251#[allow(clippy::large_enum_variant)]
252#[derive(Migrate, Debug, Serialize, Deserialize, TypeFingerprint, Versioned)]
253#[migrate_to_version(SuperBlockRecordV55)]
254#[migrate_nodefault]
255pub enum SuperBlockRecordV54 {
256    Extent(Range<u64>),
257    ObjectItem(crate::object_store::object_record::ObjectItemV54),
258    End,
259}
260
261#[allow(clippy::large_enum_variant)]
262#[derive(Migrate, Serialize, Deserialize, TypeFingerprint, Versioned)]
263#[migrate_to_version(SuperBlockRecordV54)]
264#[migrate_nodefault]
265pub enum SuperBlockRecordV50 {
266    Extent(Range<u64>),
267    ObjectItem(ObjectItemV50),
268    End,
269}
270
271#[allow(clippy::large_enum_variant)]
272#[derive(Migrate, Serialize, Deserialize, TypeFingerprint, Versioned)]
273#[migrate_to_version(SuperBlockRecordV50)]
274pub enum SuperBlockRecordV49 {
275    Extent(Range<u64>),
276    ObjectItem(ObjectItemV49),
277    End,
278}
279
280#[allow(clippy::large_enum_variant)]
281#[derive(Migrate, Serialize, Deserialize, TypeFingerprint, Versioned)]
282#[migrate_to_version(SuperBlockRecordV49)]
283pub enum SuperBlockRecordV47 {
284    Extent(Range<u64>),
285    ObjectItem(ObjectItemV47),
286    End,
287}
288
289#[allow(clippy::large_enum_variant)]
290#[derive(Migrate, Serialize, Deserialize, TypeFingerprint, Versioned)]
291#[migrate_to_version(SuperBlockRecordV47)]
292pub enum SuperBlockRecordV46 {
293    Extent(Range<u64>),
294    ObjectItem(ObjectItemV46),
295    End,
296}
297
298#[allow(clippy::large_enum_variant)]
299#[derive(Migrate, Serialize, Deserialize, TypeFingerprint, Versioned)]
300#[migrate_to_version(SuperBlockRecordV46)]
301pub enum SuperBlockRecordV43 {
302    Extent(Range<u64>),
303    ObjectItem(ObjectItemV43),
304    End,
305}
306
307#[derive(Migrate, Serialize, Deserialize, TypeFingerprint, Versioned)]
308#[migrate_to_version(SuperBlockRecordV43)]
309pub enum SuperBlockRecordV41 {
310    Extent(Range<u64>),
311    ObjectItem(ObjectItemV41),
312    End,
313}
314
315#[derive(Migrate, Serialize, Deserialize, TypeFingerprint, Versioned)]
316#[migrate_to_version(SuperBlockRecordV41)]
317pub enum SuperBlockRecordV40 {
318    Extent(Range<u64>),
319    ObjectItem(ObjectItemV40),
320    End,
321}
322
323struct SuperBlockMetrics {
324    /// Time we wrote the most recent superblock in milliseconds since [`std::time::UNIX_EPOCH`].
325    /// Uses [`std::time::SystemTime`] as the clock source.
326    last_super_block_update_time_ms: UintProperty,
327
328    /// Offset of the most recent superblock we wrote in the journal.
329    last_super_block_offset: UintProperty,
330}
331
332impl Default for SuperBlockMetrics {
333    fn default() -> Self {
334        SuperBlockMetrics {
335            last_super_block_update_time_ms: metrics::detail()
336                .create_uint("last_super_block_update_time_ms", 0),
337            last_super_block_offset: metrics::detail().create_uint("last_super_block_offset", 0),
338        }
339    }
340}
341
342/// Reads an individual (A/B) super-block instance and root_parent_store from device.
343/// Users should use SuperBlockManager::load() instead.
344async fn read(
345    device: Arc<dyn Device>,
346    block_size: u64,
347    instance: SuperBlockInstance,
348) -> Result<(SuperBlockHeader, SuperBlockInstance, ObjectStore), Error> {
349    let (super_block_header, mut reader) = SuperBlockHeader::read_header(device.clone(), instance)
350        .await
351        .context("failed to read superblock")?;
352    let root_parent = ObjectStore::new_root_parent(
353        device,
354        block_size,
355        super_block_header.root_parent_store_object_id,
356    );
357    root_parent.set_graveyard_directory_object_id(
358        super_block_header.root_parent_graveyard_directory_object_id,
359    );
360
361    loop {
362        // TODO: Flatten a layer and move reader here?
363        let mutation = match reader.next_item().await? {
364            // RecordReader should filter out extent records.
365            SuperBlockRecord::Extent(_) => bail!("Unexpected extent record"),
366            SuperBlockRecord::ObjectItem(item) => Mutation::insert_object(item.key, item.value),
367            SuperBlockRecord::End => break,
368        };
369        root_parent.apply_mutation(
370            mutation,
371            &ApplyContext {
372                mode: ApplyMode::Replay,
373                // A file offset of 0 is safe here because this is only used for the root parent
374                // store, which is completely reconstructed from the superblock at each mount,
375                // making the checkpoint offset irrelevant.
376                checkpoint: JournalCheckpoint { file_offset: 0, ..Default::default() },
377            },
378            AssocObj::None,
379        )?;
380    }
381    Ok((super_block_header, instance, root_parent))
382}
383
384/// Write a super-block to the given file handle.
385/// Requires that the filesystem is fully loaded and writable as this may require allocation.
386async fn write<S: HandleOwner>(
387    super_block_header: &SuperBlockHeader,
388    items: LayerSet<ObjectKey, ObjectValue>,
389    handle: DataObjectHandle<S>,
390) -> Result<(), Error> {
391    let object_manager = handle.store().filesystem().object_manager().clone();
392    // TODO(https://fxbug.dev/42177407): Don't use the same code here for Journal and SuperBlock. They
393    // aren't the same things and it is already getting convoluted. e.g of diff stream content:
394    //   Superblock:  (Magic, Ver, Header(Ver), Extent(Ver)*, SuperBlockRecord(Ver)*, ...)
395    //   Journal:     (Ver, JournalRecord(Ver)*, RESET, Ver2, JournalRecord(Ver2)*, ...)
396    // We should abstract away the checksum code and implement these separately.
397
398    let mut writer =
399        SuperBlockWriter::new(handle, super_block_header, object_manager.metadata_reservation())
400            .await?;
401    let mut merger = items.merger();
402    let mut iter = LSMTree::major_iter(merger.query(Query::FullScan).await?).await?;
403    while let Some(item) = iter.get() {
404        writer.write_root_parent_item(item.cloned()).await?;
405        iter.advance().await?;
406    }
407    writer.finalize().await
408}
409
410// Compacts and returns the *old* snapshot of the root_parent store.
411// Must be performed whilst holding a writer lock.
412pub fn compact_root_parent(
413    root_parent_store: &ObjectStore,
414) -> Result<LayerSet<ObjectKey, ObjectValue>, Error> {
415    // The root parent always uses in-memory layers which shouldn't be async, so we can use
416    // `now_or_never`.
417    let tree = root_parent_store.tree();
418    let layer_set = tree.layer_set();
419    {
420        let mut merger = layer_set.merger();
421        let mut iter = LSMTree::major_iter(merger.query(Query::FullScan).now_or_never().unwrap()?)
422            .now_or_never()
423            .unwrap()?;
424        let new_layer = LSMTree::new_mutable_layer();
425        while let Some(item_ref) = iter.get() {
426            new_layer.insert(item_ref.cloned())?;
427            iter.advance().now_or_never().unwrap()?;
428        }
429        tree.set_mutable_layer(new_layer);
430    }
431    Ok(layer_set)
432}
433
434/// This encapsulates the A/B alternating super-block logic.
435/// All super-block load/save operations should be via the methods on this type.
436pub(super) struct SuperBlockManager {
437    pub next_instance: Mutex<SuperBlockInstance>,
438    metrics: SuperBlockMetrics,
439}
440
441impl SuperBlockManager {
442    pub fn new() -> Self {
443        Self { next_instance: Mutex::new(SuperBlockInstance::A), metrics: Default::default() }
444    }
445
446    /// Loads both A/B super-blocks and root_parent ObjectStores and and returns the newest valid
447    /// pair. Also ensures the next superblock updated via |save| will be the other instance.
448    pub async fn load(
449        &self,
450        device: Arc<dyn Device>,
451        block_size: u64,
452    ) -> Result<(SuperBlockHeader, ObjectStore), Error> {
453        // Superblocks consume a minimum of one block. We currently hard code the length of
454        // this first extent. It should work with larger block sizes, but has not been tested.
455        // TODO(https://fxbug.dev/42063349): Consider relaxing this.
456        debug_assert!(MIN_SUPER_BLOCK_SIZE == block_size);
457
458        let (super_block, current_super_block, root_parent) = match futures::join!(
459            read(device.clone(), block_size, SuperBlockInstance::A),
460            read(device.clone(), block_size, SuperBlockInstance::B)
461        ) {
462            (Err(e1), Err(e2)) => {
463                bail!("Failed to load both superblocks due to {:?}\nand\n{:?}", e1, e2)
464            }
465            (Ok(result), Err(_)) => result,
466            (Err(_), Ok(result)) => result,
467            (Ok(result1), Ok(result2)) => {
468                // Break the tie by taking the super-block with the greatest generation.
469                if (result2.0.generation as i64).wrapping_sub(result1.0.generation as i64) > 0 {
470                    result2
471                } else {
472                    result1
473                }
474            }
475        };
476        info!(super_block:?, current_super_block:?; "loaded super-block");
477        *self.next_instance.lock() = current_super_block.next();
478        Ok((super_block, root_parent))
479    }
480
481    /// Writes the provided superblock and root_parent ObjectStore to the device.
482    /// Requires that the filesystem is fully loaded and writable as this may require allocation.
483    pub async fn save(
484        &self,
485        super_block_header: SuperBlockHeader,
486        filesystem: Arc<FxFilesystem>,
487        root_parent: LayerSet<ObjectKey, ObjectValue>,
488    ) -> Result<(), Error> {
489        let root_store = filesystem.root_store();
490        let object_id = {
491            let mut next_instance = self.next_instance.lock();
492            let object_id = next_instance.object_id();
493            *next_instance = next_instance.next();
494            object_id
495        };
496        let handle = ObjectStore::open_object(
497            &root_store,
498            object_id,
499            HandleOptions { skip_journal_checks: true, ..Default::default() },
500            None,
501        )
502        .await
503        .context("Failed to open superblock object")?;
504        write(&super_block_header, root_parent, handle).await?;
505        self.metrics
506            .last_super_block_offset
507            .set(super_block_header.super_block_journal_file_offset);
508        self.metrics.last_super_block_update_time_ms.set(
509            SystemTime::now()
510                .duration_since(SystemTime::UNIX_EPOCH)
511                .unwrap()
512                .as_millis()
513                .try_into()
514                .unwrap_or(0u64),
515        );
516        Ok(())
517    }
518}
519
520impl SuperBlockHeader {
521    /// Creates a new instance with random GUID.
522    pub fn new(
523        generation: u64,
524        root_parent_store_object_id: u64,
525        root_parent_graveyard_directory_object_id: u64,
526        root_store_object_id: u64,
527        allocator_object_id: u64,
528        journal_object_id: u64,
529        journal_checkpoint: JournalCheckpoint,
530        earliest_version: Version,
531    ) -> Self {
532        SuperBlockHeader {
533            guid: UuidWrapper::new(),
534            generation,
535            root_parent_store_object_id,
536            root_parent_graveyard_directory_object_id,
537            root_store_object_id,
538            allocator_object_id,
539            journal_object_id,
540            journal_checkpoint,
541            earliest_version,
542            ..Default::default()
543        }
544    }
545
546    /// Read the super-block header, and return it and a reader that produces the records that are
547    /// to be replayed in to the root parent object store.
548    async fn read_header(
549        device: Arc<dyn Device>,
550        target_super_block: SuperBlockInstance,
551    ) -> Result<(SuperBlockHeader, RecordReader), Error> {
552        let handle = BootstrapObjectHandle::new(
553            target_super_block.object_id(),
554            device,
555            target_super_block.first_extent(),
556        );
557        let mut reader = JournalReader::new(handle, &JournalCheckpoint::default());
558        reader.set_eof_ok();
559
560        reader.fill_buf().await?;
561
562        let mut super_block_header;
563        let super_block_version;
564        reader.consume({
565            let mut cursor = std::io::Cursor::new(reader.buffer());
566            // Validate magic bytes.
567            let mut magic_bytes: [u8; 8] = [0; 8];
568            cursor.read_exact(&mut magic_bytes)?;
569            if magic_bytes.as_slice() != SUPER_BLOCK_MAGIC.as_slice() {
570                bail!("Invalid magic: {:?}", magic_bytes);
571            }
572            (super_block_header, super_block_version) =
573                SuperBlockHeader::deserialize_with_version(&mut cursor)?;
574
575            // Ensure all store IDs are distinct.
576            let mut stores = HashSet::new();
577            ensure!(
578                stores.insert(super_block_header.root_parent_store_object_id),
579                FxfsError::Inconsistent
580            );
581            ensure!(
582                stores.insert(super_block_header.root_store_object_id),
583                FxfsError::Inconsistent
584            );
585
586            // Ensure all objects in the root parent store are distinct.
587            let mut root_parent_objects = HashSet::new();
588            ensure!(
589                root_parent_objects
590                    .insert(super_block_header.root_parent_graveyard_directory_object_id),
591                FxfsError::Inconsistent
592            );
593            ensure!(
594                root_parent_objects.insert(super_block_header.root_store_object_id),
595                FxfsError::Inconsistent
596            );
597            ensure!(
598                root_parent_objects.insert(super_block_header.journal_object_id),
599                FxfsError::Inconsistent
600            );
601
602            // The allocator (in root_store) cannot match any store ID.
603            ensure!(
604                !stores.contains(&super_block_header.allocator_object_id),
605                FxfsError::Inconsistent
606            );
607
608            if super_block_version < EARLIEST_SUPPORTED_VERSION {
609                bail!("Unsupported SuperBlock version: {:?}", super_block_version);
610            }
611
612            // NOTE: It is possible that data was written to the journal with an old version
613            // but no compaction ever happened, so the journal version could potentially be older
614            // than the layer file versions.
615            if super_block_header.journal_checkpoint.version < EARLIEST_SUPPORTED_VERSION {
616                bail!(
617                    "Unsupported JournalCheckpoint version: {:?}",
618                    super_block_header.journal_checkpoint.version
619                );
620            }
621
622            if super_block_header.earliest_version < EARLIEST_SUPPORTED_VERSION {
623                bail!(
624                    "Filesystem contains struct with unsupported version: {:?}",
625                    super_block_header.earliest_version
626                );
627            }
628
629            cursor.position() as usize
630        });
631
632        // From version 45 superblocks describe their own extents (a noop here).
633        // At version 44, superblocks assume a 4kb first extent.
634        // Prior to version 44, superblocks assume a 512kb first extent.
635        if super_block_version < SMALL_SUPERBLOCK_VERSION {
636            reader.handle().push_extent(0, target_super_block.legacy_first_extent());
637        } else if super_block_version < FIRST_EXTENT_IN_SUPERBLOCK_VERSION {
638            reader.handle().push_extent(0, target_super_block.first_extent())
639        }
640
641        // If guid is zeroed (e.g. in a newly imaged system), assign one randomly.
642        if super_block_header.guid.0.is_nil() {
643            super_block_header.guid = UuidWrapper::new();
644        }
645        reader.set_version(super_block_version);
646        Ok((super_block_header, RecordReader { reader }))
647    }
648}
649
650struct SuperBlockWriter<'a, S: HandleOwner> {
651    handle: DataObjectHandle<S>,
652    writer: JournalWriter,
653    existing_extents: VecDeque<FileExtent>,
654    size: u64,
655    reservation: &'a Reservation,
656}
657
658impl<'a, S: HandleOwner> SuperBlockWriter<'a, S> {
659    /// Create a new writer, outputs FXFS magic, version and SuperBlockHeader.
660    /// On success, the writer is ready to accept root parent store mutations.
661    pub async fn new(
662        handle: DataObjectHandle<S>,
663        super_block_header: &SuperBlockHeader,
664        reservation: &'a Reservation,
665    ) -> Result<Self, Error> {
666        let existing_extents = handle.device_extents().await?;
667        let mut this = Self {
668            handle,
669            writer: JournalWriter::new(BLOCK_SIZE as usize, 0),
670            existing_extents: existing_extents.into_iter().collect(),
671            size: 0,
672            reservation,
673        };
674        this.writer.write_all(SUPER_BLOCK_MAGIC)?;
675        super_block_header.serialize_with_version(&mut this.writer)?;
676        Ok(this)
677    }
678
679    /// Internal helper function to pull ranges from a list of existing extents and tack
680    /// corresponding extent records onto the journal.
681    fn try_extend_existing(&mut self, target_size: u64) -> Result<(), Error> {
682        while self.size < target_size {
683            if let Some(extent) = self.existing_extents.pop_front() {
684                ensure!(
685                    extent.logical_range().start == self.size,
686                    "superblock file contains a hole."
687                );
688                self.size += extent.length();
689                SuperBlockRecord::Extent(extent.device_range().clone())
690                    .serialize_into(&mut self.writer)?;
691            } else {
692                break;
693            }
694        }
695        Ok(())
696    }
697
698    pub async fn write_root_parent_item(&mut self, record: ObjectItem) -> Result<(), Error> {
699        let min_len = self.writer.journal_file_checkpoint().file_offset + SUPER_BLOCK_CHUNK_SIZE;
700        self.try_extend_existing(min_len)?;
701        if min_len > self.size {
702            // Need to allocate some more space.
703            let mut transaction = self
704                .handle
705                .new_transaction_with_options(Options {
706                    skip_journal_checks: true,
707                    borrow_metadata_space: true,
708                    allocator_reservation: Some(self.reservation),
709                    ..Default::default()
710                })
711                .await?;
712            let mut file_range = self.size..self.size + SUPER_BLOCK_CHUNK_SIZE;
713            let allocated = self
714                .handle
715                .preallocate_range(&mut transaction, &mut file_range)
716                .await
717                .context("preallocate superblock")?;
718            if file_range.start < file_range.end {
719                bail!("preallocate_range returned too little space");
720            }
721            transaction.commit().await?;
722            for device_range in allocated {
723                self.size += device_range.end - device_range.start;
724                SuperBlockRecord::Extent(device_range).serialize_into(&mut self.writer)?;
725            }
726        }
727        SuperBlockRecord::ObjectItem(record).serialize_into(&mut self.writer)?;
728        Ok(())
729    }
730
731    pub async fn finalize(mut self) -> Result<(), Error> {
732        SuperBlockRecord::End.serialize_into(&mut self.writer)?;
733        self.writer.pad_to_block()?;
734        let mut buf = self.handle.allocate_buffer(self.writer.flushable_bytes()).await;
735        let offset = self.writer.take_flushable(buf.as_mut());
736        self.handle.overwrite(offset, buf.as_mut(), OverwriteOptions::default()).await?;
737        let len =
738            std::cmp::max(MIN_SUPER_BLOCK_SIZE, self.writer.journal_file_checkpoint().file_offset)
739                + SUPER_BLOCK_CHUNK_SIZE;
740        self.handle
741            .truncate_with_options(
742                Options {
743                    skip_journal_checks: true,
744                    borrow_metadata_space: true,
745                    ..Default::default()
746                },
747                len,
748            )
749            .await?;
750        Ok(())
751    }
752}
753
754pub struct RecordReader {
755    reader: JournalReader,
756}
757
758impl RecordReader {
759    pub async fn next_item(&mut self) -> Result<SuperBlockRecord, Error> {
760        loop {
761            match self.reader.deserialize().await? {
762                ReadResult::Reset(_) => bail!("Unexpected reset"),
763                ReadResult::ChecksumMismatch => bail!("Checksum mismatch"),
764                ReadResult::Some(SuperBlockRecord::Extent(extent)) => {
765                    ensure!(extent.is_valid(), FxfsError::Inconsistent);
766                    self.reader.handle().push_extent(0, extent)
767                }
768                ReadResult::Some(x) => return Ok(x),
769            }
770        }
771    }
772}
773
774#[cfg(test)]
775mod tests {
776    use super::{
777        MIN_SUPER_BLOCK_SIZE, SUPER_BLOCK_CHUNK_SIZE, SUPER_BLOCK_MAGIC, SuperBlockHeader,
778        SuperBlockInstance, SuperBlockManager, SuperBlockRecord, UuidWrapper, compact_root_parent,
779        write,
780    };
781    use crate::filesystem::{FxFilesystem, OpenFxFilesystem, SyncOptions};
782    use crate::object_handle::ReadObjectHandle;
783    use crate::object_store::journal::JournalCheckpoint;
784    use crate::object_store::journal::writer::JournalWriter;
785    use crate::object_store::transaction::{Options, lock_keys};
786    use crate::object_store::{
787        DataObjectHandle, HandleOptions, ObjectHandle, ObjectKey, ObjectStore,
788    };
789    use crate::serialized_types::{LATEST_VERSION, Versioned, VersionedLatest};
790    use std::io::Write;
791    use storage_device::DeviceHolder;
792    use storage_device::fake_device::FakeDevice;
793
794    // We require 512kiB each for A/B super-blocks, 256kiB for the journal (128kiB before flush)
795    // and compactions require double the layer size to complete.
796    const TEST_DEVICE_BLOCK_SIZE: u32 = 512;
797    const TEST_DEVICE_BLOCK_COUNT: u64 = 16384;
798
799    async fn filesystem_and_super_block_handles()
800    -> (OpenFxFilesystem, DataObjectHandle<ObjectStore>, DataObjectHandle<ObjectStore>) {
801        let device =
802            DeviceHolder::new(FakeDevice::new(TEST_DEVICE_BLOCK_COUNT, TEST_DEVICE_BLOCK_SIZE));
803        let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
804        fs.close().await.expect("Close failed");
805        let device = fs.take_device().await;
806        device.reopen(false);
807        let fs = FxFilesystem::open(device).await.expect("open failed");
808
809        let handle_a = ObjectStore::open_object(
810            &fs.object_manager().root_store(),
811            SuperBlockInstance::A.object_id(),
812            HandleOptions::default(),
813            None,
814        )
815        .await
816        .expect("open superblock failed");
817
818        let handle_b = ObjectStore::open_object(
819            &fs.object_manager().root_store(),
820            SuperBlockInstance::B.object_id(),
821            HandleOptions::default(),
822            None,
823        )
824        .await
825        .expect("open superblock failed");
826        (fs, handle_a, handle_b)
827    }
828
829    #[fuchsia::test]
830    async fn test_read_written_super_block() {
831        let (fs, _handle_a, _handle_b) = filesystem_and_super_block_handles().await;
832        const JOURNAL_OBJECT_ID: u64 = 5;
833
834        // Confirm that the (first) super-block is expected size.
835        // It should be MIN_SUPER_BLOCK_SIZE + SUPER_BLOCK_CHUNK_SIZE.
836        assert_eq!(
837            ObjectStore::open_object(
838                &fs.root_store(),
839                SuperBlockInstance::A.object_id(),
840                HandleOptions::default(),
841                None,
842            )
843            .await
844            .expect("open_object failed")
845            .get_size(),
846            MIN_SUPER_BLOCK_SIZE + SUPER_BLOCK_CHUNK_SIZE
847        );
848
849        // Create a large number of objects in the root parent store so that we test growing
850        // of the super-block file, requiring us to add extents.
851        let mut created_object_ids = vec![];
852        const NUM_ENTRIES: u64 = 16384;
853        for _ in 0..NUM_ENTRIES {
854            let mut transaction = fs
855                .root_store()
856                .new_transaction(lock_keys![], Options::default())
857                .await
858                .expect("new_transaction failed");
859            created_object_ids.push(
860                ObjectStore::create_object(
861                    &fs.object_manager().root_parent_store(),
862                    &mut transaction,
863                    HandleOptions::default(),
864                    None,
865                )
866                .await
867                .expect("create_object failed")
868                .object_id(),
869            );
870            transaction.commit().await.expect("commit failed");
871        }
872
873        // Note here that DataObjectHandle caches the size given to it at construction.
874        // If we want to know the true size after a super-block has been written, we need
875        // a new handle.
876        assert!(
877            ObjectStore::open_object(
878                &fs.root_store(),
879                SuperBlockInstance::A.object_id(),
880                HandleOptions::default(),
881                None,
882            )
883            .await
884            .expect("open_object failed")
885            .get_size()
886                > MIN_SUPER_BLOCK_SIZE + SUPER_BLOCK_CHUNK_SIZE
887        );
888
889        let written_super_block_a =
890            SuperBlockHeader::read_header(fs.device(), SuperBlockInstance::A)
891                .await
892                .expect("read failed");
893        let written_super_block_b =
894            SuperBlockHeader::read_header(fs.device(), SuperBlockInstance::B)
895                .await
896                .expect("read failed");
897
898        // Check that a non-zero GUID has been assigned.
899        assert!(!written_super_block_a.0.guid.0.is_nil());
900
901        // Depending on specific offsets is fragile so we just validate the fields we believe
902        // to be stable.
903        assert_eq!(written_super_block_a.0.guid, written_super_block_b.0.guid);
904        assert_eq!(written_super_block_a.0.guid, written_super_block_b.0.guid);
905        assert!(written_super_block_a.0.generation != written_super_block_b.0.generation);
906        assert_eq!(
907            written_super_block_a.0.root_parent_store_object_id,
908            written_super_block_b.0.root_parent_store_object_id
909        );
910        assert_eq!(
911            written_super_block_a.0.root_parent_graveyard_directory_object_id,
912            written_super_block_b.0.root_parent_graveyard_directory_object_id
913        );
914        assert_eq!(written_super_block_a.0.root_store_object_id, fs.root_store().store_object_id());
915        assert_eq!(
916            written_super_block_a.0.root_store_object_id,
917            written_super_block_b.0.root_store_object_id
918        );
919        assert_eq!(written_super_block_a.0.allocator_object_id, fs.allocator().object_id());
920        assert_eq!(
921            written_super_block_a.0.allocator_object_id,
922            written_super_block_b.0.allocator_object_id
923        );
924        assert_eq!(written_super_block_a.0.journal_object_id, JOURNAL_OBJECT_ID);
925        assert_eq!(
926            written_super_block_a.0.journal_object_id,
927            written_super_block_b.0.journal_object_id
928        );
929        assert!(
930            written_super_block_a.0.journal_checkpoint.file_offset
931                != written_super_block_b.0.journal_checkpoint.file_offset
932        );
933        assert!(
934            written_super_block_a.0.super_block_journal_file_offset
935                != written_super_block_b.0.super_block_journal_file_offset
936        );
937        // Nb: We skip journal_file_offsets and borrowed metadata space checks.
938        assert_eq!(written_super_block_a.0.earliest_version, LATEST_VERSION);
939        assert_eq!(
940            written_super_block_a.0.earliest_version,
941            written_super_block_b.0.earliest_version
942        );
943
944        // Nb: Skip comparison of root_parent store contents because we have no way of anticipating
945        // the extent offsets and it is reasonable that a/b differ.
946
947        // Delete all the objects we just made.
948        for object_id in created_object_ids {
949            let mut transaction = fs
950                .root_store()
951                .new_transaction(lock_keys![], Options::default())
952                .await
953                .expect("new_transaction failed");
954            fs.object_manager()
955                .root_parent_store()
956                .adjust_refs(&mut transaction, object_id, -1)
957                .await
958                .expect("adjust_refs failed");
959            transaction.commit().await.expect("commit failed");
960            fs.object_manager()
961                .root_parent_store()
962                .tombstone_object(object_id, Options::default())
963                .await
964                .expect("tombstone failed");
965        }
966        // Write some stuff to the root store to ensure we rotate the journal and produce new
967        // super blocks.
968        for _ in 0..NUM_ENTRIES {
969            let mut transaction = fs
970                .root_store()
971                .new_transaction(lock_keys![], Options::default())
972                .await
973                .expect("new_transaction failed");
974            ObjectStore::create_object(
975                &fs.object_manager().root_store(),
976                &mut transaction,
977                HandleOptions::default(),
978                None,
979            )
980            .await
981            .expect("create_object failed");
982            transaction.commit().await.expect("commit failed");
983        }
984
985        assert_eq!(
986            ObjectStore::open_object(
987                &fs.root_store(),
988                SuperBlockInstance::A.object_id(),
989                HandleOptions::default(),
990                None,
991            )
992            .await
993            .expect("open_object failed")
994            .get_size(),
995            MIN_SUPER_BLOCK_SIZE + SUPER_BLOCK_CHUNK_SIZE
996        );
997    }
998
999    #[fuchsia::test]
1000    async fn test_generation_comparison_wrapping() {
1001        let device = DeviceHolder::new(FakeDevice::new(
1002            TEST_DEVICE_BLOCK_COUNT,
1003            MIN_SUPER_BLOCK_SIZE as u32,
1004        ));
1005        let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
1006        fs.close().await.expect("close");
1007        let device = fs.take_device().await;
1008        device.reopen(false);
1009
1010        // Helper to write a superblock with a specific generation to a specific instance.
1011        // We need to clone the inner Arc to pass to the closure.
1012        let device_arc = (*device).clone();
1013        let write_sb = |instance: SuperBlockInstance, generation: u64| {
1014            let device = device_arc.clone();
1015            async move {
1016                let mut super_block_header = SuperBlockHeader::new(
1017                    1, // generation
1018                    3, // root_parent_store_object_id
1019                    4, // root_parent_graveyard_directory_object_id
1020                    5, // root_store_object_id
1021                    6, // allocator_object_id
1022                    7, // journal_object_id
1023                    JournalCheckpoint::default(),
1024                    LATEST_VERSION,
1025                );
1026                super_block_header.generation = generation;
1027                super_block_header.journal_checkpoint.version = LATEST_VERSION;
1028
1029                let mut writer = JournalWriter::new(MIN_SUPER_BLOCK_SIZE as usize, 0);
1030                writer.write_all(SUPER_BLOCK_MAGIC).unwrap();
1031                super_block_header.serialize_with_version(&mut writer).unwrap();
1032                SuperBlockRecord::End.serialize_into(&mut writer).unwrap();
1033                writer.pad_to_block().unwrap();
1034
1035                let mut buf = device.allocate_buffer(writer.flushable_bytes()).await;
1036                writer.take_flushable(buf.as_mut());
1037                device
1038                    .write(instance.first_extent().start, buf.as_ref())
1039                    .await
1040                    .expect("write failed");
1041            }
1042        };
1043
1044        // Case 1: A has MAX, B has 0. B should be selected.
1045        write_sb(SuperBlockInstance::A, u64::MAX).await;
1046        write_sb(SuperBlockInstance::B, 0).await;
1047        let manager = SuperBlockManager::new();
1048        let (header, _) = manager
1049            .load((*device).clone(), MIN_SUPER_BLOCK_SIZE as u64)
1050            .await
1051            .expect("load failed");
1052        assert_eq!(header.generation, 0);
1053
1054        // Case 2: A has 0, B has MAX. A should be selected.
1055        write_sb(SuperBlockInstance::A, 0).await;
1056        write_sb(SuperBlockInstance::B, u64::MAX).await;
1057        let manager = SuperBlockManager::new();
1058        let (header, _) = manager
1059            .load((*device).clone(), MIN_SUPER_BLOCK_SIZE as u64)
1060            .await
1061            .expect("load failed");
1062        assert_eq!(header.generation, 0);
1063
1064        // Case 3: A has 100, B has 200. B should be selected.
1065        write_sb(SuperBlockInstance::A, 100).await;
1066        write_sb(SuperBlockInstance::B, 200).await;
1067        let manager = SuperBlockManager::new();
1068        let (header, _) = manager
1069            .load((*device).clone(), MIN_SUPER_BLOCK_SIZE as u64)
1070            .await
1071            .expect("load failed");
1072        assert_eq!(header.generation, 200);
1073    }
1074
1075    #[fuchsia::test]
1076    async fn test_generation_wrapping_on_flush() {
1077        let block_size = 4096;
1078        let mut device =
1079            DeviceHolder::new(FakeDevice::new(TEST_DEVICE_BLOCK_COUNT, block_size as u32));
1080        {
1081            let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
1082            let root_store = fs.root_store();
1083            let mut transaction = fs
1084                .root_store()
1085                .new_transaction(lock_keys![], Options::default())
1086                .await
1087                .expect("new_transaction failed");
1088            ObjectStore::create_object(
1089                &root_store,
1090                &mut transaction,
1091                HandleOptions::default(),
1092                None,
1093            )
1094            .await
1095            .expect("create_object failed");
1096            transaction.commit().await.expect("commit failed");
1097            fs.sync(SyncOptions::default()).await.expect("sync failed");
1098            fs.close().await.expect("close failed");
1099            device = fs.take_device().await;
1100        }
1101        device.reopen(false);
1102
1103        let manager = SuperBlockManager::new();
1104        let (mut header, _) =
1105            manager.load((*device).clone(), block_size as u64).await.expect("load failed");
1106
1107        {
1108            let fs = FxFilesystem::open(device).await.expect("open failed");
1109            // To test wrapping, we need to get into a state where the current generation is
1110            // u64::MAX. Since we have A and B, and wrapping comparison is used, we need to set them
1111            // up carefully. We will set A to u64::MAX - 1, and B to u64::MAX.
1112            // Then the next write will be to A, and should be 0.
1113            header.generation = u64::MAX - 1;
1114            manager
1115                .save(header.clone(), (*fs).clone(), fs.root_parent_store().tree().layer_set())
1116                .await
1117                .expect("save 1 failed");
1118            header.generation = u64::MAX;
1119            manager
1120                .save(header, (*fs).clone(), fs.root_parent_store().tree().layer_set())
1121                .await
1122                .expect("save 2 failed");
1123            fs.close().await.expect("close failed");
1124            device = fs.take_device().await;
1125            device.reopen(false);
1126
1127            let fs = FxFilesystem::open(device).await.expect("open failed");
1128
1129            let root_store = fs.root_store();
1130            for _ in 0..6000 {
1131                let mut transaction = fs
1132                    .root_store()
1133                    .new_transaction(lock_keys![], Options::default())
1134                    .await
1135                    .expect("new_transaction failed");
1136                ObjectStore::create_object(
1137                    &root_store,
1138                    &mut transaction,
1139                    HandleOptions::default(),
1140                    None,
1141                )
1142                .await
1143                .expect("create_object failed");
1144                transaction.commit().await.expect("commit failed");
1145            }
1146            fs.sync(SyncOptions::default()).await.expect("sync failed");
1147            fs.close().await.expect("close failed");
1148            device = fs.take_device().await;
1149        }
1150        device.reopen(false);
1151
1152        let (header, _) =
1153            manager.load((*device).clone(), block_size as u64).await.expect("load failed");
1154        assert!(header.generation < 10);
1155    }
1156
1157    #[fuchsia::test]
1158    async fn test_guid_assign_on_read() {
1159        let (fs, handle_a, _handle_b) = filesystem_and_super_block_handles().await;
1160        const JOURNAL_OBJECT_ID: u64 = 5;
1161        let mut super_block_header_a = SuperBlockHeader::new(
1162            1,
1163            fs.object_manager().root_parent_store().store_object_id(),
1164            /* root_parent_graveyard_directory_object_id: */ 1000,
1165            fs.root_store().store_object_id(),
1166            fs.allocator().object_id(),
1167            JOURNAL_OBJECT_ID,
1168            JournalCheckpoint { file_offset: 1234, checksum: 5678, version: LATEST_VERSION },
1169            /* earliest_version: */ LATEST_VERSION,
1170        );
1171        // Ensure the superblock has no set GUID.
1172        super_block_header_a.guid = UuidWrapper::nil();
1173        write(
1174            &super_block_header_a,
1175            compact_root_parent(fs.object_manager().root_parent_store().as_ref())
1176                .expect("scan failed"),
1177            handle_a,
1178        )
1179        .await
1180        .expect("write failed");
1181        let super_block_header = SuperBlockHeader::read_header(fs.device(), SuperBlockInstance::A)
1182            .await
1183            .expect("read failed");
1184        // Ensure a GUID has been assigned.
1185        assert!(!super_block_header.0.guid.0.is_nil());
1186    }
1187
1188    #[fuchsia::test]
1189    async fn test_init_wipes_superblocks() {
1190        let device = DeviceHolder::new(FakeDevice::new(8192, TEST_DEVICE_BLOCK_SIZE));
1191
1192        let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
1193        let root_store = fs.root_store();
1194        // Generate enough work to induce a journal flush and thus a new superblock being written.
1195        for _ in 0..6000 {
1196            let mut transaction = fs
1197                .root_store()
1198                .new_transaction(lock_keys![], Options::default())
1199                .await
1200                .expect("new_transaction failed");
1201            ObjectStore::create_object(
1202                &root_store,
1203                &mut transaction,
1204                HandleOptions::default(),
1205                None,
1206            )
1207            .await
1208            .expect("create_object failed");
1209            transaction.commit().await.expect("commit failed");
1210        }
1211        fs.close().await.expect("Close failed");
1212        let device = fs.take_device().await;
1213        device.reopen(false);
1214
1215        SuperBlockHeader::read_header(device.clone(), SuperBlockInstance::A)
1216            .await
1217            .expect("read failed");
1218        let header = SuperBlockHeader::read_header(device.clone(), SuperBlockInstance::B)
1219            .await
1220            .expect("read failed");
1221
1222        let old_guid = header.0.guid;
1223
1224        // Re-initialize the filesystem.  The A and B blocks should be for the new FS.
1225        let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
1226        fs.close().await.expect("Close failed");
1227        let device = fs.take_device().await;
1228        device.reopen(false);
1229
1230        let a = SuperBlockHeader::read_header(device.clone(), SuperBlockInstance::A)
1231            .await
1232            .expect("read failed");
1233        let b = SuperBlockHeader::read_header(device.clone(), SuperBlockInstance::B)
1234            .await
1235            .expect("read failed");
1236
1237        assert_eq!(a.0.guid, b.0.guid);
1238        assert_ne!(old_guid, a.0.guid);
1239    }
1240
1241    #[fuchsia::test]
1242    async fn test_alternating_super_blocks() {
1243        let device = DeviceHolder::new(FakeDevice::new(8192, TEST_DEVICE_BLOCK_SIZE));
1244
1245        let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
1246        fs.close().await.expect("Close failed");
1247        let device = fs.take_device().await;
1248        device.reopen(false);
1249
1250        let (super_block_header_a, _) =
1251            SuperBlockHeader::read_header(device.clone(), SuperBlockInstance::A)
1252                .await
1253                .expect("read failed");
1254
1255        // The second super-block won't be valid at this time so there's no point reading it.
1256
1257        let fs = FxFilesystem::open(device).await.expect("open failed");
1258        let root_store = fs.root_store();
1259        // Generate enough work to induce a journal flush.
1260        for _ in 0..6000 {
1261            let mut transaction = fs
1262                .root_store()
1263                .new_transaction(lock_keys![], Options::default())
1264                .await
1265                .expect("new_transaction failed");
1266            ObjectStore::create_object(
1267                &root_store,
1268                &mut transaction,
1269                HandleOptions::default(),
1270                None,
1271            )
1272            .await
1273            .expect("create_object failed");
1274            transaction.commit().await.expect("commit failed");
1275        }
1276        fs.close().await.expect("Close failed");
1277        let device = fs.take_device().await;
1278        device.reopen(false);
1279
1280        let (super_block_header_a_after, _) =
1281            SuperBlockHeader::read_header(device.clone(), SuperBlockInstance::A)
1282                .await
1283                .expect("read failed");
1284        let (super_block_header_b_after, _) =
1285            SuperBlockHeader::read_header(device.clone(), SuperBlockInstance::B)
1286                .await
1287                .expect("read failed");
1288
1289        // It's possible that multiple super-blocks were written, so cater for that.
1290
1291        // The generations should be one apart.
1292        assert_eq!(
1293            (super_block_header_b_after.generation as i64
1294                - super_block_header_a_after.generation as i64)
1295                .abs(),
1296            1
1297        );
1298
1299        // At least one super-block should have been written.
1300        assert!(
1301            std::cmp::max(
1302                super_block_header_a_after.generation,
1303                super_block_header_b_after.generation
1304            ) > super_block_header_a.generation
1305        );
1306
1307        // They should have the same oddness.
1308        assert_eq!(super_block_header_a_after.generation & 1, super_block_header_a.generation & 1);
1309    }
1310
1311    #[fuchsia::test]
1312    async fn test_root_parent_is_compacted() {
1313        let device = DeviceHolder::new(FakeDevice::new(8192, TEST_DEVICE_BLOCK_SIZE));
1314
1315        let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
1316
1317        let mut transaction = fs
1318            .root_store()
1319            .new_transaction(lock_keys![], Options::default())
1320            .await
1321            .expect("new_transaction failed");
1322        let store = fs.root_parent_store();
1323        let handle =
1324            ObjectStore::create_object(&store, &mut transaction, HandleOptions::default(), None)
1325                .await
1326                .expect("create_object failed");
1327        store.add_to_graveyard(&mut transaction, handle.object_id());
1328        transaction.commit().await.expect("commit failed");
1329
1330        store
1331            .tombstone_object(handle.object_id(), Options::default())
1332            .await
1333            .expect("tombstone failed");
1334
1335        // Generate enough work to induce a journal flush.
1336        let root_store = fs.root_store();
1337        for _ in 0..6000 {
1338            let mut transaction = fs
1339                .root_store()
1340                .new_transaction(lock_keys![], Options::default())
1341                .await
1342                .expect("new_transaction failed");
1343            ObjectStore::create_object(
1344                &root_store,
1345                &mut transaction,
1346                HandleOptions::default(),
1347                None,
1348            )
1349            .await
1350            .expect("create_object failed");
1351            transaction.commit().await.expect("commit failed");
1352        }
1353
1354        // The root parent store should have been compacted, so we shouldn't be able to find any
1355        // record referring to the object we tombstoned.
1356        assert_eq!(
1357            store.tree().find(&ObjectKey::object(handle.object_id())).await.expect("find failed"),
1358            None
1359        );
1360    }
1361
1362    #[fuchsia::test]
1363    async fn test_invalid_object_ids_validation() {
1364        let device = DeviceHolder::new(FakeDevice::new(
1365            TEST_DEVICE_BLOCK_COUNT,
1366            MIN_SUPER_BLOCK_SIZE as u32,
1367        ));
1368        let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
1369        fs.close().await.expect("close");
1370        let device = fs.take_device().await;
1371        device.reopen(false);
1372
1373        // Helper to write a superblock with specific object IDs.
1374        let device_arc = (*device).clone();
1375        let write_sb = |instance: SuperBlockInstance,
1376                        root_parent_store_object_id: u64,
1377                        root_parent_graveyard_directory_object_id: u64,
1378                        root_store_object_id: u64,
1379                        allocator_object_id: u64,
1380                        journal_object_id: u64| {
1381            let device = device_arc.clone();
1382            async move {
1383                let mut super_block_header = SuperBlockHeader::new(
1384                    1, // generation
1385                    root_parent_store_object_id,
1386                    root_parent_graveyard_directory_object_id,
1387                    root_store_object_id,
1388                    allocator_object_id,
1389                    journal_object_id,
1390                    JournalCheckpoint::default(),
1391                    LATEST_VERSION,
1392                );
1393                super_block_header.journal_checkpoint.version = LATEST_VERSION;
1394
1395                let mut writer = JournalWriter::new(MIN_SUPER_BLOCK_SIZE as usize, 0);
1396                writer.write_all(SUPER_BLOCK_MAGIC).unwrap();
1397                super_block_header.serialize_with_version(&mut writer).unwrap();
1398                SuperBlockRecord::End.serialize_into(&mut writer).unwrap();
1399                writer.pad_to_block().unwrap();
1400
1401                let mut buf = device.allocate_buffer(writer.flushable_bytes()).await;
1402                writer.take_flushable(buf.as_mut());
1403                device
1404                    .write(instance.first_extent().start, buf.as_ref())
1405                    .await
1406                    .expect("write failed");
1407            }
1408        };
1409
1410        let manager = SuperBlockManager::new();
1411
1412        // Case 1: Duplicate store IDs (3, 3)
1413        write_sb(SuperBlockInstance::A, 3, 4, 3, 5, 6).await;
1414        write_sb(SuperBlockInstance::B, 3, 4, 3, 5, 6).await;
1415        assert!(manager.load((*device).clone(), MIN_SUPER_BLOCK_SIZE as u64).await.is_err());
1416
1417        // Case 2: Allocator matches root_parent_store_object_id (3, 3)
1418        write_sb(SuperBlockInstance::A, 3, 4, 5, 3, 6).await;
1419        write_sb(SuperBlockInstance::B, 3, 4, 5, 3, 6).await;
1420        assert!(manager.load((*device).clone(), MIN_SUPER_BLOCK_SIZE as u64).await.is_err());
1421
1422        // Case 3: Allocator matches root_store_object_id (5, 5)
1423        write_sb(SuperBlockInstance::A, 3, 4, 5, 5, 6).await;
1424        write_sb(SuperBlockInstance::B, 3, 4, 5, 5, 6).await;
1425        assert!(manager.load((*device).clone(), MIN_SUPER_BLOCK_SIZE as u64).await.is_err());
1426
1427        // Case 4: Duplicate objects in root_parent_store (graveyard 4, journal 4)
1428        write_sb(SuperBlockInstance::A, 3, 4, 5, 6, 4).await;
1429        write_sb(SuperBlockInstance::B, 3, 4, 5, 6, 4).await;
1430        assert!(manager.load((*device).clone(), MIN_SUPER_BLOCK_SIZE as u64).await.is_err());
1431
1432        // Case 5: Valid configuration
1433        write_sb(SuperBlockInstance::A, 3, 4, 5, 6, 7).await;
1434        write_sb(SuperBlockInstance::B, 3, 4, 5, 6, 7).await;
1435        assert!(manager.load((*device).clone(), MIN_SUPER_BLOCK_SIZE as u64).await.is_ok());
1436    }
1437}