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