Skip to main content

fxfs/object_store/
journal.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//! The journal is implemented as an ever extending file which contains variable length records
6//! that describe mutations to be applied to various objects.  The journal file consists of
7//! blocks, with a checksum at the end of each block, but otherwise it can be considered a
8//! continuous stream.
9//!
10//! The checksum is seeded with the checksum from the previous block.  To free space in the
11//! journal, records are replaced with sparse extents when it is known they are no longer
12//! needed to mount.
13//!
14//! At mount time, the journal is replayed: the mutations are applied into memory.
15//! Eventually, a checksum failure will indicate no more records exist to be replayed,
16//! at which point the mount can continue and the journal will be extended from that point with
17//! further mutations as required.
18
19mod bootstrap_handle;
20mod checksum_list;
21mod reader;
22pub mod super_block;
23mod writer;
24
25use crate::checksum::{Checksum, Checksums};
26use crate::errors::FxfsError;
27use crate::filesystem::{
28    ApplyContext, ApplyMode, FlushReason, ForceMajor, FxFilesystem, SyncOptions,
29};
30use crate::hooks::HooksHandle;
31use crate::log::*;
32use crate::lsm_tree::types::LayerIterator;
33use crate::object_handle::{ObjectHandle as _, ReadObjectHandle};
34use crate::object_store::allocator::Allocator;
35use crate::object_store::data_object_handle::OverwriteOptions;
36use crate::object_store::extent_record::{ExtentMode, ExtentValue};
37use crate::object_store::graveyard::Graveyard;
38use crate::object_store::journal::bootstrap_handle::BootstrapObjectHandle;
39use crate::object_store::journal::checksum_list::ChecksumList;
40use crate::object_store::journal::reader::{JournalReader, ReadResult};
41use crate::object_store::journal::super_block::{
42    SuperBlockHeader, SuperBlockInstance, SuperBlockManager,
43};
44use crate::object_store::journal::writer::JournalWriter;
45use crate::object_store::object_manager::ObjectManager;
46use crate::object_store::object_record::{AttributeKey, ObjectKey, ObjectKeyData, ObjectValue};
47use crate::object_store::transaction::{
48    AllocatorMutation, LockKey, Mutation, MutationV56, MutationV57, MutationV59, MutationV61,
49    MutationV62, ObjectMutationIterator, ObjectStoreMutation, Options, ReservationOptions,
50    TRANSACTION_MAX_JOURNAL_USAGE, Transaction, lock_keys,
51};
52use crate::object_store::{
53    AssocObj, AttributeId, DataObjectHandle, Extent, HandleOptions, HandleOwner, INVALID_OBJECT_ID,
54    Item, ItemRef, NewChildStoreOptions, ObjectStore, ReservedId,
55};
56use crate::range::RangeExt;
57use crate::round::round_div;
58use crate::serialized_types::{LATEST_VERSION, Migrate, Version, Versioned, migrate_to_version};
59use anyhow::{Context, Error, anyhow, bail, ensure};
60use core::iter::Iterator;
61use event_listener::Event;
62use fprint::TypeFingerprint;
63use fuchsia_inspect::NumericProperty;
64use fuchsia_sync::Mutex;
65use futures::FutureExt as _;
66use futures::future::poll_fn;
67use rustc_hash::FxHashMap as HashMap;
68use serde::{Deserialize, Serialize};
69use smallvec::SmallVec;
70use static_assertions::const_assert;
71use std::clone::Clone;
72use std::collections::HashSet;
73use std::num::NonZero;
74use std::ops::{Bound, Range};
75use std::sync::atomic::{AtomicBool, Ordering};
76use std::sync::{Arc, OnceLock};
77use std::task::{Poll, Waker};
78use storage_device::Device;
79use storage_units::BlockSize;
80
81// The journal file is written to in blocks of this size.
82pub const BLOCK_SIZE: BlockSize = BlockSize::SIZE_4KIB;
83
84// The journal file is extended by this amount when necessary.
85const CHUNK_SIZE: u64 = 131_072;
86const_assert!(CHUNK_SIZE > TRANSACTION_MAX_JOURNAL_USAGE);
87
88// See the comment for the `reclaim_size` member of Inner.
89pub const DEFAULT_RECLAIM_SIZE: u64 = 524_288;
90
91// Temporary space that should be reserved for the journal.  For example: space that is currently
92// used in the journal file but cannot be deallocated yet because we are flushing.
93pub const RESERVED_SPACE: u64 = 1_048_576;
94
95// Whenever the journal is replayed (i.e. the system is unmounted and remounted), we reset the
96// journal stream, at which point any half-complete transactions are discarded.  We indicate a
97// journal reset by XORing the previous block's checksum with this mask, and using that value as a
98// seed for the next journal block.
99const RESET_XOR: u64 = 0xffffffffffffffff;
100
101// To keep track of offsets within a journal file, we need both the file offset and the check-sum of
102// the preceding block, since the check-sum of the preceding block is an input to the check-sum of
103// every block.
104pub type JournalCheckpoint = JournalCheckpointV32;
105
106#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize, TypeFingerprint)]
107pub struct JournalCheckpointV32 {
108    pub file_offset: u64,
109
110    // Starting check-sum for block that contains file_offset i.e. the checksum for the previous
111    // block.
112    pub checksum: Checksum,
113
114    // If versioned, the version of elements stored in the journal. e.g. JournalRecord version.
115    // This can change across reset events so we store it along with the offset and checksum to
116    // know which version to deserialize.
117    pub version: Version,
118}
119
120pub type JournalRecord = JournalRecordV62;
121
122#[allow(clippy::large_enum_variant)]
123#[derive(Clone, Debug, Serialize, Deserialize, TypeFingerprint, Versioned)]
124#[cfg_attr(fuzz, derive(arbitrary::Arbitrary))]
125pub enum JournalRecordV62 {
126    EndBlock,
127    Mutation {
128        object_id: u64,
129        mutation: MutationV62,
130    },
131
132    /// Commits records in the transaction.
133    Commit,
134
135    /// Discard all mutations with offsets greater than or equal to the given offset.
136    Discard(u64),
137
138    /// Indicates the device was flushed at the given journal offset.
139    /// Note that this really means that at this point in the journal offset, we can be certain that
140    /// there's no remaining buffered data in the block device; the buffers and the disk contents
141    /// are consistent.
142    /// We insert one of these records *after* a flush along with the *next* transaction to go
143    /// through.  If that never comes (either due to graceful or hard shutdown), the journal reset
144    /// on the next mount will serve the same purpose and count as a flush, although it is necessary
145    /// to defensively flush the device before replaying the journal (if possible, i.e. not
146    /// read-only) in case the block device connection was reused.
147    DidFlushDevice(u64),
148
149    /// Checksums for a data range written by this transaction, for a particular store identified by
150    /// the u64 object id. A transaction is only valid if these checksums are right. The range is
151    /// the device offset the checksums are for.
152    ///
153    /// A boolean indicates whether this range is being written to for the first time. For overwrite
154    /// extents, we only check the checksums for a block if it has been written to for the first
155    /// time since the last flush, because otherwise we can't roll it back anyway so it doesn't
156    /// matter. For copy-on-write extents, the bool is always true.
157    DataChecksums(u64, Range<u64>, crate::checksum::ChecksumsV38, bool),
158
159    /// Commits records in a transaction that borrows metadata space, recording the total bytes
160    /// currently held in `ObjectManager::metadata_reservation` (`M_total`).
161    CommitBorrowed(u64),
162}
163
164#[allow(clippy::large_enum_variant)]
165#[derive(Migrate, Clone, Debug, Serialize, Deserialize, TypeFingerprint, Versioned)]
166#[migrate_to_version(JournalRecordV62)]
167pub enum JournalRecordV61 {
168    EndBlock,
169    Mutation { object_id: u64, mutation: MutationV61 },
170    Commit,
171    Discard(u64),
172    DidFlushDevice(u64),
173    DataChecksums(u64, Range<u64>, crate::checksum::ChecksumsV38, bool),
174    CommitBorrowed(u64),
175}
176
177#[allow(clippy::large_enum_variant)]
178#[derive(Clone, Debug, Serialize, Deserialize, TypeFingerprint, Versioned)]
179pub enum JournalRecordV60 {
180    EndBlock,
181    Mutation { object_id: u64, mutation: MutationV59 },
182    Commit,
183    Discard(u64),
184    DidFlushDevice(u64),
185    DataChecksums(u64, Range<u64>, crate::checksum::ChecksumsV38, bool),
186}
187
188impl From<JournalRecordV60> for JournalRecordV61 {
189    fn from(record: JournalRecordV60) -> Self {
190        match record {
191            JournalRecordV60::EndBlock => Self::EndBlock,
192            JournalRecordV60::Mutation { object_id, mutation } => {
193                Self::Mutation { object_id, mutation: mutation.into() }
194            }
195            JournalRecordV60::Commit => Self::Commit,
196            JournalRecordV60::Discard(offset) => Self::Discard(offset),
197            JournalRecordV60::DidFlushDevice(offset) => Self::DidFlushDevice(offset),
198            JournalRecordV60::DataChecksums(owner, range, checksums, first_write) => {
199                Self::DataChecksums(owner, range, checksums, first_write)
200            }
201        }
202    }
203}
204
205#[allow(clippy::large_enum_variant)]
206#[derive(Clone, Debug, Serialize, Deserialize, TypeFingerprint, Versioned)]
207pub enum JournalRecordV59 {
208    EndBlock,
209    Mutation { object_id: u64, mutation: MutationV59 },
210    Commit,
211    Discard(u64),
212    DidFlushDevice(u64),
213    DataChecksums(Range<u64>, crate::checksum::ChecksumsV38, bool),
214}
215
216impl From<JournalRecordV59> for JournalRecordV60 {
217    fn from(record: JournalRecordV59) -> Self {
218        match record {
219            JournalRecordV59::EndBlock => Self::EndBlock,
220            JournalRecordV59::Mutation { object_id, mutation } => {
221                Self::Mutation { object_id, mutation }
222            }
223            JournalRecordV59::Commit => Self::Commit,
224            JournalRecordV59::Discard(offset) => Self::Discard(offset),
225            JournalRecordV59::DidFlushDevice(offset) => Self::DidFlushDevice(offset),
226            JournalRecordV59::DataChecksums(range, checksums, first_write) => {
227                // `JournalRecordV59::DataChecksums` did not record the owning store ID, so we map
228                // it to `INVALID_OBJECT_ID`. During replay, `read_transactions` only permits
229                // `INVALID_OBJECT_ID` when `checkpoint.version.major < 60` (rejecting it in V60+
230                // records), and `ChecksumList::push` allows an existing `INVALID_OBJECT_ID` entry
231                // to be overlapped by a subsequent write (adopting the new `owner_object_id`).
232                Self::DataChecksums(INVALID_OBJECT_ID, range, checksums, first_write)
233            }
234        }
235    }
236}
237
238#[allow(clippy::large_enum_variant)]
239#[derive(Migrate, Clone, Debug, Serialize, Deserialize, TypeFingerprint, Versioned)]
240#[migrate_to_version(JournalRecordV59)]
241pub enum JournalRecordV57 {
242    EndBlock,
243    Mutation { object_id: u64, mutation: MutationV57 },
244    Commit,
245    Discard(u64),
246    DidFlushDevice(u64),
247    DataChecksums(Range<u64>, crate::checksum::ChecksumsV38, bool),
248}
249
250#[allow(clippy::large_enum_variant)]
251#[derive(Migrate, Clone, Debug, Serialize, Deserialize, TypeFingerprint, Versioned)]
252#[migrate_to_version(JournalRecordV57)]
253pub enum JournalRecordV56 {
254    EndBlock,
255    Mutation { object_id: u64, mutation: MutationV56 },
256    Commit,
257    Discard(u64),
258    DidFlushDevice(u64),
259    DataChecksums(Range<u64>, crate::checksum::ChecksumsV38, bool),
260}
261
262pub(super) fn journal_handle_options() -> HandleOptions {
263    HandleOptions { skip_journal_checks: true, ..Default::default() }
264}
265
266/// The journal records a stream of mutations that are to be applied to other objects.  At mount
267/// time, these records can be replayed into memory.  It provides a way to quickly persist changes
268/// without having to make a large number of writes; they can be deferred to a later time (e.g.
269/// when a sufficient number have been queued).  It also provides support for transactions, the
270/// ability to have mutations that are to be applied atomically together.
271pub struct Journal {
272    objects: Arc<ObjectManager>,
273    hooks: Arc<HooksHandle>,
274    handle: OnceLock<DataObjectHandle<ObjectStore>>,
275    super_block_manager: SuperBlockManager,
276    inner: Mutex<Inner>,
277    writer_mutex: Mutex<()>,
278    sync_mutex: futures::lock::Mutex<()>,
279    trace: AtomicBool,
280
281    // This event is used when we are waiting for a compaction to free up journal space.
282    reclaim_event: Event,
283}
284
285#[derive(Clone, Copy, Debug, PartialEq, Eq)]
286enum CompactionState {
287    Idle,
288    Running,
289    Forced,
290}
291
292impl CompactionState {
293    fn is_running(self) -> bool {
294        !matches!(self, Self::Idle)
295    }
296}
297
298struct Inner {
299    super_block_header: SuperBlockHeader,
300
301    // The offset that we can zero the journal up to now that it is no longer needed.
302    zero_offset: Option<u64>,
303
304    // The journal offset that we most recently flushed to the device.
305    device_flushed_offset: u64,
306
307    // If true, indicates a DidFlushDevice record is pending.
308    needs_did_flush_device: bool,
309
310    // The writer for the journal.
311    writer: JournalWriter,
312
313    // Set when a reset is encountered during a read.
314    // Used at write pre_commit() time to ensure we write a version first thing after a reset.
315    output_reset_version: bool,
316
317    // Waker for the flush task.
318    flush_waker: Option<Waker>,
319
320    // Indicates the journal has been terminated.
321    terminate: bool,
322
323    // Latched error indicating reason for journal termination if not graceful.
324    terminate_reason: Option<Error>,
325
326    // Disable compactions.
327    disable_compactions: bool,
328
329    // When true, compactions are paused.
330    compactions_paused: bool,
331
332    // Current compaction state.
333    compaction_state: CompactionState,
334
335    // Waker for the sync task for when it's waiting for the flush task to finish.
336    sync_waker: Option<Waker>,
337
338    // The last offset we flushed to the journal file.
339    flushed_offset: u64,
340
341    // The last offset that should be considered valid in the journal file.  Most of the time, this
342    // will be the same as `flushed_offset` but at mount time, this could be less and will only be
343    // up to the end of the last valid transaction; it won't include transactions that follow that
344    // have been discarded.
345    valid_to: u64,
346
347    // If, after replaying, we have to discard a number of mutations (because they don't validate),
348    // this offset specifies where we need to discard back to.  This is so that when we next replay,
349    // we ignore those mutations and continue with new good mutations.
350    discard_offset: Option<u64>,
351
352    // In the steady state, the journal should fluctuate between being approximately half of this
353    // number and this number.  New super-blocks will be written every time about half of this
354    // amount is written to the journal.
355    reclaim_size: u64,
356
357    image_builder_mode: Option<SuperBlockInstance>,
358
359    // If true and `needs_barrier`, issue a pre-barrier on the first device write of each journal
360    // write (which happens in multiples of `BLOCK_SIZE`). This ensures that all the corresponding
361    // data writes make it to disk before the journal gets written to.
362    barriers_enabled: bool,
363
364    // If true, indicates that data write requests have been made to the device since the last
365    // journal write.
366    needs_barrier: bool,
367}
368
369impl Inner {
370    fn terminate(&mut self, reason: Option<Error>) {
371        self.terminate = true;
372
373        if let Some(err) = reason {
374            error!(error:? = err; "Terminating journal");
375            // Log previous error if one was already set, otherwise latch the error.
376            if let Some(prev_err) = self.terminate_reason.as_ref() {
377                error!(error:? = prev_err; "Journal previously terminated");
378            } else {
379                self.terminate_reason = Some(err);
380            }
381        }
382
383        if let Some(waker) = self.flush_waker.take() {
384            waker.wake();
385        }
386        if let Some(waker) = self.sync_waker.take() {
387            waker.wake();
388        }
389    }
390}
391
392pub struct JournalOptions {
393    /// In the steady state, the journal should fluctuate between being approximately half of this
394    /// number and this number.  New super-blocks will be written every time about half of this
395    /// amount is written to the journal.
396    pub reclaim_size: u64,
397
398    // If true, issue a pre-barrier on the first device write of each journal write (which happens
399    // in multiples of `BLOCK_SIZE`). This ensures that all the corresponding data writes make it
400    // to disk before the journal gets written to.
401    pub barriers_enabled: bool,
402}
403
404impl Default for JournalOptions {
405    fn default() -> Self {
406        JournalOptions { reclaim_size: DEFAULT_RECLAIM_SIZE, barriers_enabled: false }
407    }
408}
409
410struct JournaledTransactions {
411    transactions: Vec<JournaledTransaction>,
412    device_flushed_offset: u64,
413}
414
415/// Indicates whether a replayed transaction borrowed metadata space.
416#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
417pub enum BorrowedMetadata {
418    /// Funded transaction (`JournalRecord::Commit`) that adds `4 * txn_size` to `M_total`.
419    #[default]
420    None,
421
422    /// V61+ transaction (`JournalRecord::CommitBorrowed(u64)`) that borrowed metadata space,
423    /// carrying `M_total` (`metadata_reservation.amount()`).
424    Borrowed(u64),
425
426    /// Pre-V61 transaction that contained a `Mutation::UpdateBorrowed(u64)` record.
427    LegacyBorrowed(u64),
428}
429
430#[derive(Debug, Default)]
431pub struct JournaledTransaction {
432    pub checkpoint: JournalCheckpoint,
433    pub root_parent_mutations: Vec<Mutation>,
434    pub root_mutations: Vec<Mutation>,
435    /// List of (store_object_id, mutation).
436    pub non_root_mutations: Vec<(u64, Mutation)>,
437    pub end_offset: u64,
438    pub checksums: Vec<JournaledChecksums>,
439
440    /// Records offset + 1 of the matching begin_flush transaction. The +1 is because we want to
441    /// ignore the begin flush transaction; we don't need or want to replay it.
442    pub end_flush: Option<(/* store_id: */ u64, /* begin offset: */ u64)>,
443
444    /// The volume which was deleted in this transaction, if any.
445    pub volume_deleted: Option</* store_id: */ u64>,
446
447    /// Whether this transaction borrowed metadata space.
448    pub borrowed_metadata: BorrowedMetadata,
449
450    /// Per-object journal usage (in bytes) for mutations in this transaction.
451    pub object_journal_usage: SmallVec<[(u64, u64); 2]>,
452}
453
454impl JournaledTransaction {
455    fn new(checkpoint: JournalCheckpoint) -> Self {
456        Self { checkpoint, ..Default::default() }
457    }
458}
459
460const VOLUME_DELETED: u64 = u64::MAX;
461
462#[derive(Debug)]
463pub struct JournaledChecksums {
464    pub owner_object_id: u64,
465    pub device_range: Range<u64>,
466    pub checksums: Checksums,
467    pub first_write: bool,
468}
469
470/// Handles for journal-like objects have some additional functionality to manage their extents,
471/// since during replay we need to add extents as we find them.
472pub trait JournalHandle: ReadObjectHandle {
473    /// The end offset of the last extent in the JournalHandle.  Used only for validating extents
474    /// (which will be skipped if None is returned).
475    /// Note this is equivalent in value to ReadObjectHandle::get_size, when present.
476    fn end_offset(&self) -> Option<u64>;
477    /// Adds an extent to the current end of the journal stream.
478    /// `added_offset` is the offset into the journal of the transaction which added this extent,
479    /// used for discard_extents.
480    fn push_extent(&mut self, added_offset: u64, device_range: Range<u64>);
481    /// Discards all extents which were added in a transaction at offset >= |discard_offset|.
482    fn discard_extents(&mut self, discard_offset: u64);
483}
484
485// Provide a stub implementation for DataObjectHandle so we can use it in
486// Journal::read_transactions.  Manual extent management is a NOP (which is OK since presumably the
487// DataObjectHandle already knows where its extents live).
488impl<S: HandleOwner> JournalHandle for DataObjectHandle<S> {
489    fn end_offset(&self) -> Option<u64> {
490        None
491    }
492    fn push_extent(&mut self, _added_offset: u64, _device_range: Range<u64>) {
493        // NOP
494    }
495    fn discard_extents(&mut self, _discard_offset: u64) {
496        // NOP
497    }
498}
499
500#[fxfs_trace::trace]
501impl Journal {
502    pub fn new(
503        objects: Arc<ObjectManager>,
504        options: JournalOptions,
505        hooks: Arc<HooksHandle>,
506    ) -> Journal {
507        let starting_checksum = rand::random_range(1..u64::MAX);
508        Journal {
509            objects: objects,
510            hooks,
511            handle: OnceLock::new(),
512            super_block_manager: SuperBlockManager::new(),
513            inner: Mutex::new(Inner {
514                super_block_header: SuperBlockHeader::default(),
515                zero_offset: None,
516                device_flushed_offset: 0,
517                needs_did_flush_device: false,
518                writer: JournalWriter::new(BLOCK_SIZE, starting_checksum),
519                output_reset_version: false,
520                flush_waker: None,
521                terminate: false,
522                terminate_reason: None,
523                disable_compactions: false,
524                compactions_paused: false,
525                compaction_state: CompactionState::Idle,
526                sync_waker: None,
527                flushed_offset: 0,
528                valid_to: 0,
529                discard_offset: None,
530                reclaim_size: options.reclaim_size,
531                image_builder_mode: None,
532                barriers_enabled: options.barriers_enabled,
533                needs_barrier: false,
534            }),
535            writer_mutex: Mutex::new(()),
536            sync_mutex: futures::lock::Mutex::new(()),
537            trace: AtomicBool::new(false),
538            reclaim_event: Event::new(),
539        }
540    }
541
542    pub fn set_trace(&self, trace: bool) {
543        let old_value = self.trace.swap(trace, Ordering::Relaxed);
544        if trace != old_value {
545            info!(trace; "J: trace");
546        }
547    }
548
549    pub fn set_image_builder_mode(&self, mode: Option<SuperBlockInstance>) {
550        self.inner.lock().image_builder_mode = mode;
551        if let Some(instance) = mode {
552            *self.super_block_manager.next_instance.lock() = instance;
553        }
554    }
555
556    pub fn image_builder_mode(&self) -> Option<SuperBlockInstance> {
557        self.inner.lock().image_builder_mode
558    }
559
560    #[cfg(feature = "migration")]
561    pub fn set_filesystem_uuid(&self, uuid: &[u8; 16]) -> Result<(), Error> {
562        ensure!(
563            self.inner.lock().image_builder_mode.is_some(),
564            "Can only set filesystem uuid in image builder mode."
565        );
566        self.inner.lock().super_block_header.guid.0 = uuid::Uuid::from_bytes(*uuid);
567        Ok(())
568    }
569
570    pub(crate) async fn read_superblocks(
571        &self,
572        device: Arc<dyn Device>,
573        block_size: BlockSize,
574    ) -> Result<(SuperBlockHeader, ObjectStore), Error> {
575        self.super_block_manager.load(device, block_size).await
576    }
577
578    /// Used during replay to validate a mutation.  This should return false if the mutation is not
579    /// valid and should not be applied.  This could be for benign reasons: e.g. the device flushed
580    /// data out-of-order, or because of a malicious actor.
581    fn validate_mutation(
582        &self,
583        mutation: &Mutation,
584        block_size: BlockSize,
585        device_size: u64,
586    ) -> bool {
587        match mutation {
588            Mutation::ObjectStore(ObjectStoreMutation {
589                item:
590                    Item {
591                        key:
592                            ObjectKey {
593                                data: ObjectKeyData::Attribute(_, AttributeKey::Extent(extent)),
594                                ..
595                            },
596                        value: ObjectValue::Extent(ExtentValue::Some { device_offset, mode, .. }),
597                        ..
598                    },
599                ..
600            }) => {
601                if extent.is_empty() || !block_size.is_aligned(extent) {
602                    return false;
603                }
604                let len = extent.length().unwrap();
605                if let ExtentMode::Cow(checksums) = mode {
606                    if checksums.len() > 0 {
607                        if len % checksums.len() as u64 != 0 {
608                            return false;
609                        }
610                        if !block_size.is_aligned(len / checksums.len() as u64) {
611                            return false;
612                        }
613                    }
614                }
615                if !block_size.is_aligned(device_offset)
616                    || *device_offset >= device_size
617                    || device_size - *device_offset < len
618                {
619                    return false;
620                }
621            }
622            Mutation::ObjectStore(_) => {}
623            Mutation::EncryptedObjectStore(_) => {}
624            Mutation::Allocator(AllocatorMutation::Allocate { device_range, owner_object_id }) => {
625                return !device_range.is_empty()
626                    && *owner_object_id != INVALID_OBJECT_ID
627                    && device_range.end <= device_size;
628            }
629            Mutation::Allocator(AllocatorMutation::Deallocate {
630                device_range,
631                owner_object_id,
632            }) => {
633                return !device_range.is_empty()
634                    && *owner_object_id != INVALID_OBJECT_ID
635                    && device_range.end <= device_size;
636            }
637            Mutation::Allocator(AllocatorMutation::MarkForDeletion(owner_object_id)) => {
638                return *owner_object_id != INVALID_OBJECT_ID;
639            }
640            Mutation::Allocator(AllocatorMutation::SetLimit { owner_object_id, .. }) => {
641                return *owner_object_id != INVALID_OBJECT_ID;
642            }
643            Mutation::BeginFlush => {}
644            Mutation::EndFlush(_) => {}
645            Mutation::DeleteVolume => {}
646            Mutation::UpdateBorrowed(_) => {}
647            Mutation::UpdateMutationsKey(_) => {}
648            Mutation::CreateInternalDir(owner_object_id) => {
649                return *owner_object_id != INVALID_OBJECT_ID;
650            }
651        }
652        true
653    }
654
655    // Assumes that `mutation` has been validated.
656    fn update_checksum_list(
657        &self,
658        journal_offset: u64,
659        mutation: &Mutation,
660        checksum_list: &mut ChecksumList,
661    ) -> Result<(), Error> {
662        match mutation {
663            Mutation::ObjectStore(_) => {}
664            Mutation::Allocator(AllocatorMutation::Deallocate { device_range, .. }) => {
665                checksum_list.mark_deallocated(journal_offset, device_range.clone().into());
666            }
667            Mutation::Allocator(AllocatorMutation::MarkForDeletion(owner_object_id)) => {
668                checksum_list.mark_for_deletion(journal_offset, *owner_object_id);
669            }
670            _ => {}
671        }
672        Ok(())
673    }
674
675    /// Reads the latest super-block, and then replays journaled records.
676    #[trace]
677    pub async fn replay(
678        &self,
679        filesystem: Arc<FxFilesystem>,
680        on_new_allocator: Option<Box<dyn Fn(Arc<Allocator>) + Send + Sync>>,
681    ) -> Result<(), Error> {
682        let block_size = filesystem.block_size();
683
684        let (super_block, root_parent) =
685            self.super_block_manager.load(filesystem.device(), block_size).await?;
686
687        let root_parent = Arc::new(ObjectStore::attach_filesystem(root_parent, filesystem.clone()));
688
689        self.objects.set_root_parent_store(root_parent.clone());
690        let allocator =
691            Arc::new(Allocator::new(filesystem.clone(), super_block.allocator_object_id));
692        if let Some(on_new_allocator) = on_new_allocator {
693            on_new_allocator(allocator.clone());
694        }
695        self.objects.set_allocator(allocator.clone());
696        self.objects.init_replay_metadata_reservation(
697            super_block.journal_checkpoint.version,
698            &super_block.reserved_metadata_space,
699        );
700        self.objects.set_last_end_offset(super_block.super_block_journal_file_offset);
701        {
702            let mut inner = self.inner.lock();
703            inner.super_block_header = super_block.clone();
704        }
705
706        let device = filesystem.device();
707
708        let mut handle;
709        {
710            let root_parent_layer = root_parent.tree().mutable_layer();
711            let mut iter = root_parent_layer.seek(Bound::Included(&ObjectKey::attribute(
712                super_block.journal_object_id,
713                AttributeId::DATA,
714                AttributeKey::Extent(Extent::search_key_from_offset(
715                    BLOCK_SIZE.align_down(super_block.journal_checkpoint.file_offset),
716                )),
717            )));
718            let start_offset = if let Some(ItemRef {
719                key:
720                    ObjectKey {
721                        data:
722                            ObjectKeyData::Attribute(AttributeId::DATA, AttributeKey::Extent(extent)),
723                        ..
724                    },
725                ..
726            }) = iter.get()
727            {
728                extent.start
729            } else {
730                0
731            };
732            handle = BootstrapObjectHandle::new_with_start_offset(
733                super_block.journal_object_id,
734                device.clone(),
735                start_offset,
736            );
737            while let Some(item) = iter.get() {
738                if !match item.into() {
739                    Some((
740                        object_id,
741                        AttributeId::DATA,
742                        extent,
743                        ExtentValue::Some { device_offset, .. },
744                    )) if object_id == super_block.journal_object_id => {
745                        if let Some(end_offset) = handle.end_offset() {
746                            if extent.start != end_offset {
747                                bail!(anyhow!(FxfsError::Inconsistent).context(format!(
748                                    "Unexpected journal extent {:?}, expected start: {}",
749                                    item, end_offset
750                                )));
751                            }
752                        }
753                        handle.push_extent(
754                            0, // We never discard extents from the root parent store.
755                            *device_offset
756                                ..*device_offset + extent.length().context("Invalid extent")?,
757                        );
758                        true
759                    }
760                    _ => false,
761                } {
762                    break;
763                }
764                iter.advance().await.context("Failed to advance root parent store iterator")?;
765            }
766        }
767
768        let mut reader = JournalReader::new(handle, &super_block.journal_checkpoint);
769        let JournaledTransactions { mut transactions, device_flushed_offset } = self
770            .read_transactions(&mut reader, None, INVALID_OBJECT_ID)
771            .await
772            .context("Reading transactions for replay")?;
773
774        // Validate all the mutations.
775        let mut checksum_list = ChecksumList::new(device_flushed_offset);
776        let mut valid_to = reader.journal_file_checkpoint().file_offset;
777        let device_size = device.size();
778        'bad_replay: for JournaledTransaction {
779            checkpoint,
780            root_parent_mutations,
781            root_mutations,
782            non_root_mutations,
783            checksums,
784            ..
785        } in &transactions
786        {
787            for JournaledChecksums { owner_object_id, device_range, checksums, first_write } in
788                checksums
789            {
790                checksum_list
791                    .push(
792                        checkpoint.file_offset,
793                        *owner_object_id,
794                        device_range.clone(),
795                        checksums.maybe_as_ref().context("Malformed checksums")?,
796                        *first_write,
797                    )
798                    .context("Pushing journal checksum records to checksum list")?;
799            }
800            for mutation in root_parent_mutations
801                .iter()
802                .chain(root_mutations)
803                .chain(non_root_mutations.iter().map(|(_, m)| m))
804            {
805                if !self.validate_mutation(mutation, block_size, device_size) {
806                    info!(mutation:?; "Stopping replay at bad mutation");
807                    valid_to = checkpoint.file_offset;
808                    break 'bad_replay;
809                }
810                self.update_checksum_list(checkpoint.file_offset, &mutation, &mut checksum_list)?;
811            }
812        }
813
814        // Validate the checksums. Note if barriers are enabled, there will be no checksums in
815        // practice to verify.
816        let valid_to = checksum_list
817            .verify(device.as_ref(), valid_to)
818            .await
819            .context("Failed to validate checksums")?;
820
821        // Apply the mutations...
822
823        let mut last_checkpoint = reader.journal_file_checkpoint();
824        let mut journal_offsets = super_block.journal_file_offsets.clone();
825
826        // Start with the root-parent mutations, and also determine the journal offsets for all
827        // other objects.
828        for (
829            index,
830            JournaledTransaction {
831                checkpoint,
832                root_parent_mutations,
833                end_flush,
834                volume_deleted,
835                ..
836            },
837        ) in transactions.iter_mut().enumerate()
838        {
839            if checkpoint.file_offset >= valid_to {
840                last_checkpoint = checkpoint.clone();
841
842                // Truncate the transactions so we don't need to worry about them on the next pass.
843                transactions.truncate(index);
844                break;
845            }
846
847            let context = ApplyContext { mode: ApplyMode::Replay, checkpoint: checkpoint.clone() };
848            for mutation in root_parent_mutations.drain(..) {
849                self.objects
850                    .apply_mutation(
851                        super_block.root_parent_store_object_id,
852                        mutation,
853                        &context,
854                        AssocObj::None,
855                    )
856                    .context("Failed to replay root parent store mutations")?;
857            }
858
859            if let Some((object_id, journal_offset)) = end_flush {
860                journal_offsets.insert(*object_id, *journal_offset);
861            }
862
863            if let Some(object_id) = volume_deleted {
864                journal_offsets.insert(*object_id, VOLUME_DELETED);
865            }
866        }
867
868        // Now we can open the root store.
869        let root_store = ObjectStore::open(&root_parent, super_block.root_store_object_id, None)
870            .await
871            .context("Unable to open root store")?;
872
873        ensure!(
874            !root_store.is_encrypted(),
875            anyhow!(FxfsError::Inconsistent).context("Root store is encrypted")
876        );
877        self.objects.set_root_store(root_store);
878
879        let root_store_offset =
880            journal_offsets.get(&super_block.root_store_object_id).copied().unwrap_or(0);
881
882        // Now replay the root store mutations.
883        for JournaledTransaction { checkpoint, root_mutations, .. } in &mut transactions {
884            if checkpoint.file_offset < root_store_offset {
885                continue;
886            }
887
888            let context = ApplyContext { mode: ApplyMode::Replay, checkpoint: checkpoint.clone() };
889            for mutation in root_mutations.drain(..) {
890                self.objects
891                    .apply_mutation(
892                        super_block.root_store_object_id,
893                        mutation,
894                        &context,
895                        AssocObj::None,
896                    )
897                    .context("Failed to replay root store mutations")?;
898            }
899        }
900
901        // Now we can open the allocator.
902        allocator.open().await.context("Failed to open allocator")?;
903
904        // Now replay all other mutations.
905        for JournaledTransaction {
906            checkpoint,
907            non_root_mutations,
908            end_offset,
909            borrowed_metadata,
910            object_journal_usage,
911            ..
912        } in transactions
913        {
914            self.objects
915                .replay_mutations(
916                    non_root_mutations,
917                    &object_journal_usage,
918                    &journal_offsets,
919                    &ApplyContext { mode: ApplyMode::Replay, checkpoint },
920                    end_offset,
921                    borrowed_metadata,
922                )
923                .await
924                .context("Failed to replay mutations")?;
925        }
926
927        allocator.on_replay_complete().await.context("Failed to complete replay for allocator")?;
928
929        let discarded_to =
930            if last_checkpoint.file_offset != reader.journal_file_checkpoint().file_offset {
931                Some(reader.journal_file_checkpoint().file_offset)
932            } else {
933                None
934            };
935
936        // Configure the journal writer so that we can continue.
937        {
938            if last_checkpoint.file_offset < super_block.super_block_journal_file_offset {
939                return Err(anyhow!(FxfsError::Inconsistent).context(format!(
940                    "journal replay cut short; journal finishes at {}, but super-block was \
941                     written at {}",
942                    last_checkpoint.file_offset, super_block.super_block_journal_file_offset
943                )));
944            }
945            let handle = ObjectStore::open_object(
946                &root_parent,
947                super_block.journal_object_id,
948                journal_handle_options(),
949                None,
950            )
951            .await
952            .with_context(|| {
953                format!(
954                    "Failed to open journal file (object id: {})",
955                    super_block.journal_object_id
956                )
957            })?;
958            let _ = self.handle.set(handle);
959            let mut inner = self.inner.lock();
960            reader.skip_to_end_of_block();
961            let mut writer_checkpoint = reader.journal_file_checkpoint();
962
963            // Make sure we don't accidentally use the reader from now onwards.
964            std::mem::drop(reader);
965
966            // Reset the stream to indicate that we've remounted the journal.
967            writer_checkpoint.checksum ^= RESET_XOR;
968            writer_checkpoint.version = LATEST_VERSION;
969            inner.flushed_offset = writer_checkpoint.file_offset;
970
971            // When we open the filesystem as writable, we flush the device.
972            inner.device_flushed_offset = inner.flushed_offset;
973
974            inner.writer.seek(writer_checkpoint);
975            inner.output_reset_version = true;
976            inner.valid_to = last_checkpoint.file_offset;
977            if last_checkpoint.file_offset < inner.flushed_offset {
978                inner.discard_offset = Some(last_checkpoint.file_offset);
979            }
980        }
981
982        self.objects
983            .on_replay_complete()
984            .await
985            .context("Failed to complete replay for object manager")?;
986
987        let required = self.objects.required_reservation();
988        let borrowed = self.objects.borrowed_metadata_space();
989        info!(
990            checkpoint = last_checkpoint.file_offset,
991            discarded_to,
992            metadata_reservation:% =
993                format_args!("{}({} - {})", required - borrowed, required, borrowed),
994            store_borrowed:? = self.objects.stores_borrowed();
995            "replay complete"
996        );
997        Ok(())
998    }
999
1000    async fn read_transactions(
1001        &self,
1002        reader: &mut JournalReader,
1003        end_offset: Option<u64>,
1004        object_id_filter: u64,
1005    ) -> Result<JournaledTransactions, Error> {
1006        let mut transactions = Vec::new();
1007        let (mut device_flushed_offset, root_parent_store_object_id, root_store_object_id) = {
1008            let super_block = &self.inner.lock().super_block_header;
1009            (
1010                super_block.super_block_journal_file_offset,
1011                super_block.root_parent_store_object_id,
1012                super_block.root_store_object_id,
1013            )
1014        };
1015        let mut current_transaction = None;
1016        let mut begin_flush_offsets = HashMap::default();
1017        let mut stores_deleted = HashSet::new();
1018        loop {
1019            // Cache the checkpoint before we deserialize a record.
1020            let checkpoint = reader.journal_file_checkpoint();
1021            if let Some(end_offset) = end_offset {
1022                if checkpoint.file_offset >= end_offset {
1023                    break;
1024                }
1025            }
1026            let result = reader
1027                .deserialize::<JournalRecord>()
1028                .await
1029                .context("Failed to deserialize journal record")?;
1030            match result {
1031                ReadResult::Reset(_) => {
1032                    if current_transaction.is_some() {
1033                        current_transaction = None;
1034                        transactions.pop();
1035                    }
1036                    let offset = reader.journal_file_checkpoint().file_offset;
1037                    if offset > device_flushed_offset {
1038                        device_flushed_offset = offset;
1039                    }
1040                }
1041                ReadResult::Some(record) => match record {
1042                    JournalRecord::EndBlock => {
1043                        reader.skip_to_end_of_block();
1044                    }
1045                    JournalRecord::Mutation { object_id, mutation } => {
1046                        let record_start_offset = checkpoint.file_offset;
1047                        let current_transaction = match current_transaction.as_mut() {
1048                            None => {
1049                                transactions.push(JournaledTransaction::new(checkpoint));
1050                                current_transaction = transactions.last_mut();
1051                                current_transaction.as_mut().unwrap()
1052                            }
1053                            Some(transaction) => transaction,
1054                        };
1055
1056                        if stores_deleted.contains(&object_id) {
1057                            bail!(
1058                                anyhow!(FxfsError::Inconsistent)
1059                                    .context("Encountered mutations for deleted store")
1060                            );
1061                        }
1062
1063                        match &mutation {
1064                            Mutation::BeginFlush => {
1065                                begin_flush_offsets
1066                                    .insert(object_id, current_transaction.checkpoint.file_offset);
1067                            }
1068                            Mutation::EndFlush(_) => {
1069                                if let Some(offset) = begin_flush_offsets.remove(&object_id) {
1070                                    if let Some(deleted_volume) =
1071                                        &current_transaction.volume_deleted
1072                                    {
1073                                        if *deleted_volume == object_id {
1074                                            bail!(anyhow!(FxfsError::Inconsistent).context(
1075                                                "Multiple EndFlush/DeleteVolume mutations in a \
1076                                                single transaction for the same object"
1077                                            ));
1078                                        }
1079                                    }
1080                                    // `BeginFlush` seals the object's in-memory mutable layer so
1081                                    // all mutations prior to `BeginFlush` are written to the new
1082                                    // layer file referenced by `EndFlush`. Transactions committed
1083                                    // after `BeginFlush` (starting at `>= offset + 1`) are written
1084                                    // to the new mutable layer and must still be replayed; the
1085                                    // `BeginFlush` transaction itself at `offset` has no remaining
1086                                    // mutations to replay once `EndFlush` commits.
1087                                    if current_transaction
1088                                        .end_flush
1089                                        .replace((object_id, offset + 1))
1090                                        .is_some()
1091                                    {
1092                                        bail!(anyhow!(FxfsError::Inconsistent).context(
1093                                            "Multiple EndFlush mutations in a \
1094                                             single transaction"
1095                                        ));
1096                                    }
1097                                }
1098                            }
1099                            Mutation::DeleteVolume => {
1100                                if let Some((flushed_object, _)) = &current_transaction.end_flush {
1101                                    if *flushed_object == object_id {
1102                                        bail!(anyhow!(FxfsError::Inconsistent).context(
1103                                            "Multiple EndFlush/DeleteVolume mutations in a \
1104                                                single transaction for the same object"
1105                                        ));
1106                                    }
1107                                }
1108                                if current_transaction.volume_deleted.replace(object_id).is_some() {
1109                                    bail!(anyhow!(FxfsError::Inconsistent).context(
1110                                        "Multiple DeleteVolume mutations in a single \
1111                                         transaction"
1112                                    ));
1113                                }
1114                                stores_deleted.insert(object_id);
1115                            }
1116                            Mutation::UpdateBorrowed(borrowed) => {
1117                                ensure!(
1118                                    object_id == 0,
1119                                    anyhow!(FxfsError::Inconsistent)
1120                                        .context("Invalid object_id for UpdateBorrowed")
1121                                );
1122                                current_transaction.borrowed_metadata =
1123                                    BorrowedMetadata::LegacyBorrowed(*borrowed);
1124                                continue;
1125                            }
1126                            _ => {}
1127                        }
1128
1129                        let record_len =
1130                            reader.journal_file_checkpoint().file_offset - record_start_offset;
1131
1132                        // If this mutation doesn't need to be applied, don't bother adding it
1133                        // to the transaction.
1134                        if (object_id_filter == INVALID_OBJECT_ID || object_id_filter == object_id)
1135                            && self.should_apply(object_id, &current_transaction.checkpoint)
1136                        {
1137                            if object_id == root_parent_store_object_id {
1138                                current_transaction.root_parent_mutations.push(mutation);
1139                            } else if object_id == root_store_object_id {
1140                                current_transaction.root_mutations.push(mutation);
1141                            } else {
1142                                if mutation.counts_towards_object_journal_usage() {
1143                                    if let Some((last_id, usage)) =
1144                                        current_transaction.object_journal_usage.last_mut()
1145                                        && *last_id == object_id
1146                                    {
1147                                        *usage += record_len;
1148                                    } else if let Some((_, usage)) = current_transaction
1149                                        .object_journal_usage
1150                                        .iter_mut()
1151                                        .find(|(id, _)| *id == object_id)
1152                                    {
1153                                        *usage += record_len;
1154                                    } else {
1155                                        current_transaction
1156                                            .object_journal_usage
1157                                            .push((object_id, record_len));
1158                                    }
1159                                }
1160                                current_transaction.non_root_mutations.push((object_id, mutation));
1161                            }
1162                        }
1163                    }
1164                    JournalRecord::DataChecksums(
1165                        owner_object_id,
1166                        device_range,
1167                        checksums,
1168                        first_write,
1169                    ) => {
1170                        ensure!(
1171                            checkpoint.version.major < 60 || owner_object_id != INVALID_OBJECT_ID,
1172                            anyhow!(FxfsError::Inconsistent)
1173                                .context("Invalid owner_object_id in DataChecksums")
1174                        );
1175                        if stores_deleted.contains(&owner_object_id) {
1176                            bail!(
1177                                anyhow!(FxfsError::Inconsistent)
1178                                    .context("Encountered DataChecksums for deleted store")
1179                            );
1180                        }
1181                        let current_transaction = match current_transaction.as_mut() {
1182                            None => {
1183                                transactions.push(JournaledTransaction::new(checkpoint));
1184                                current_transaction = transactions.last_mut();
1185                                current_transaction.as_mut().unwrap()
1186                            }
1187                            Some(transaction) => transaction,
1188                        };
1189                        current_transaction.checksums.push(JournaledChecksums {
1190                            owner_object_id,
1191                            device_range,
1192                            checksums,
1193                            first_write,
1194                        });
1195                    }
1196                    JournalRecord::Commit | JournalRecord::CommitBorrowed(_) => {
1197                        // `pre_commit` can emit an empty `Borrowed` transaction
1198                        // (`CommitBorrowed` with no mutations) when `needs_borrow_for_journal`
1199                        // is true after a journal reset or discard. Recording it during full
1200                        // journal replay advances `last_end_offset` so that a subsequent funded
1201                        // transaction is not charged for the discarded/padded journal range.
1202                        if current_transaction.is_none()
1203                            && object_id_filter == INVALID_OBJECT_ID
1204                            && matches!(record, JournalRecord::CommitBorrowed(_))
1205                        {
1206                            transactions.push(JournaledTransaction::new(checkpoint));
1207                            current_transaction = transactions.last_mut();
1208                        }
1209                        if let Some(&mut JournaledTransaction {
1210                            ref checkpoint,
1211                            ref root_parent_mutations,
1212                            ref mut end_offset,
1213                            ref mut borrowed_metadata,
1214                            ..
1215                        }) = current_transaction.take()
1216                        {
1217                            for mutation in root_parent_mutations {
1218                                // Snoop the mutations for any that might apply to the journal
1219                                // file so that we can pass them to the reader so that it can
1220                                // read the journal file.
1221                                if let Mutation::ObjectStore(ObjectStoreMutation {
1222                                    item:
1223                                        Item {
1224                                            key:
1225                                                ObjectKey {
1226                                                    object_id,
1227                                                    data:
1228                                                        ObjectKeyData::Attribute(
1229                                                            AttributeId::DATA,
1230                                                            AttributeKey::Extent(extent),
1231                                                        ),
1232                                                    ..
1233                                                },
1234                                            value:
1235                                                ObjectValue::Extent(ExtentValue::Some {
1236                                                    device_offset,
1237                                                    ..
1238                                                }),
1239                                            ..
1240                                        },
1241                                    ..
1242                                }) = mutation
1243                                {
1244                                    // Add the journal extents we find on the way to our
1245                                    // reader.
1246                                    let handle = reader.handle();
1247                                    if *object_id != handle.object_id() {
1248                                        continue;
1249                                    }
1250                                    if let Some(end_offset) = handle.end_offset() {
1251                                        if extent.start != end_offset {
1252                                            bail!(anyhow!(FxfsError::Inconsistent).context(
1253                                                format!(
1254                                                    "Unexpected journal extent {:?} -> {}, \
1255                                                       expected start: {}",
1256                                                    *extent, device_offset, end_offset,
1257                                                )
1258                                            ));
1259                                        }
1260                                    }
1261                                    handle.push_extent(
1262                                        checkpoint.file_offset,
1263                                        *device_offset
1264                                            ..*device_offset
1265                                                + extent.length().context("Invalid extent")?,
1266                                    );
1267                                }
1268                            }
1269                            *end_offset = reader.journal_file_checkpoint().file_offset;
1270                            if let JournalRecord::CommitBorrowed(borrowed) = record {
1271                                *borrowed_metadata = BorrowedMetadata::Borrowed(borrowed);
1272                            }
1273                        }
1274                    }
1275                    JournalRecord::Discard(offset) => {
1276                        if offset == 0 {
1277                            bail!(
1278                                anyhow!(FxfsError::Inconsistent)
1279                                    .context("Invalid offset for Discard")
1280                            );
1281                        }
1282                        if let Some(transaction) = current_transaction.as_ref() {
1283                            if transaction.checkpoint.file_offset < offset {
1284                                // Odd, but OK.
1285                                continue;
1286                            }
1287                        }
1288                        current_transaction = None;
1289                        while let Some(transaction) = transactions.last() {
1290                            if transaction.checkpoint.file_offset < offset {
1291                                break;
1292                            }
1293                            transactions.pop();
1294                        }
1295                        reader.handle().discard_extents(offset);
1296                    }
1297                    JournalRecord::DidFlushDevice(offset) => {
1298                        if offset > device_flushed_offset {
1299                            device_flushed_offset = offset;
1300                        }
1301                    }
1302                },
1303
1304                // This is expected when we reach the end of the journal stream.
1305                ReadResult::ChecksumMismatch => break,
1306            }
1307        }
1308
1309        // Discard any uncommitted transaction.
1310        if current_transaction.is_some() {
1311            transactions.pop();
1312        }
1313
1314        Ok(JournaledTransactions { transactions, device_flushed_offset })
1315    }
1316
1317    /// Creates an empty filesystem with the minimum viable objects (including a root parent and
1318    /// root store but no further child stores).
1319    pub async fn init_empty(&self, filesystem: Arc<FxFilesystem>) -> Result<(), Error> {
1320        // The following constants are only used at format time. When mounting, the recorded values
1321        // in the superblock should be used.  The root parent store does not have a parent, but
1322        // needs an object ID to be registered with ObjectManager, so it cannot collide (i.e. have
1323        // the same object ID) with any objects in the root store that use the journal to track
1324        // mutations.
1325        const INIT_ROOT_PARENT_STORE_OBJECT_ID: u64 = 3;
1326        const INIT_ROOT_STORE_OBJECT_ID: u64 = 4;
1327        const INIT_ALLOCATOR_OBJECT_ID: u64 = 5;
1328
1329        info!(device_size = filesystem.device().size(); "Formatting");
1330
1331        let checkpoint = JournalCheckpoint {
1332            version: LATEST_VERSION,
1333            ..self.inner.lock().writer.journal_file_checkpoint()
1334        };
1335
1336        let mut current_generation = 1;
1337        if filesystem.options().image_builder_mode.is_some() {
1338            // Note that in non-image_builder_mode we write both superblocks when we format
1339            // (in FxFilesystemBuilder::open). In image_builder_mode we only write once at the end
1340            // as part of finalize(), which is why we must make sure the generation we write is
1341            // newer than any existing generation.
1342
1343            // Note: This should is the *filesystem* block size, not the device block size which
1344            // is currently always 4096 (https://fxbug.dev/42063349)
1345            let block_size = filesystem.block_size();
1346            match self.read_superblocks(filesystem.device(), block_size).await {
1347                Ok((super_block, _)) => {
1348                    log::info!(
1349                        "Found existing superblock with generation {}. Bumping by 1.",
1350                        super_block.generation
1351                    );
1352                    current_generation = super_block.generation.wrapping_add(1);
1353                }
1354                Err(_) => {
1355                    // TODO(https://fxbug.dev/463757813): It's not unusual to fail to read
1356                    // superblocks when we're formatting a new filesystem but we should probably
1357                    // fail the format if we get an IO error.
1358                }
1359            }
1360        }
1361
1362        let root_parent = ObjectStore::new_empty(
1363            None,
1364            INIT_ROOT_PARENT_STORE_OBJECT_ID,
1365            filesystem.clone(),
1366            None,
1367        );
1368        self.objects.set_root_parent_store(root_parent.clone());
1369
1370        let allocator = Arc::new(Allocator::new(filesystem.clone(), INIT_ALLOCATOR_OBJECT_ID));
1371        self.objects.set_allocator(allocator.clone());
1372        self.objects.init_metadata_reservation().await?;
1373
1374        let journal_handle;
1375        let super_block_a_handle;
1376        let super_block_b_handle;
1377        let root_store;
1378        let mut transaction = root_parent
1379            .new_transaction(
1380                lock_keys![],
1381                Options { skip_journal_checks: true, ..Default::default() },
1382            )
1383            .await?;
1384        root_store = root_parent
1385            .new_child_store(
1386                &mut transaction,
1387                NewChildStoreOptions { object_id: INIT_ROOT_STORE_OBJECT_ID, ..Default::default() },
1388                None,
1389            )
1390            .await
1391            .context("new_child_store")?;
1392        self.objects.set_root_store(root_store.clone());
1393
1394        allocator.create(&mut transaction).await?;
1395
1396        // Create the super-block objects...
1397        super_block_a_handle = ObjectStore::create_object_with_id(
1398            &root_store,
1399            &mut transaction,
1400            ReservedId::new(&root_store, NonZero::new(SuperBlockInstance::A.object_id()).unwrap()),
1401            HandleOptions::default(),
1402            None,
1403        )
1404        .context("create super block")?;
1405        root_store.update_last_object_id(SuperBlockInstance::A.object_id());
1406        super_block_a_handle
1407            .extend(&mut transaction, SuperBlockInstance::A.first_extent())
1408            .await
1409            .context("extend super block")?;
1410        super_block_b_handle = ObjectStore::create_object_with_id(
1411            &root_store,
1412            &mut transaction,
1413            ReservedId::new(&root_store, NonZero::new(SuperBlockInstance::B.object_id()).unwrap()),
1414            HandleOptions::default(),
1415            None,
1416        )
1417        .context("create super block")?;
1418        root_store.update_last_object_id(SuperBlockInstance::B.object_id());
1419        super_block_b_handle
1420            .extend(&mut transaction, SuperBlockInstance::B.first_extent())
1421            .await
1422            .context("extend super block")?;
1423
1424        // the journal object...
1425        journal_handle = ObjectStore::create_object(
1426            &root_parent,
1427            &mut transaction,
1428            journal_handle_options(),
1429            None,
1430        )
1431        .await
1432        .context("create journal")?;
1433        if self.inner.lock().image_builder_mode.is_none() {
1434            let mut file_range = 0..self.chunk_size();
1435            journal_handle
1436                .preallocate_range(&mut transaction, &mut file_range)
1437                .await
1438                .context("preallocate journal")?;
1439            if file_range.start < file_range.end {
1440                bail!("preallocate_range returned too little space");
1441            }
1442        }
1443
1444        // Write the root store object info.
1445        root_store.create(&mut transaction).await?;
1446
1447        // The root parent graveyard.
1448        root_parent.set_graveyard_directory_object_id(
1449            Graveyard::create(&mut transaction, &root_parent).await?,
1450        );
1451
1452        transaction.commit().await?;
1453
1454        self.inner.lock().super_block_header = SuperBlockHeader::new(
1455            current_generation,
1456            root_parent.store_object_id(),
1457            root_parent.graveyard_directory_object_id(),
1458            root_store.store_object_id(),
1459            allocator.object_id(),
1460            journal_handle.object_id(),
1461            checkpoint,
1462            /* earliest_version: */ LATEST_VERSION,
1463        );
1464
1465        // Initialize the journal writer.
1466        let _ = self.handle.set(journal_handle);
1467        Ok(())
1468    }
1469
1470    /// Finalizes a filesystem built in `image_builder_mode` by preallocating the journal, flushing
1471    /// all in-memory object store mutations to layer files, funding the metadata reservation for
1472    /// those layers, and writing the super-block.
1473    pub async fn finalize_image_builder(&self) -> Result<(), Error> {
1474        let handle = self.handle.get().unwrap();
1475        let mut transaction = handle
1476            .store()
1477            .new_transaction(
1478                lock_keys![LockKey::object(handle.store().store_object_id(), handle.object_id())],
1479                Options { skip_journal_checks: true, ..Default::default() },
1480            )
1481            .await?;
1482        let mut file_range = 0..self.chunk_size();
1483        handle
1484            .preallocate_range(&mut transaction, &mut file_range)
1485            .await
1486            .context("preallocate journal")?;
1487        if file_range.start < file_range.end {
1488            bail!("preallocate_range returned too little space");
1489        }
1490        transaction.commit().await?;
1491
1492        {
1493            let mut inner = self.inner.lock();
1494            inner.image_builder_mode = None;
1495            inner.compaction_state = CompactionState::Forced;
1496        }
1497        scopeguard::defer! {
1498            let mut inner = self.inner.lock();
1499            inner.compaction_state = CompactionState::Idle;
1500            if let Some(waker) = inner.flush_waker.take() {
1501                waker.wake();
1502            }
1503            self.reclaim_event.notify(usize::MAX);
1504        }
1505
1506        let earliest_version = self
1507            .objects
1508            .flush(FlushReason::Journal(ForceMajor::False))
1509            .await
1510            .context("Failed to flush objects")?;
1511        self.inner.lock().super_block_header.earliest_version = earliest_version;
1512
1513        // In image builder mode, transactions borrow metadata space and allocate directly from the
1514        // allocator rather than from `metadata_reservation`. Now that all stores have been flushed
1515        // and their layer sizes are known, top up `metadata_reservation` to
1516        // `required_reservation()` so that the finalized image starts with a fully funded metadata
1517        // reservation.
1518        let reservation = self.objects.metadata_reservation();
1519        let needed = self.objects.required_reservation().saturating_sub(reservation.amount());
1520        if needed > 0 {
1521            reservation.add(
1522                self.objects
1523                    .allocator()
1524                    .reserve(None, needed)
1525                    .ok_or(FxfsError::NoSpace)
1526                    .context("Failed to reserve metadata space in image builder mode")?
1527                    .forget(),
1528            );
1529        }
1530
1531        self.write_super_block().await.context("Failed to write superblock")
1532    }
1533
1534    pub async fn init_superblocks(&self) -> Result<(), Error> {
1535        // Overwrite both superblocks.
1536        for _ in 0..2 {
1537            self.write_super_block().await?;
1538        }
1539        Ok(())
1540    }
1541
1542    /// Takes a snapshot of all journaled transactions which affect |object_id| since its last
1543    /// flush.
1544    /// The caller is responsible for locking; it must ensure that the journal is not trimmed during
1545    /// this call.  For example, a Flush lock could be held on the object in question (assuming that
1546    /// object has data to flush and is registered with ObjectManager).
1547    pub async fn read_transactions_for_object(
1548        &self,
1549        object_id: u64,
1550    ) -> Result<Vec<JournaledTransaction>, Error> {
1551        let handle = self.handle.get().expect("No journal handle");
1552        // Reopen the handle since JournalReader needs an owned handle.
1553        let handle = ObjectStore::open_object(
1554            handle.owner(),
1555            handle.object_id(),
1556            journal_handle_options(),
1557            None,
1558        )
1559        .await?;
1560
1561        let checkpoint = match self.objects.journal_checkpoint(object_id) {
1562            Some(checkpoint) => checkpoint,
1563            None => return Ok(vec![]),
1564        };
1565        let mut reader = JournalReader::new(handle, &checkpoint);
1566        // Record the current end offset and only read to there, so we don't accidentally read any
1567        // partially flushed blocks.
1568        let end_offset = self.inner.lock().valid_to;
1569        Ok(self.read_transactions(&mut reader, Some(end_offset), object_id).await?.transactions)
1570    }
1571
1572    /// Commits a transaction.  This is not thread safe; the caller must take appropriate locks.
1573    pub async fn commit(&self, transaction: &mut Transaction<'_>) -> Result<u64, Error> {
1574        if transaction.is_empty() {
1575            return Ok(self.inner.lock().writer.journal_file_checkpoint().file_offset);
1576        }
1577
1578        self.pre_commit(transaction).await?;
1579        Ok(self.write_and_apply_mutations(transaction))
1580    }
1581
1582    // Before we commit, we might need to extend the journal or write pending records to the
1583    // journal.
1584    async fn pre_commit(&self, _transaction: &Transaction<'_>) -> Result<(), Error> {
1585        let handle;
1586
1587        let (size, zero_offset) = {
1588            let mut inner = self.inner.lock();
1589
1590            // If this is the first write after a RESET, we need to output version first.
1591            if std::mem::take(&mut inner.output_reset_version) {
1592                LATEST_VERSION.serialize_into(&mut inner.writer)?;
1593            }
1594
1595            if let Some(discard_offset) = inner.discard_offset {
1596                JournalRecord::Discard(discard_offset).serialize_into(&mut inner.writer)?;
1597                inner.discard_offset = None;
1598            }
1599
1600            if inner.needs_did_flush_device {
1601                let offset = inner.device_flushed_offset;
1602                JournalRecord::DidFlushDevice(offset).serialize_into(&mut inner.writer)?;
1603                inner.needs_did_flush_device = false;
1604            }
1605
1606            handle = match self.handle.get() {
1607                None => return Ok(()),
1608                Some(x) => x,
1609            };
1610
1611            let file_offset = inner.writer.journal_file_checkpoint().file_offset;
1612
1613            let size = handle.get_size();
1614            let size = if file_offset + self.chunk_size() > size { Some(size) } else { None };
1615
1616            if size.is_none()
1617                && inner.zero_offset.is_none()
1618                && !self.objects.needs_borrow_for_journal(file_offset)
1619            {
1620                return Ok(());
1621            }
1622
1623            (size, inner.zero_offset)
1624        };
1625
1626        let mut transaction = handle
1627            .new_transaction_with_options(Options {
1628                skip_journal_checks: true,
1629                reservation: ReservationOptions::Borrowed,
1630                ..Default::default()
1631            })
1632            .await?;
1633        if let Some(size) = size {
1634            handle
1635                .preallocate_range(&mut transaction, &mut (size..size + self.chunk_size()))
1636                .await?;
1637        }
1638        if let Some(zero_offset) = zero_offset {
1639            handle.zero(&mut transaction, 0..zero_offset).await?;
1640        }
1641
1642        // We can't use regular transaction commit, because that can cause re-entrancy issues, so
1643        // instead we just apply the transaction directly here.
1644        self.write_and_apply_mutations(&mut transaction);
1645
1646        let mut inner = self.inner.lock();
1647
1648        // Make sure the transaction to extend the journal made it to the journal within the old
1649        // size, since otherwise, it won't be possible to replay.
1650        if let Some(size) = size {
1651            assert!(inner.writer.journal_file_checkpoint().file_offset < size);
1652        }
1653
1654        if inner.zero_offset == zero_offset {
1655            inner.zero_offset = None;
1656        }
1657
1658        Ok(())
1659    }
1660
1661    // Determines whether a mutation at the given checkpoint should be applied.  During replay, not
1662    // all records should be applied because the object store or allocator might already contain the
1663    // mutation.  After replay, that obviously isn't the case and we want to apply all mutations.
1664    fn should_apply(&self, object_id: u64, journal_file_checkpoint: &JournalCheckpoint) -> bool {
1665        let super_block_header = &self.inner.lock().super_block_header;
1666        let offset = super_block_header
1667            .journal_file_offsets
1668            .get(&object_id)
1669            .cloned()
1670            .unwrap_or(super_block_header.super_block_journal_file_offset);
1671        journal_file_checkpoint.file_offset >= offset
1672    }
1673
1674    /// Flushes previous writes to the device and then writes out a new super-block.
1675    /// Callers must ensure that we do not make concurrent calls.
1676    async fn write_super_block(&self) -> Result<(), Error> {
1677        let root_parent_store = self.objects.root_parent_store();
1678
1679        // We need to flush previous writes to the device since the new super-block we are writing
1680        // relies on written data being observable, and we also need to lock the root parent store
1681        // so that no new entries are written to it whilst we are writing the super-block, and for
1682        // that we use the write lock.
1683        let old_layers;
1684        let old_super_block_offset;
1685        let mut new_super_block_header;
1686        let checkpoint;
1687        let reserved_metadata_space;
1688
1689        {
1690            let _sync_guard = debug_assert_not_too_long!(self.sync_mutex.lock());
1691            {
1692                let _write_guard = self.writer_mutex.lock();
1693                checkpoint = self.pad_to_block()?;
1694                reserved_metadata_space =
1695                    self.objects.reconcile_metadata_reservation(checkpoint.file_offset);
1696                old_layers = super_block::compact_root_parent(&*root_parent_store)?;
1697            }
1698            self.flush_device(checkpoint.file_offset)
1699                .await
1700                .context("flush failed when writing superblock")?;
1701        }
1702
1703        new_super_block_header = self.inner.lock().super_block_header.clone();
1704
1705        old_super_block_offset = new_super_block_header.journal_checkpoint.file_offset;
1706
1707        let (journal_file_offsets, min_checkpoint) = self.objects.journal_file_offsets();
1708
1709        new_super_block_header.generation = new_super_block_header.generation.wrapping_add(1);
1710        new_super_block_header.super_block_journal_file_offset = checkpoint.file_offset;
1711        new_super_block_header.journal_checkpoint = min_checkpoint.unwrap_or(checkpoint);
1712        new_super_block_header.journal_checkpoint.version = LATEST_VERSION;
1713        new_super_block_header.journal_file_offsets = journal_file_offsets;
1714        new_super_block_header.reserved_metadata_space = reserved_metadata_space;
1715
1716        self.super_block_manager
1717            .save(
1718                new_super_block_header.clone(),
1719                self.objects.root_parent_store().filesystem(),
1720                old_layers,
1721            )
1722            .await?;
1723        {
1724            let mut inner = self.inner.lock();
1725            inner.super_block_header = new_super_block_header;
1726            inner.zero_offset = Some(BLOCK_SIZE.align_down(old_super_block_offset));
1727        }
1728
1729        Ok(())
1730    }
1731
1732    /// Flushes any buffered journal data to the device.  Note that this does not flush the device
1733    /// unless the flush_device option is set, in which case data should have been persisted to
1734    /// lower layers.  If a precondition is supplied, it is evaluated and the sync will be skipped
1735    /// if it returns false.  This allows callers to check a condition whilst a lock is held.  If a
1736    /// sync is performed, this function returns the checkpoint that was flushed.
1737    pub async fn sync(&self, options: SyncOptions<'_>) -> Result<Option<JournalCheckpoint>, Error> {
1738        let _guard = debug_assert_not_too_long!(self.sync_mutex.lock());
1739
1740        let checkpoint = {
1741            if let Some(precondition) = options.precondition {
1742                if !precondition() {
1743                    return Ok(None);
1744                }
1745            }
1746
1747            // This guard is required so that we don't insert an EndBlock record in the middle of a
1748            // transaction.
1749            let _guard = self.writer_mutex.lock();
1750
1751            self.pad_to_block()?
1752        };
1753
1754        if options.flush_device {
1755            self.flush_device(checkpoint.file_offset).await.context("sync: flush failed")?;
1756        }
1757
1758        Ok(Some(checkpoint))
1759    }
1760
1761    // Returns the checkpoint as it was prior to padding.  This is done because the super block
1762    // needs to record where the last transaction ends and it's the next transaction that pays the
1763    // price of the padding.
1764    fn pad_to_block(&self) -> Result<JournalCheckpoint, Error> {
1765        let mut inner = self.inner.lock();
1766        let checkpoint = inner.writer.journal_file_checkpoint();
1767        if checkpoint.file_offset % BLOCK_SIZE != 0 {
1768            JournalRecord::EndBlock.serialize_into(&mut inner.writer)?;
1769            inner.writer.pad_to_block()?;
1770            if let Some(waker) = inner.flush_waker.take() {
1771                waker.wake();
1772            }
1773        }
1774        Ok(checkpoint)
1775    }
1776
1777    async fn flush_device(&self, checkpoint_offset: u64) -> Result<(), Error> {
1778        assert!(
1779            self.inner.lock().image_builder_mode.is_none(),
1780            "flush_device called in image builder mode"
1781        );
1782        debug_assert_not_too_long!(poll_fn(|ctx| {
1783            let mut inner = self.inner.lock();
1784            if inner.flushed_offset >= checkpoint_offset {
1785                Poll::Ready(Ok(()))
1786            } else if inner.terminate {
1787                let context = inner
1788                    .terminate_reason
1789                    .as_ref()
1790                    .map(|e| format!("Journal closed with error: {:?}", e))
1791                    .unwrap_or_else(|| "Journal closed".to_string());
1792                Poll::Ready(Err(anyhow!(FxfsError::JournalFlushError).context(context)))
1793            } else {
1794                inner.sync_waker = Some(ctx.waker().clone());
1795                Poll::Pending
1796            }
1797        }))?;
1798
1799        let needs_flush = self.inner.lock().device_flushed_offset < checkpoint_offset;
1800        if needs_flush {
1801            let trace = self.trace.load(Ordering::Relaxed);
1802            if trace {
1803                info!("J: start flush device");
1804            }
1805            self.handle.get().unwrap().flush_device().await?;
1806            if trace {
1807                info!("J: end flush device");
1808            }
1809
1810            // We need to write a DidFlushDevice record at some point, but if we are in the
1811            // process of shutting down the filesystem, we want to leave the journal clean to
1812            // avoid there being log messages complaining about unwritten journal data, so we
1813            // queue it up so that the next transaction will trigger this record to be written.
1814            // If we are shutting down, that will never happen but since the DidFlushDevice
1815            // message is purely advisory (it reduces the number of checksums we have to verify
1816            // during replay), it doesn't matter if it isn't written.
1817            {
1818                let mut inner = self.inner.lock();
1819                inner.device_flushed_offset = checkpoint_offset;
1820                inner.needs_did_flush_device = true;
1821            }
1822
1823            // Tell the allocator that we flushed the device so that it can now start using
1824            // space that was deallocated.
1825            self.objects.allocator().did_flush_device(checkpoint_offset);
1826            if trace {
1827                info!("J: did flush device");
1828            }
1829        }
1830
1831        Ok(())
1832    }
1833
1834    /// Returns a copy of the super-block header.
1835    pub fn super_block_header(&self) -> SuperBlockHeader {
1836        self.inner.lock().super_block_header.clone()
1837    }
1838
1839    /// Waits for there to be sufficient space in the journal.
1840    pub async fn check_journal_space(&self) -> Result<(), Error> {
1841        loop {
1842            let listener = {
1843                let inner = self.inner.lock();
1844                if inner.terminate {
1845                    // If the flush error is set, this will never make progress, since we can't
1846                    // extend the journal any more.
1847                    let context = inner
1848                        .terminate_reason
1849                        .as_ref()
1850                        .map(|e| format!("Journal closed with error: {:?}", e))
1851                        .unwrap_or_else(|| "Journal closed".to_string());
1852                    return Err(anyhow!(FxfsError::JournalFlushError).context(context));
1853                }
1854                if self.objects.last_end_offset()
1855                    - inner.super_block_header.journal_checkpoint.file_offset
1856                    < inner.reclaim_size
1857                {
1858                    return Ok(());
1859                }
1860                if inner.image_builder_mode.is_some() {
1861                    return Ok(());
1862                }
1863                if inner.disable_compactions {
1864                    return Err(
1865                        anyhow!(FxfsError::JournalFlushError).context("Compactions disabled")
1866                    );
1867                }
1868                self.reclaim_event.listen()
1869            };
1870            self.hooks.on_waiting_for_journal_space();
1871            debug_assert_not_too_long!(listener);
1872        }
1873    }
1874
1875    fn chunk_size(&self) -> u64 {
1876        CHUNK_SIZE
1877    }
1878
1879    fn write_and_apply_mutations(&self, transaction: &mut Transaction<'_>) -> u64 {
1880        let checkpoint_before;
1881        let checkpoint_after;
1882        let mut object_usages = SmallVec::<[(u64, u64); 4]>::new();
1883        {
1884            let _guard = self.writer_mutex.lock();
1885            checkpoint_before = {
1886                let mut inner = self.inner.lock();
1887                if transaction.includes_write() {
1888                    inner.needs_barrier = true;
1889                }
1890                let checkpoint = inner.writer.journal_file_checkpoint();
1891                let mut iter = transaction.mutations().iter().peekable();
1892                while let Some(store_iter) = ObjectMutationIterator::new(&mut iter) {
1893                    let object_id = store_iter.object_id();
1894                    let mut usage = 0;
1895                    self.objects.write_mutations(
1896                        object_id,
1897                        store_iter,
1898                        Writer { object_id, writer: &mut inner.writer, usage: &mut usage },
1899                    );
1900                    if usage > 0 {
1901                        object_usages.push((object_id, usage));
1902                    }
1903                }
1904                checkpoint
1905            };
1906            self.objects.apply_transaction(transaction, &checkpoint_before).expect(
1907                "apply_transaction should not fail in live mode; \
1908                 filesystem will be in an inconsistent state",
1909            );
1910            checkpoint_after = {
1911                let mut inner = self.inner.lock();
1912                for (owner_object_id, device_range, checksums, first_write) in
1913                    transaction.take_checksums().into_iter()
1914                {
1915                    inner
1916                        .writer
1917                        .write_record(&JournalRecord::DataChecksums(
1918                            owner_object_id,
1919                            device_range,
1920                            Checksums::fletcher(checksums),
1921                            first_write,
1922                        ))
1923                        .unwrap();
1924                }
1925                let commit_record = if transaction.metadata_reservation.is_borrowed() {
1926                    JournalRecord::CommitBorrowed(self.objects.metadata_reservation().amount())
1927                } else {
1928                    JournalRecord::Commit
1929                };
1930                inner.writer.write_record(&commit_record).unwrap();
1931
1932                inner.writer.journal_file_checkpoint()
1933            };
1934        }
1935        self.objects.did_commit_transaction(
1936            transaction,
1937            checkpoint_after.file_offset,
1938            &object_usages,
1939        );
1940
1941        if let Some(waker) = self.inner.lock().flush_waker.take() {
1942            waker.wake();
1943        }
1944
1945        checkpoint_before.file_offset
1946    }
1947
1948    /// This task will flush journal data to the device when there is data that needs flushing, and
1949    /// trigger compactions when short of journal space.  It will return after the terminate method
1950    /// has been called, or an error is encountered with either flushing or compaction.
1951    pub async fn flush_task(self: Arc<Self>) {
1952        let mut flush_fut = None;
1953        let mut compact_fut = None;
1954        let mut flush_error = false;
1955        poll_fn(|ctx| {
1956            loop {
1957                {
1958                    let mut inner = self.inner.lock();
1959                    if flush_fut.is_none() && !flush_error && self.handle.get().is_some() {
1960                        let flushable = inner.writer.flushable_bytes();
1961                        if flushable > 0 {
1962                            flush_fut = Some(Box::pin(self.flush(flushable)));
1963                        }
1964                    }
1965                    if inner.terminate && flush_fut.is_none() && compact_fut.is_none() {
1966                        return Poll::Ready(());
1967                    }
1968                    // `journal_bytes` refers to bytes in the journal that haven't yet been flushed
1969                    // to the layer files. It increases with each transaction and decreases when
1970                    // compactions complete. The flush task is woken whenever a transaction is
1971                    // committed and we should see this metric updated regularly.
1972                    let journal_bytes = self.objects.last_end_offset()
1973                        - inner.super_block_header.journal_checkpoint.file_offset;
1974                    fxfs_trace::counter!("journal-bytes", 0, "total" => journal_bytes);
1975                    // The / 2 is here because after compacting, we cannot reclaim the space until
1976                    // the _next_ time we flush the device since the super-block is not guaranteed
1977                    // to persist until then.
1978                    if compact_fut.is_none()
1979                        && !inner.compaction_state.is_running()
1980                        && !inner.terminate
1981                        && !inner.disable_compactions
1982                        && !inner.compactions_paused
1983                        && inner.image_builder_mode.is_none()
1984                        && journal_bytes > inner.reclaim_size / 2
1985                    {
1986                        compact_fut = Some(Box::pin(self.compact()));
1987                        inner.compaction_state = CompactionState::Running;
1988                    }
1989                    inner.flush_waker = Some(ctx.waker().clone());
1990                }
1991                let mut pending = true;
1992                if let Some(fut) = flush_fut.as_mut() {
1993                    if let Poll::Ready(result) = fut.poll_unpin(ctx) {
1994                        if let Err(e) = result {
1995                            self.inner.lock().terminate(Some(e.context("Flush error")));
1996                            self.reclaim_event.notify(usize::MAX);
1997                            flush_error = true;
1998                        }
1999                        flush_fut = None;
2000                        pending = false;
2001                    }
2002                }
2003                if let Some(fut) = compact_fut.as_mut() {
2004                    if let Poll::Ready(result) = fut.poll_unpin(ctx) {
2005                        let mut inner = self.inner.lock();
2006                        if let Err(e) = result {
2007                            inner.terminate(Some(e.context("Compaction error")));
2008                        }
2009                        compact_fut = None;
2010                        inner.compaction_state = CompactionState::Idle;
2011                        self.reclaim_event.notify(usize::MAX);
2012                        pending = false;
2013                        fxfs_trace::counter!(
2014                            "journal-bytes",
2015                            0,
2016                            "total" => self.objects.last_end_offset()
2017                                - inner.super_block_header.journal_checkpoint.file_offset
2018                        );
2019                    }
2020                }
2021                if pending {
2022                    return Poll::Pending;
2023                }
2024            }
2025        })
2026        .await;
2027    }
2028
2029    /// Returns a yielder that can be used for compactions.
2030    pub fn get_compaction_yielder(&self) -> CompactionYielder<'_> {
2031        CompactionYielder::new(self)
2032    }
2033
2034    async fn flush(&self, amount: usize) -> Result<(), Error> {
2035        let handle = self.handle.get().unwrap();
2036        let mut buf = handle.allocate_buffer(amount).await;
2037        let (offset, len, barrier_on_first_write) = {
2038            let mut inner = self.inner.lock();
2039            let offset = inner.writer.take_flushable(buf.as_mut());
2040            let barrier_on_first_write = inner.needs_barrier && inner.barriers_enabled;
2041            // Reset `needs_barrier` before instead of after the overwrite in case a txn commit
2042            // that contains data happens during the overwrite.
2043            inner.needs_barrier = false;
2044            (offset, buf.len() as u64, barrier_on_first_write)
2045        };
2046        self.handle
2047            .get()
2048            .unwrap()
2049            .overwrite(
2050                offset,
2051                buf.as_mut(),
2052                OverwriteOptions { barrier_on_first_write, ..Default::default() },
2053            )
2054            .await?;
2055
2056        let mut inner = self.inner.lock();
2057        if let Some(waker) = inner.sync_waker.take() {
2058            waker.wake();
2059        }
2060        inner.flushed_offset = offset + len;
2061        inner.valid_to = inner.flushed_offset;
2062        Ok(())
2063    }
2064
2065    #[trace]
2066    async fn compact(&self) -> Result<(), Error> {
2067        assert!(
2068            self.inner.lock().image_builder_mode.is_none(),
2069            "compact called in image builder mode"
2070        );
2071        let bytes_before = self.objects.compaction_bytes_written();
2072        let _measure = crate::metrics::DurationMeasureScope::new(
2073            &crate::metrics::lsm_tree_metrics().journal_compaction_time,
2074        );
2075        crate::metrics::lsm_tree_metrics().journal_compactions_total.add(1);
2076        let trace = self.trace.load(Ordering::Relaxed);
2077        debug!("Compaction starting");
2078        if trace {
2079            info!("J: start compaction");
2080        }
2081        let earliest_version = self
2082            .objects
2083            .flush(FlushReason::Journal(ForceMajor::False))
2084            .await
2085            .context("Failed to flush objects")?;
2086        self.inner.lock().super_block_header.earliest_version = earliest_version;
2087        self.write_super_block().await.context("Failed to write superblock")?;
2088        if trace {
2089            info!("J: end compaction");
2090        }
2091        debug!("Compaction finished");
2092        let bytes_after = self.objects.compaction_bytes_written();
2093        crate::metrics::lsm_tree_metrics()
2094            .journal_compaction_bytes_written
2095            .add(bytes_after.saturating_sub(bytes_before));
2096        Ok(())
2097    }
2098
2099    /// This should generally NOT be called externally. It is public to allow use by FIDL service
2100    /// fxfs.Debug.
2101    pub async fn force_compact(&self) -> Result<(), Error> {
2102        loop {
2103            debug_assert_not_too_long!({
2104                let mut inner = self.inner.lock();
2105                if !inner.compaction_state.is_running() {
2106                    inner.compaction_state = CompactionState::Forced;
2107                    break;
2108                }
2109                self.reclaim_event.listen()
2110            });
2111        }
2112        scopeguard::defer! {
2113            let mut inner = self.inner.lock();
2114            inner.compaction_state = CompactionState::Idle;
2115            if let Some(waker) = inner.flush_waker.take() {
2116                waker.wake();
2117            }
2118            self.reclaim_event.notify(usize::MAX);
2119        }
2120        self.compact().await
2121    }
2122
2123    pub async fn stop_compactions(&self) {
2124        loop {
2125            debug_assert_not_too_long!({
2126                let mut inner = self.inner.lock();
2127                inner.disable_compactions = true;
2128                if !inner.compaction_state.is_running() {
2129                    return;
2130                }
2131                self.reclaim_event.listen()
2132            });
2133        }
2134    }
2135
2136    pub async fn pause_compactions(&self) {
2137        loop {
2138            debug_assert_not_too_long!({
2139                let mut inner = self.inner.lock();
2140                inner.compactions_paused = true;
2141                if !inner.compaction_state.is_running() {
2142                    return;
2143                }
2144                self.reclaim_event.listen()
2145            });
2146        }
2147    }
2148
2149    pub fn resume_compactions(&self) {
2150        let mut inner = self.inner.lock();
2151        inner.compactions_paused = false;
2152        if let Some(waker) = inner.flush_waker.take() {
2153            waker.wake();
2154        }
2155    }
2156
2157    /// Creates a lazy inspect node named `str` under `parent` which will yield statistics for the
2158    /// journal when queried.
2159    pub fn track_statistics(self: &Arc<Self>, parent: &fuchsia_inspect::Node, name: &str) {
2160        let this = Arc::downgrade(self);
2161        parent.record_lazy_child(name, move || {
2162            let this_clone = this.clone();
2163            async move {
2164                let inspector = fuchsia_inspect::Inspector::default();
2165                if let Some(this) = this_clone.upgrade() {
2166                    let (journal_min, journal_max, journal_reclaim_size) = {
2167                        // TODO(https://fxbug.dev/42069513): Push-back or rate-limit to prevent DoS.
2168                        let inner = this.inner.lock();
2169                        (
2170                            BLOCK_SIZE.align_down(
2171                                inner.super_block_header.journal_checkpoint.file_offset,
2172                            ),
2173                            inner.flushed_offset,
2174                            inner.reclaim_size,
2175                        )
2176                    };
2177                    let root = inspector.root();
2178                    root.record_uint("journal_min_offset", journal_min);
2179                    root.record_uint("journal_max_offset", journal_max);
2180                    root.record_uint("journal_size", journal_max - journal_min);
2181                    root.record_uint("journal_reclaim_size", journal_reclaim_size);
2182
2183                    // TODO(https://fxbug.dev/42068224): Post-compute rather than manually computing metrics.
2184                    if let Some(x) = round_div(
2185                        100 * (journal_max - journal_min),
2186                        this.objects.allocator().get_disk_bytes(),
2187                    ) {
2188                        root.record_uint("journal_size_to_disk_size_percent", x);
2189                    }
2190                }
2191                Ok(inspector)
2192            }
2193            .boxed()
2194        });
2195    }
2196
2197    /// Terminate all journal activity.
2198    pub fn terminate(&self) {
2199        self.inner.lock().terminate(/*reason*/ None);
2200        self.reclaim_event.notify(usize::MAX);
2201    }
2202}
2203
2204/// Wrapper to allow records to be written to the journal.
2205pub struct Writer<'a> {
2206    object_id: u64,
2207    writer: &'a mut JournalWriter,
2208    usage: &'a mut u64,
2209}
2210
2211impl Writer<'_> {
2212    pub fn write(&mut self, mutation: Mutation) {
2213        let count_usage = mutation.counts_towards_object_journal_usage();
2214        let offset_before = self.writer.journal_file_checkpoint().file_offset;
2215        self.writer
2216            .write_record(&JournalRecord::Mutation { object_id: self.object_id, mutation })
2217            .unwrap();
2218        if count_usage {
2219            *self.usage += self.writer.journal_file_checkpoint().file_offset - offset_before;
2220        }
2221    }
2222}
2223
2224#[cfg(target_os = "fuchsia")]
2225mod yielder {
2226    use super::{CompactionState, Journal};
2227    use crate::lsm_tree::Yielder;
2228    use fuchsia_async as fasync;
2229
2230    /// CompactionYielder uses fuchsia-async to yield if other tasks are being polled, which should
2231    /// be a proxy for how busy the system is.  We can afford to delay compactions for a small
2232    /// amount of time but not so long that we end up blocking new transactions.
2233    pub struct CompactionYielder<'a> {
2234        journal: &'a Journal,
2235        low_priority_task: Option<fasync::LowPriorityTask>,
2236    }
2237
2238    impl<'a> CompactionYielder<'a> {
2239        pub fn new(journal: &'a Journal) -> Self {
2240            Self { journal, low_priority_task: None }
2241        }
2242    }
2243
2244    impl Yielder for CompactionYielder<'_> {
2245        async fn yield_now(&mut self) {
2246            // We will wait for the executor to be idle for 4ms, but no longer than 16ms.  We need
2247            // to cap the maximum amount of time we wait in case we've reached a point where
2248            // compaction is now urgent or else we could block new transactions.
2249            const IDLE_PERIOD: zx::MonotonicDuration = zx::MonotonicDuration::from_millis(4);
2250            const MAX_YIELD_DURATION: zx::MonotonicDuration =
2251                zx::MonotonicDuration::from_millis(16);
2252
2253            {
2254                let inner = self.journal.inner.lock();
2255                if inner.compaction_state == CompactionState::Forced {
2256                    return;
2257                }
2258                let outstanding = self.journal.objects.last_end_offset()
2259                    - inner.super_block_header.journal_checkpoint.file_offset;
2260                let half_reclaim_size = inner.reclaim_size / 2;
2261                if outstanding
2262                    .checked_sub(half_reclaim_size)
2263                    .is_some_and(|x| x >= half_reclaim_size / 2)
2264                {
2265                    // If we have got to the point where we have used up 3/4 of reclaim size in the
2266                    // journal, do not delay any further.  If we continue to yield we will get to
2267                    // the point where we block new transactions.
2268                    self.low_priority_task = None;
2269                    return;
2270                }
2271            }
2272
2273            self.low_priority_task
2274                .get_or_insert_with(|| fasync::LowPriorityTask::new())
2275                .wait_until_idle_for(
2276                    IDLE_PERIOD,
2277                    fasync::MonotonicInstant::after(MAX_YIELD_DURATION),
2278                )
2279                .await;
2280        }
2281    }
2282}
2283
2284#[cfg(not(target_os = "fuchsia"))]
2285mod yielder {
2286    use super::Journal;
2287    use crate::lsm_tree::Yielder;
2288
2289    #[expect(dead_code)]
2290    pub struct CompactionYielder<'a>(&'a Journal);
2291
2292    impl<'a> CompactionYielder<'a> {
2293        pub fn new(journal: &'a Journal) -> Self {
2294            Self(journal)
2295        }
2296    }
2297
2298    impl Yielder for CompactionYielder<'_> {
2299        async fn yield_now(&mut self) {}
2300    }
2301}
2302
2303pub use yielder::*;
2304
2305#[cfg(test)]
2306mod tests {
2307    use super::{JournalRecord, JournalRecordV59, SuperBlockInstance};
2308    use crate::checksum::Checksums;
2309    use crate::errors::FxfsError;
2310    use crate::filesystem::{
2311        FlushReason, ForceMajor, FxFilesystem, FxFilesystemBuilder, SyncOptions,
2312    };
2313    use crate::fsck::fsck;
2314    use crate::object_handle::{
2315        INVALID_OBJECT_ID, ObjectHandle, ReadObjectHandle, WriteObjectHandle,
2316    };
2317    use crate::object_store::directory::{Directory, replace_child};
2318    use crate::object_store::object_record::{ObjectKey, ObjectValue};
2319    use crate::object_store::transaction::{Mutation, Options, ReservationOptions};
2320    use crate::object_store::volume::root_volume;
2321    use crate::object_store::{
2322        HandleOptions, LockKey, NewChildStoreOptions, ObjectStore, StoreOptions, lock_keys,
2323    };
2324    use crate::serialized_types::Version;
2325    #[cfg(target_os = "fuchsia")]
2326    use fuchsia_async::TestExecutor;
2327    use fuchsia_async::{self as fasync, MonotonicDuration};
2328    use fxfs_insecure_crypto::new_insecure_crypt;
2329    use rustc_hash::FxHashMap as HashMap;
2330    use std::sync::Arc;
2331    use storage_device::DeviceHolder;
2332    use storage_device::fake_device::FakeDevice;
2333
2334    const TEST_DEVICE_BLOCK_SIZE: u32 = 512;
2335
2336    #[fuchsia::test]
2337    async fn test_replay() {
2338        const TEST_DATA: &[u8] = b"hello";
2339
2340        let device = DeviceHolder::new(FakeDevice::new(8192, TEST_DEVICE_BLOCK_SIZE));
2341
2342        let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
2343
2344        let object_id = {
2345            let root_store = fs.root_store();
2346            let root_directory =
2347                Directory::open(&root_store, root_store.root_directory_object_id())
2348                    .await
2349                    .expect("open failed");
2350            let mut transaction = fs
2351                .root_store()
2352                .new_transaction(
2353                    lock_keys![LockKey::object(
2354                        root_store.store_object_id(),
2355                        root_store.root_directory_object_id(),
2356                    )],
2357                    Options::default(),
2358                )
2359                .await
2360                .expect("new_transaction failed");
2361            let handle = root_directory
2362                .create_child_file(&mut transaction, "test")
2363                .await
2364                .expect("create_child_file failed");
2365
2366            transaction.commit().await.expect("commit failed");
2367            let mut buf = handle.allocate_buffer(TEST_DATA.len()).await;
2368            buf.copy_from_slice(TEST_DATA);
2369            handle.write_or_append(Some(0), buf.as_ref()).await.expect("write failed");
2370            // As this is the first sync, this will actually trigger a new super-block, but normally
2371            // this would not be the case.
2372            fs.sync(SyncOptions::default()).await.expect("sync failed");
2373            handle.object_id()
2374        };
2375
2376        {
2377            fs.close().await.expect("Close failed");
2378            let device = fs.take_device().await;
2379            device.reopen(false);
2380            let fs = FxFilesystem::open(device).await.expect("open failed");
2381            let handle = ObjectStore::open_object(
2382                &fs.root_store(),
2383                object_id,
2384                HandleOptions::default(),
2385                None,
2386            )
2387            .await
2388            .expect("open_object failed");
2389            let mut buf = handle.allocate_buffer(handle.block_size().get() as usize).await;
2390            assert_eq!(
2391                handle.read_aligned(0, buf.as_mut()).await.expect("read failed"),
2392                TEST_DATA.len()
2393            );
2394            assert_eq!(buf.subslice(..TEST_DATA.len()).to_vec(), TEST_DATA);
2395            fsck(fs.clone()).await.expect("fsck failed");
2396            fs.close().await.expect("Close failed");
2397        }
2398    }
2399
2400    #[fuchsia::test]
2401    async fn test_reset() {
2402        const TEST_DATA: &[u8] = b"hello";
2403
2404        let device = DeviceHolder::new(FakeDevice::new(32768, TEST_DEVICE_BLOCK_SIZE));
2405
2406        let mut object_ids = Vec::new();
2407
2408        let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
2409        {
2410            let root_store = fs.root_store();
2411            let root_directory =
2412                Directory::open(&root_store, root_store.root_directory_object_id())
2413                    .await
2414                    .expect("open failed");
2415            let mut transaction = fs
2416                .root_store()
2417                .new_transaction(
2418                    lock_keys![LockKey::object(
2419                        root_store.store_object_id(),
2420                        root_store.root_directory_object_id(),
2421                    )],
2422                    Options::default(),
2423                )
2424                .await
2425                .expect("new_transaction failed");
2426            let handle = root_directory
2427                .create_child_file(&mut transaction, "test")
2428                .await
2429                .expect("create_child_file failed");
2430            transaction.commit().await.expect("commit failed");
2431            let mut buf = handle.allocate_buffer(TEST_DATA.len()).await;
2432            buf.copy_from_slice(TEST_DATA);
2433            handle.write_or_append(Some(0), buf.as_ref()).await.expect("write failed");
2434            fs.sync(SyncOptions::default()).await.expect("sync failed");
2435            object_ids.push(handle.object_id());
2436
2437            // Create a lot of objects but don't sync at the end. This should leave the filesystem
2438            // with a half finished transaction that cannot be replayed.
2439            for i in 0..1000 {
2440                let mut transaction = fs
2441                    .root_store()
2442                    .new_transaction(
2443                        lock_keys![LockKey::object(
2444                            root_store.store_object_id(),
2445                            root_store.root_directory_object_id(),
2446                        )],
2447                        Options::default(),
2448                    )
2449                    .await
2450                    .expect("new_transaction failed");
2451                let handle = root_directory
2452                    .create_child_file(&mut transaction, &format!("{}", i))
2453                    .await
2454                    .expect("create_child_file failed");
2455                transaction.commit().await.expect("commit failed");
2456                let mut buf = handle.allocate_buffer(TEST_DATA.len()).await;
2457                buf.copy_from_slice(TEST_DATA);
2458                handle.write_or_append(Some(0), buf.as_ref()).await.expect("write failed");
2459                object_ids.push(handle.object_id());
2460            }
2461        }
2462        fs.close().await.expect("fs close failed");
2463        let device = fs.take_device().await;
2464        device.reopen(false);
2465        let fs = FxFilesystem::open(device).await.expect("open failed");
2466        fsck(fs.clone()).await.expect("fsck failed");
2467        {
2468            let root_store = fs.root_store();
2469            // Check the first two objects which should exist.
2470            for &object_id in &object_ids[0..1] {
2471                let handle = ObjectStore::open_object(
2472                    &root_store,
2473                    object_id,
2474                    HandleOptions::default(),
2475                    None,
2476                )
2477                .await
2478                .expect("open_object failed");
2479                let mut buf = handle.allocate_buffer(handle.block_size().get() as usize).await;
2480                assert_eq!(
2481                    handle.read_aligned(0, buf.as_mut()).await.expect("read failed"),
2482                    TEST_DATA.len()
2483                );
2484                let data = buf.subslice(..TEST_DATA.len()).to_vec();
2485                assert_eq!(data, TEST_DATA);
2486            }
2487
2488            // Write one more object and sync.
2489            let root_directory =
2490                Directory::open(&root_store, root_store.root_directory_object_id())
2491                    .await
2492                    .expect("open failed");
2493            let mut transaction = fs
2494                .root_store()
2495                .new_transaction(
2496                    lock_keys![LockKey::object(
2497                        root_store.store_object_id(),
2498                        root_store.root_directory_object_id(),
2499                    )],
2500                    Options::default(),
2501                )
2502                .await
2503                .expect("new_transaction failed");
2504            let handle = root_directory
2505                .create_child_file(&mut transaction, "test2")
2506                .await
2507                .expect("create_child_file failed");
2508            transaction.commit().await.expect("commit failed");
2509            let mut buf = handle.allocate_buffer(TEST_DATA.len()).await;
2510            buf.copy_from_slice(TEST_DATA);
2511            handle.write_or_append(Some(0), buf.as_ref()).await.expect("write failed");
2512            fs.sync(SyncOptions::default()).await.expect("sync failed");
2513            object_ids.push(handle.object_id());
2514        }
2515
2516        fs.close().await.expect("close failed");
2517        let device = fs.take_device().await;
2518        device.reopen(false);
2519        let fs = FxFilesystem::open(device).await.expect("open failed");
2520        {
2521            fsck(fs.clone()).await.expect("fsck failed");
2522
2523            // Check the first two and the last objects.
2524            for &object_id in object_ids[0..1].iter().chain(object_ids.last().cloned().iter()) {
2525                let handle = ObjectStore::open_object(
2526                    &fs.root_store(),
2527                    object_id,
2528                    HandleOptions::default(),
2529                    None,
2530                )
2531                .await
2532                .unwrap_or_else(|e| {
2533                    panic!("open_object failed (object_id: {}): {:?}", object_id, e)
2534                });
2535                let mut buf = handle.allocate_buffer(handle.block_size().get() as usize).await;
2536                assert_eq!(
2537                    handle.read_aligned(0, buf.as_mut()).await.expect("read failed"),
2538                    TEST_DATA.len()
2539                );
2540                let data = buf.subslice(..TEST_DATA.len()).to_vec();
2541                assert_eq!(data, TEST_DATA);
2542            }
2543        }
2544        fs.close().await.expect("close failed");
2545    }
2546
2547    #[fuchsia::test]
2548    async fn test_discard() {
2549        let device = {
2550            let device = DeviceHolder::new(FakeDevice::new(8192, 4096));
2551            let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
2552            let root_volume = root_volume(fs.clone()).await.expect("root_volume failed");
2553
2554            let store = root_volume
2555                .new_volume("test", NewChildStoreOptions::default())
2556                .await
2557                .expect("new_volume failed");
2558            let root_directory = Directory::open(&store, store.root_directory_object_id())
2559                .await
2560                .expect("open failed");
2561
2562            // Create enough data so that another journal extent is used.
2563            let mut i = 0;
2564            loop {
2565                let mut transaction = fs
2566                    .root_store()
2567                    .new_transaction(
2568                        lock_keys![LockKey::object(
2569                            store.store_object_id(),
2570                            store.root_directory_object_id()
2571                        )],
2572                        Options::default(),
2573                    )
2574                    .await
2575                    .expect("new_transaction failed");
2576                root_directory
2577                    .create_child_file(&mut transaction, &format!("a {i}"))
2578                    .await
2579                    .expect("create_child_file failed");
2580                if transaction.commit().await.expect("commit failed") > super::CHUNK_SIZE {
2581                    break;
2582                }
2583                i += 1;
2584            }
2585
2586            // Compact and then disable compactions.
2587            fs.journal().force_compact().await.expect("compact failed");
2588            fs.journal().stop_compactions().await;
2589
2590            // Keep going until we need another journal extent.
2591            let mut i = 0;
2592            loop {
2593                let mut transaction = fs
2594                    .root_store()
2595                    .new_transaction(
2596                        lock_keys![LockKey::object(
2597                            store.store_object_id(),
2598                            store.root_directory_object_id()
2599                        )],
2600                        Options::default(),
2601                    )
2602                    .await
2603                    .expect("new_transaction failed");
2604                root_directory
2605                    .create_child_file(&mut transaction, &format!("b {i}"))
2606                    .await
2607                    .expect("create_child_file failed");
2608                if transaction.commit().await.expect("commit failed") > 2 * super::CHUNK_SIZE {
2609                    break;
2610                }
2611                i += 1;
2612            }
2613
2614            // Allow the journal to flush, but we don't want to sync.
2615            fasync::Timer::new(MonotonicDuration::from_millis(10)).await;
2616            // Because we're not gracefully closing the filesystem, a Discard record will be
2617            // emitted.
2618            fs.device().snapshot().expect("snapshot failed")
2619        };
2620
2621        let fs = FxFilesystem::open(device).await.expect("open failed");
2622
2623        {
2624            let root_volume = root_volume(fs.clone()).await.expect("root_volume failed");
2625
2626            let store =
2627                root_volume.volume("test", StoreOptions::default()).await.expect("volume failed");
2628
2629            let root_directory = Directory::open(&store, store.root_directory_object_id())
2630                .await
2631                .expect("open failed");
2632
2633            // Write one more transaction.
2634            let mut transaction = fs
2635                .root_store()
2636                .new_transaction(
2637                    lock_keys![LockKey::object(
2638                        store.store_object_id(),
2639                        store.root_directory_object_id()
2640                    )],
2641                    Options::default(),
2642                )
2643                .await
2644                .expect("new_transaction failed");
2645            root_directory
2646                .create_child_file(&mut transaction, &format!("d"))
2647                .await
2648                .expect("create_child_file failed");
2649            transaction.commit().await.expect("commit failed");
2650        }
2651
2652        fs.close().await.expect("close failed");
2653        let device = fs.take_device().await;
2654        device.reopen(false);
2655
2656        let fs = FxFilesystem::open(device).await.expect("open failed");
2657        fsck(fs.clone()).await.expect("fsck failed");
2658        fs.close().await.expect("close failed");
2659    }
2660
2661    #[fuchsia::test]
2662    async fn test_use_existing_generation() {
2663        let device = DeviceHolder::new(FakeDevice::new(8192, 4096));
2664
2665        // First format should be generation 1.
2666        let fs = FxFilesystemBuilder::new()
2667            .format(true)
2668            .image_builder_mode(Some(SuperBlockInstance::A))
2669            .open(device)
2670            .await
2671            .expect("open failed");
2672        fs.enable_allocations();
2673        let generation0 = fs.super_block_header().generation;
2674        assert_eq!(generation0, 1);
2675        fs.close().await.expect("close failed");
2676        let device = fs.take_device().await;
2677        device.reopen(false);
2678
2679        // Format the device normally (again, generation 1).
2680        let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
2681        let generation1 = fs.super_block_header().generation;
2682        {
2683            let root_volume = crate::object_store::volume::root_volume(fs.clone())
2684                .await
2685                .expect("root_volume failed");
2686            root_volume
2687                .new_volume("test", crate::object_store::NewChildStoreOptions::default())
2688                .await
2689                .expect("new_volume failed");
2690        }
2691        fs.close().await.expect("close failed");
2692        let device = fs.take_device().await;
2693        device.reopen(false);
2694
2695        // Format again with image_builder_mode.
2696        let fs = FxFilesystemBuilder::new()
2697            .format(true)
2698            .image_builder_mode(Some(SuperBlockInstance::A))
2699            .open(device)
2700            .await
2701            .expect("open failed");
2702        fs.enable_allocations();
2703        let generation2 = fs.super_block_header().generation;
2704        assert!(
2705            generation2 > generation1,
2706            "generation2 ({}) should be greater than generation1 ({})",
2707            generation2,
2708            generation1
2709        );
2710        fs.close().await.expect("close failed");
2711    }
2712
2713    #[fuchsia::test]
2714    async fn test_image_builder_mode_generation_bump_512_byte_block() {
2715        let device = DeviceHolder::new(FakeDevice::new(16384, 512));
2716
2717        // Format initial filesystem (generation 1)
2718        let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
2719        let generation1 = fs.super_block_header().generation;
2720        fs.close().await.expect("close failed");
2721        let device = fs.take_device().await;
2722        device.reopen(false);
2723
2724        // Format again with image_builder_mode (should bump generation)
2725        let fs = FxFilesystemBuilder::new()
2726            .format(true)
2727            .image_builder_mode(Some(SuperBlockInstance::A))
2728            .open(device)
2729            .await
2730            .expect("open failed");
2731
2732        fs.enable_allocations();
2733        let generation2 = fs.super_block_header().generation;
2734        assert!(
2735            generation2 > generation1,
2736            "Expected generation bump, got {} vs {}",
2737            generation2,
2738            generation1
2739        );
2740        fs.close().await.expect("close failed");
2741    }
2742
2743    #[fuchsia::test]
2744    #[cfg(target_os = "fuchsia")]
2745    fn test_low_priority_compaction() {
2746        let mut executor = TestExecutor::new_with_fake_time();
2747        let mut fut = std::pin::pin!(async {
2748            use std::sync::Arc;
2749            use std::sync::atomic::{AtomicBool, Ordering};
2750
2751            let device = DeviceHolder::new(FakeDevice::new(16384, TEST_DEVICE_BLOCK_SIZE));
2752            let fs = FxFilesystemBuilder::new()
2753                .journal_options(super::JournalOptions {
2754                    reclaim_size: 65536,
2755                    ..Default::default()
2756                })
2757                .format(true)
2758                .open(device)
2759                .await
2760                .expect("open failed");
2761
2762            let _low = fasync::LowPriorityTask::new();
2763
2764            // Add some data to the tree.
2765            {
2766                let root_store = fs.root_store();
2767                let root_directory =
2768                    Directory::open(&root_store, root_store.root_directory_object_id())
2769                        .await
2770                        .expect("open failed");
2771                for i in 0..100 {
2772                    let mut transaction = fs
2773                        .root_store()
2774                        .new_transaction(
2775                            lock_keys![LockKey::object(
2776                                root_store.store_object_id(),
2777                                root_store.root_directory_object_id(),
2778                            )],
2779                            Options::default(),
2780                        )
2781                        .await
2782                        .expect("new_transaction failed");
2783                    root_directory
2784                        .create_child_file(&mut transaction, &format!("test{}", i))
2785                        .await
2786                        .expect("create_child_file failed");
2787                    transaction.commit().await.expect("commit failed");
2788                }
2789            }
2790
2791            // Spawn a task that polls every 1ms.
2792            let stop = Arc::new(AtomicBool::new(false));
2793            let stop_clone = stop.clone();
2794            let _normal_task = fasync::Task::spawn(async move {
2795                while !stop_clone.load(Ordering::Relaxed) {
2796                    fasync::Timer::new(fasync::MonotonicInstant::after(
2797                        MonotonicDuration::from_millis(1),
2798                    ))
2799                    .await;
2800                }
2801            });
2802
2803            // Trigger journal compaction.
2804            // We can do this by writing more data until outstanding > reclaim_size / 2.
2805            {
2806                let root_store = fs.root_store();
2807                let root_directory =
2808                    Directory::open(&root_store, root_store.root_directory_object_id())
2809                        .await
2810                        .expect("open failed");
2811                let mut i = 0;
2812                loop {
2813                    let mut transaction = fs
2814                        .root_store()
2815                        .new_transaction(
2816                            lock_keys![LockKey::object(
2817                                root_store.store_object_id(),
2818                                root_store.root_directory_object_id(),
2819                            )],
2820                            Options::default(),
2821                        )
2822                        .await
2823                        .expect("new_transaction failed");
2824                    root_directory
2825                        .create_child_file(&mut transaction, &format!("trigger{i}"))
2826                        .await
2827                        .expect("create_child_file failed");
2828                    transaction.commit().await.expect("commit failed");
2829
2830                    if fs.journal().inner.lock().compaction_state.is_running() {
2831                        break;
2832                    }
2833                    TestExecutor::advance_to(fasync::MonotonicInstant::after(
2834                        MonotonicDuration::from_millis(1),
2835                    ))
2836                    .await;
2837                    i += 1;
2838                }
2839            }
2840
2841            // Compaction should now be running. Because of our 1ms poller, it should be yielding.
2842            // It will yield for 16ms at each point.
2843            for _ in 0..10 {
2844                TestExecutor::advance_to(fasync::MonotonicInstant::after(
2845                    MonotonicDuration::from_millis(1),
2846                ))
2847                .await;
2848                assert!(fs.journal().inner.lock().compaction_state.is_running());
2849            }
2850
2851            // Stop the normal task.
2852            stop.store(true, Ordering::Relaxed);
2853            TestExecutor::advance_to(fasync::MonotonicInstant::after(
2854                MonotonicDuration::from_millis(1),
2855            ))
2856            .await;
2857
2858            // Compaction should still be running because it hasn't been 4 ms since the normal task
2859            // finished.
2860            assert!(fs.journal().inner.lock().compaction_state.is_running());
2861
2862            // For the next 3ms, compaction should still be running.
2863            for _ in 0..3 {
2864                TestExecutor::advance_to(fasync::MonotonicInstant::after(
2865                    MonotonicDuration::from_millis(1),
2866                ))
2867                .await;
2868                assert!(fs.journal().inner.lock().compaction_state.is_running());
2869            }
2870
2871            // 1 more ms and compaction should be unblocked.
2872            TestExecutor::advance_to(fasync::MonotonicInstant::after(
2873                MonotonicDuration::from_millis(1),
2874            ))
2875            .await;
2876
2877            // When the executor next stalls, compaction should be done.
2878            let _ = TestExecutor::poll_until_stalled(std::future::pending::<()>()).await;
2879            assert!(!fs.journal().inner.lock().compaction_state.is_running());
2880
2881            fs.close().await.expect("Close failed");
2882        });
2883        assert!(executor.run_until_stalled(&mut fut).is_ready());
2884    }
2885
2886    #[fuchsia::test]
2887    #[cfg(target_os = "fuchsia")]
2888    fn test_low_priority_compaction_deadline() {
2889        let mut executor = TestExecutor::new_with_fake_time();
2890        let mut fut = std::pin::pin!(async {
2891            use std::sync::Arc;
2892            use std::sync::atomic::{AtomicBool, Ordering};
2893
2894            let device = DeviceHolder::new(FakeDevice::new(216384, TEST_DEVICE_BLOCK_SIZE));
2895            let fs = FxFilesystemBuilder::new()
2896                .journal_options(super::JournalOptions {
2897                    reclaim_size: 65536,
2898                    ..Default::default()
2899                })
2900                .format(true)
2901                .open(device)
2902                .await
2903                .expect("open failed");
2904
2905            let _low = fasync::LowPriorityTask::new();
2906
2907            // Add some data to the tree.
2908            {
2909                let root_store = fs.root_store();
2910                let root_directory =
2911                    Directory::open(&root_store, root_store.root_directory_object_id())
2912                        .await
2913                        .expect("open failed");
2914                for i in 0..10 {
2915                    let mut transaction = fs
2916                        .root_store()
2917                        .new_transaction(
2918                            lock_keys![LockKey::object(
2919                                root_store.store_object_id(),
2920                                root_store.root_directory_object_id(),
2921                            )],
2922                            Options::default(),
2923                        )
2924                        .await
2925                        .expect("new_transaction failed");
2926                    root_directory
2927                        .create_child_file(&mut transaction, &format!("test{}", i))
2928                        .await
2929                        .expect("create_child_file failed");
2930                    transaction.commit().await.expect("commit failed");
2931                }
2932            }
2933
2934            // Spawn a task that polls every 3ms.
2935            let stop = Arc::new(AtomicBool::new(false));
2936            let stop_clone = stop.clone();
2937            let _normal_task = fasync::Task::spawn(async move {
2938                while !stop_clone.load(Ordering::Relaxed) {
2939                    fasync::Timer::new(fasync::MonotonicInstant::after(
2940                        MonotonicDuration::from_millis(3),
2941                    ))
2942                    .await;
2943                }
2944            });
2945
2946            // Trigger journal compaction.
2947            {
2948                let root_store = fs.root_store();
2949                let root_directory =
2950                    Directory::open(&root_store, root_store.root_directory_object_id())
2951                        .await
2952                        .expect("open failed");
2953                let mut i = 0;
2954                loop {
2955                    let mut transaction = fs
2956                        .root_store()
2957                        .new_transaction(
2958                            lock_keys![LockKey::object(
2959                                root_store.store_object_id(),
2960                                root_store.root_directory_object_id(),
2961                            )],
2962                            Options::default(),
2963                        )
2964                        .await
2965                        .expect("new_transaction failed");
2966                    root_directory
2967                        .create_child_file(&mut transaction, &format!("trigger{i}"))
2968                        .await
2969                        .expect("create_child_file failed");
2970                    transaction.commit().await.expect("commit failed");
2971
2972                    if fs.journal().inner.lock().compaction_state.is_running() {
2973                        break;
2974                    }
2975                    TestExecutor::advance_to(fasync::MonotonicInstant::after(
2976                        MonotonicDuration::from_millis(1),
2977                    ))
2978                    .await;
2979                    i += 1;
2980                }
2981            }
2982
2983            // Advance time in 20ms increments. Each increment should allow compaction to make progress
2984            // on one item (since MAX_YIELD_DURATION is 16ms).
2985            let mut count = 0;
2986            for _ in 0..1000 {
2987                TestExecutor::advance_to(fasync::MonotonicInstant::after(
2988                    MonotonicDuration::from_millis(20),
2989                ))
2990                .await;
2991                if !fs.journal().inner.lock().compaction_state.is_running() {
2992                    break;
2993                }
2994                count += 1;
2995            }
2996            assert!(!fs.journal().inner.lock().compaction_state.is_running());
2997
2998            // Make sure it took a few iterations to complete.  It's difficult to know what the exact
2999            // number should be.
3000            assert!(count > 200);
3001
3002            stop.store(true, Ordering::Relaxed);
3003            fs.close().await.expect("Close failed");
3004        });
3005        assert!(executor.run_until_stalled(&mut fut).is_ready());
3006    }
3007
3008    #[fuchsia::test]
3009    #[cfg(target_os = "fuchsia")]
3010    fn test_low_priority_compaction_no_yielding_when_full() {
3011        let mut executor = TestExecutor::new_with_fake_time();
3012        let mut fut = std::pin::pin!(async {
3013            use std::sync::Arc;
3014            use std::sync::atomic::{AtomicBool, Ordering};
3015
3016            let reclaim_size = 65536;
3017            let device = DeviceHolder::new(FakeDevice::new(216384, TEST_DEVICE_BLOCK_SIZE));
3018            let fs = FxFilesystemBuilder::new()
3019                .journal_options(super::JournalOptions { reclaim_size, ..Default::default() })
3020                .format(true)
3021                .open(device)
3022                .await
3023                .expect("open failed");
3024
3025            let _low = fasync::LowPriorityTask::new();
3026
3027            // Add some data to the tree.
3028            {
3029                let root_store = fs.root_store();
3030                let root_directory =
3031                    Directory::open(&root_store, root_store.root_directory_object_id())
3032                        .await
3033                        .expect("open failed");
3034                for i in 0..10 {
3035                    let mut transaction = fs
3036                        .root_store()
3037                        .new_transaction(
3038                            lock_keys![LockKey::object(
3039                                root_store.store_object_id(),
3040                                root_store.root_directory_object_id(),
3041                            )],
3042                            Options::default(),
3043                        )
3044                        .await
3045                        .expect("new_transaction failed");
3046                    root_directory
3047                        .create_child_file(&mut transaction, &format!("test{}", i))
3048                        .await
3049                        .expect("create_child_file failed");
3050                    transaction.commit().await.expect("commit failed");
3051                }
3052            }
3053
3054            // Spawn a task that polls every 3ms.
3055            let stop = Arc::new(AtomicBool::new(false));
3056            let stop_clone = stop.clone();
3057            let _normal_task = fasync::Task::spawn(async move {
3058                while !stop_clone.load(Ordering::Relaxed) {
3059                    fasync::Timer::new(fasync::MonotonicInstant::after(
3060                        MonotonicDuration::from_millis(3),
3061                    ))
3062                    .await;
3063                }
3064            });
3065
3066            // Trigger journal compaction, but this time we fill it up to 3/4 full.
3067            {
3068                let root_store = fs.root_store();
3069                let root_directory =
3070                    Directory::open(&root_store, root_store.root_directory_object_id())
3071                        .await
3072                        .expect("open failed");
3073                let mut i = 0;
3074                loop {
3075                    let mut transaction = fs
3076                        .root_store()
3077                        .new_transaction(
3078                            lock_keys![LockKey::object(
3079                                root_store.store_object_id(),
3080                                root_store.root_directory_object_id(),
3081                            )],
3082                            Options::default(),
3083                        )
3084                        .await
3085                        .expect("new_transaction failed");
3086                    root_directory
3087                        .create_child_file(&mut transaction, &format!("trigger{i}"))
3088                        .await
3089                        .expect("create_child_file failed");
3090                    transaction.commit().await.expect("commit failed");
3091
3092                    let outstanding = {
3093                        let inner = fs.journal().inner.lock();
3094                        fs.journal().objects.last_end_offset()
3095                            - inner.super_block_header.journal_checkpoint.file_offset
3096                    };
3097                    if outstanding >= reclaim_size * 7 / 8 {
3098                        break;
3099                    }
3100                    // We don't advance time here to try and reach 3/4 before compaction can yield.
3101                    i += 1;
3102                }
3103            }
3104
3105            // Advancing by 4ms should be enough to wake compaction up.
3106            TestExecutor::advance_to(fasync::MonotonicInstant::after(
3107                MonotonicDuration::from_millis(4),
3108            ))
3109            .await;
3110
3111            // When the executor next stalls, compaction should be done.
3112            let _ = TestExecutor::poll_until_stalled(std::future::pending::<()>()).await;
3113            assert!(!fs.journal().inner.lock().compaction_state.is_running());
3114
3115            stop.store(true, Ordering::Relaxed);
3116            fs.close().await.expect("Close failed");
3117        });
3118        assert!(executor.run_until_stalled(&mut fut).is_ready());
3119    }
3120
3121    #[fuchsia::test]
3122    async fn test_data_checksums_validation() {
3123        // 1. A < V60 DataChecksums record (which migrates with `owner_object_id =
3124        //    INVALID_OBJECT_ID`) is accepted during replay.
3125        {
3126            let device = DeviceHolder::new(FakeDevice::new(8192, TEST_DEVICE_BLOCK_SIZE));
3127            let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
3128            fs.close().await.expect("close failed");
3129            let device = fs.take_device().await;
3130            device.reopen(false);
3131
3132            let fs = FxFilesystem::open(device).await.expect("open failed");
3133            let _ = fs
3134                .root_store()
3135                .new_transaction(lock_keys![], Options::default())
3136                .await
3137                .expect("new_transaction failed");
3138            {
3139                let mut inner = fs.journal().inner.lock();
3140                assert!(std::mem::take(&mut inner.output_reset_version));
3141                Version { major: 59, minor: 0 }.serialize_into(&mut inner.writer).unwrap();
3142                inner
3143                    .writer
3144                    .write_record(&JournalRecordV59::DataChecksums(
3145                        512..1024,
3146                        Checksums::fletcher(vec![0]),
3147                        false,
3148                    ))
3149                    .unwrap();
3150                inner.writer.write_record(&JournalRecordV59::Commit).unwrap();
3151            }
3152            fs.close().await.expect("close failed");
3153            let device = fs.take_device().await;
3154            device.reopen(false);
3155
3156            let fs = FxFilesystem::open(device).await.expect("open V59 DataChecksums failed");
3157            fs.close().await.expect("close failed");
3158        }
3159
3160        // 2. A >= V60 DataChecksums record with `owner_object_id == INVALID_OBJECT_ID` is rejected.
3161        {
3162            let device = DeviceHolder::new(FakeDevice::new(8192, TEST_DEVICE_BLOCK_SIZE));
3163            let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
3164            {
3165                let mut inner = fs.journal().inner.lock();
3166                inner
3167                    .writer
3168                    .write_record(&JournalRecord::DataChecksums(
3169                        INVALID_OBJECT_ID,
3170                        512..1024,
3171                        Checksums::fletcher(vec![0]),
3172                        false,
3173                    ))
3174                    .unwrap();
3175                inner.writer.write_record(&JournalRecord::Commit).unwrap();
3176            }
3177            fs.close().await.expect("close failed");
3178            let device = fs.take_device().await;
3179            device.reopen(false);
3180
3181            let err = FxFilesystem::open(device)
3182                .await
3183                .err()
3184                .expect("expected INVALID_OBJECT_ID in V60+ DataChecksums to fail");
3185            assert!(FxfsError::Inconsistent.matches(&err), "unexpected error: {err:?}");
3186        }
3187
3188        // 3. A DataChecksums record for a deleted store is rejected.
3189        {
3190            let device = DeviceHolder::new(FakeDevice::new(8192, TEST_DEVICE_BLOCK_SIZE));
3191            let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
3192            {
3193                let mut inner = fs.journal().inner.lock();
3194                inner
3195                    .writer
3196                    .write_record(&JournalRecord::Mutation {
3197                        object_id: 99,
3198                        mutation: Mutation::DeleteVolume,
3199                    })
3200                    .unwrap();
3201                inner.writer.write_record(&JournalRecord::Commit).unwrap();
3202                inner
3203                    .writer
3204                    .write_record(&JournalRecord::DataChecksums(
3205                        99,
3206                        512..1024,
3207                        Checksums::fletcher(vec![0]),
3208                        false,
3209                    ))
3210                    .unwrap();
3211                inner.writer.write_record(&JournalRecord::Commit).unwrap();
3212            }
3213            fs.close().await.expect("close failed");
3214            let device = fs.take_device().await;
3215            device.reopen(false);
3216
3217            let err = FxFilesystem::open(device)
3218                .await
3219                .err()
3220                .expect("expected DataChecksums for deleted store to fail");
3221            assert!(FxfsError::Inconsistent.matches(&err), "unexpected error: {err:?}");
3222        }
3223    }
3224
3225    #[fuchsia::test]
3226    async fn test_delete_volume_reused_extents_before_did_flush_device() {
3227        let device = DeviceHolder::new(FakeDevice::new(4096, TEST_DEVICE_BLOCK_SIZE));
3228        let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
3229        let root_vol = root_volume(fs.clone()).await.expect("root_volume failed");
3230
3231        // Create "vol1" and "vol2" (with an empty file "bar" in "vol2") up front so no metadata
3232        // allocations occur between deleting "vol1" and writing to "bar".
3233        let vol1 =
3234            root_vol.new_volume("vol1", NewChildStoreOptions::default()).await.expect("new_volume");
3235        let vol2 =
3236            root_vol.new_volume("vol2", NewChildStoreOptions::default()).await.expect("new_volume");
3237        let vol2_root =
3238            Directory::open(&vol2, vol2.root_directory_object_id()).await.expect("open failed");
3239        let mut transaction = fs
3240            .root_store()
3241            .new_transaction(
3242                lock_keys![LockKey::object(
3243                    vol2.store_object_id(),
3244                    vol2.root_directory_object_id()
3245                )],
3246                Options::default(),
3247            )
3248            .await
3249            .expect("new_transaction failed");
3250        let bar =
3251            vol2_root.create_child_file(&mut transaction, "bar").await.expect("create_child_file");
3252        transaction.commit().await.expect("commit failed");
3253
3254        // Write 64 KiB to a file in "vol1", emitting `DataChecksums` owned by "vol1".
3255        {
3256            let vol1_root =
3257                Directory::open(&vol1, vol1.root_directory_object_id()).await.expect("open failed");
3258            let mut transaction = fs
3259                .root_store()
3260                .new_transaction(
3261                    lock_keys![LockKey::object(
3262                        vol1.store_object_id(),
3263                        vol1.root_directory_object_id()
3264                    )],
3265                    Options::default(),
3266                )
3267                .await
3268                .expect("new_transaction failed");
3269            let file = vol1_root
3270                .create_child_file(&mut transaction, "file")
3271                .await
3272                .expect("create_child_file");
3273            transaction.commit().await.expect("commit failed");
3274            let mut buf = file.allocate_buffer(65536).await;
3275            buf.fill(0xaa);
3276            file.write_or_append(Some(0), buf.as_ref()).await.expect("write failed");
3277        }
3278        drop(vol1);
3279
3280        // Delete "vol1", which emits `MarkForDeletion` for "vol1".
3281        let (_, transaction) = root_vol
3282            .acquire_transaction_for_remove_volume("vol1", [], false, Options::default())
3283            .await
3284            .expect("acquire_transaction_for_remove_volume failed");
3285        root_vol.delete_volume("vol1", transaction, || {}).await.expect("delete_volume failed");
3286
3287        // Write 64 KiB to "bar" in "vol2". Allocating in "vol2" sees
3288        // `volumes_deleted_pending_sync`, syncs the journal and flushes the device (queuing
3289        // `DidFlushDevice` for the next journal write), and frees "vol1"'s 64 KiB extent. Writing
3290        // to "bar" reuses and overwrites those exact disk blocks with `0xbb`, while the
3291        // `DidFlushDevice` record remains buffered in memory in `JournalWriter`.
3292        let mut buf = bar.allocate_buffer(65536).await;
3293        buf.fill(0xbb);
3294        bar.write_or_append(Some(0), buf.as_ref()).await.expect("write failed");
3295
3296        // Simulate power loss before `DidFlushDevice` is flushed to the journal on disk.
3297        let snapshot = fs.device().snapshot().expect("snapshot failed");
3298        let fs2 = FxFilesystem::open(snapshot).await.expect("open snapshot failed");
3299        fsck(fs2.clone()).await.expect("fsck failed");
3300        let root_vol2 = root_volume(fs2.clone()).await.expect("root_volume failed");
3301        root_vol2
3302            .volume("vol1", StoreOptions::default())
3303            .await
3304            .err()
3305            .expect("vol1 should remain deleted");
3306        fs2.close().await.expect("close failed");
3307        fs.close().await.expect("close failed");
3308    }
3309
3310    #[fuchsia::test]
3311    async fn test_metadata_reservation_replay_and_reserved_space_change() {
3312        let device = DeviceHolder::new(FakeDevice::new(8192, 4096));
3313        let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
3314        let root_volume = root_volume(fs.clone()).await.expect("root_volume failed");
3315        let store = root_volume
3316            .new_volume("test", NewChildStoreOptions::default())
3317            .await
3318            .expect("new_volume failed");
3319        let store_id = store.store_object_id();
3320        let root_directory =
3321            Directory::open(&store, store.root_directory_object_id()).await.expect("open failed");
3322
3323        let other_store = root_volume
3324            .new_volume("other", NewChildStoreOptions::default())
3325            .await
3326            .expect("new_volume failed");
3327        let other_store_id = other_store.store_object_id();
3328        let other_root_directory =
3329            Directory::open(&other_store, other_store.root_directory_object_id())
3330                .await
3331                .expect("open failed");
3332
3333        let object_id = {
3334            let mut transaction = fs
3335                .root_store()
3336                .new_transaction(
3337                    lock_keys![LockKey::object(store_id, store.root_directory_object_id())],
3338                    Options::default(),
3339                )
3340                .await
3341                .expect("new_transaction failed");
3342            let handle = root_directory
3343                .create_child_file(&mut transaction, "to_delete")
3344                .await
3345                .expect("create_child_file failed");
3346            transaction.commit().await.expect("commit failed");
3347            let mut buf = handle.allocate_buffer(4096).await;
3348            buf.fill(0xab);
3349            handle.write_or_append(Some(0), buf.as_ref()).await.expect("write failed");
3350            handle.object_id()
3351        };
3352
3353        {
3354            let mut transaction = fs
3355                .root_store()
3356                .new_transaction(
3357                    lock_keys![LockKey::object(
3358                        other_store_id,
3359                        other_store.root_directory_object_id()
3360                    )],
3361                    Options::default(),
3362                )
3363                .await
3364                .expect("new_transaction failed");
3365            other_root_directory
3366                .create_child_file(&mut transaction, "other_file")
3367                .await
3368                .expect("create_child_file failed");
3369            transaction.commit().await.expect("commit failed");
3370        }
3371
3372        // Compact so the super-block records the current actual reserved metadata space.
3373        fs.journal().force_compact().await.expect("force_compact failed");
3374        fs.journal().stop_compactions().await;
3375
3376        let (store_borrowed_after_compact, store_req_after_compact) =
3377            fs.object_manager().store_borrowed(store_id).unwrap();
3378        assert_eq!(store_borrowed_after_compact, 0);
3379        assert!(store_req_after_compact > 0);
3380        let (other_borrowed_after_compact, other_req_after_compact) =
3381            fs.object_manager().store_borrowed(other_store_id).unwrap();
3382        assert_eq!(other_borrowed_after_compact, 0);
3383        assert!(other_req_after_compact > 0);
3384
3385        // Commit a non-borrowing transaction followed by borrowing transactions (unlink +
3386        // tombstone) in `store` after the super-block, leaving `other_store` untouched.
3387        {
3388            let mut transaction = fs
3389                .root_store()
3390                .new_transaction(
3391                    lock_keys![LockKey::object(store_id, store.root_directory_object_id())],
3392                    Options::default(),
3393                )
3394                .await
3395                .expect("new_transaction failed");
3396            root_directory
3397                .create_child_file(&mut transaction, "keep")
3398                .await
3399                .expect("create_child_file failed");
3400            transaction.commit().await.expect("commit failed");
3401
3402            let mut transaction = fs
3403                .root_store()
3404                .new_transaction(
3405                    lock_keys![
3406                        LockKey::object(store_id, store.root_directory_object_id()),
3407                        LockKey::object(store_id, object_id),
3408                    ],
3409                    Options { reservation: ReservationOptions::Borrowed, ..Default::default() },
3410                )
3411                .await
3412                .expect("new_transaction failed");
3413            replace_child(&mut transaction, None, (&root_directory, "to_delete"))
3414                .await
3415                .expect("replace_child failed");
3416            transaction.commit().await.expect("commit failed");
3417
3418            fs.tombstone_object(store_id, object_id, None).await.expect("tombstone_object failed");
3419        }
3420
3421        fs.sync(SyncOptions { flush_device: true, ..Default::default() })
3422            .await
3423            .expect("sync failed");
3424
3425        let expected_reservation = fs.object_manager().metadata_reservation().amount();
3426        let expected_borrowed = fs.object_manager().borrowed_metadata_space();
3427        let expected_reserved_map = fs.object_manager().reserved_metadata_space();
3428        assert!(expected_borrowed > 0);
3429
3430        let (expected_store_borrowed, expected_store_req) =
3431            fs.object_manager().store_borrowed(store_id).unwrap();
3432        assert!(expected_store_borrowed > 0 && expected_store_borrowed < expected_store_req);
3433        let (expected_other_borrowed, expected_other_req) =
3434            fs.object_manager().store_borrowed(other_store_id).unwrap();
3435        assert_eq!(expected_other_borrowed, 0);
3436
3437        let snapshot1 = fs.device().snapshot().expect("snapshot failed");
3438        let snapshot2 = fs.device().snapshot().expect("snapshot failed");
3439        let snapshot3 = fs.device().snapshot().expect("snapshot failed");
3440        fs.close().await.expect("close failed");
3441
3442        // 1. Remount with unchanged required_reservation: total and per-store borrowed amounts
3443        // must match live state across the system and all child stores.
3444        {
3445            let fs2 = FxFilesystem::open(snapshot1).await.expect("open failed");
3446            assert_eq!(fs2.object_manager().metadata_reservation().amount(), expected_reservation);
3447            assert_eq!(fs2.object_manager().borrowed_metadata_space(), expected_borrowed);
3448            assert_eq!(fs2.object_manager().reserved_metadata_space(), expected_reserved_map);
3449            assert_eq!(
3450                fs2.object_manager().store_borrowed(store_id),
3451                Some((expected_store_borrowed, expected_store_req))
3452            );
3453            assert_eq!(
3454                fs2.object_manager().store_borrowed(other_store_id),
3455                Some((expected_other_borrowed, expected_other_req))
3456            );
3457            fsck(fs2.clone()).await.expect("fsck failed");
3458            fs2.close().await.expect("close failed");
3459        }
3460
3461        // 2. Remount with an increased required_reservation (simulating a version upgrade that
3462        // increases expected reservation): actual metadata_reservation must remain unchanged rather
3463        // than reserving more from the allocator at mount time, and fsck must succeed.
3464        {
3465            let extra = 65536;
3466            let fs2 = FxFilesystemBuilder::new()
3467                .on_new_store(move |store| {
3468                    store
3469                        .filesystem()
3470                        .ensure_local()
3471                        .object_manager()
3472                        .set_reserved_space(super::RESERVED_SPACE + extra);
3473                })
3474                .open(snapshot2)
3475                .await
3476                .expect("open failed");
3477            assert_eq!(fs2.object_manager().metadata_reservation().amount(), expected_reservation);
3478            assert_eq!(fs2.object_manager().borrowed_metadata_space(), expected_borrowed + extra);
3479            assert_eq!(fs2.object_manager().reserved_metadata_space(), expected_reserved_map);
3480            fsck(fs2.clone()).await.expect("fsck failed");
3481            fs2.close().await.expect("close failed");
3482        }
3483
3484        // 3. Remount with a decreased required_reservation (smaller than expected_reservation):
3485        // metadata_reservation is capped at `required_reservation()`.
3486        {
3487            let fs2 = FxFilesystemBuilder::new()
3488                .on_new_store(move |store| {
3489                    store.filesystem().ensure_local().object_manager().set_reserved_space(0);
3490                })
3491                .open(snapshot3)
3492                .await
3493                .expect("open failed");
3494            assert_eq!(
3495                fs2.object_manager().metadata_reservation().amount(),
3496                fs2.object_manager().required_reservation()
3497            );
3498            assert_eq!(fs2.object_manager().borrowed_metadata_space(), 0);
3499            fsck(fs2.clone()).await.expect("fsck failed");
3500            fs2.close().await.expect("close failed");
3501        }
3502    }
3503
3504    #[fuchsia::test]
3505    async fn test_borrowing_between_begin_and_end_flush_and_replay() {
3506        let device = DeviceHolder::new(FakeDevice::new(8192, 4096));
3507        let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
3508        let root_volume = root_volume(fs.clone()).await.expect("root_volume failed");
3509        let store_a = root_volume
3510            .new_volume("store_a", NewChildStoreOptions::default())
3511            .await
3512            .expect("new_volume failed");
3513        let store_a_id = store_a.store_object_id();
3514        let dir_a = Directory::open(&store_a, store_a.root_directory_object_id())
3515            .await
3516            .expect("open failed");
3517
3518        let store_b = root_volume
3519            .new_volume("store_b", NewChildStoreOptions::default())
3520            .await
3521            .expect("new_volume failed");
3522        let store_b_id = store_b.store_object_id();
3523        let dir_b = Directory::open(&store_b, store_b.root_directory_object_id())
3524            .await
3525            .expect("open failed");
3526
3527        let mut file_ids = Vec::new();
3528        for name in ["file1", "file2"] {
3529            let mut transaction = fs
3530                .root_store()
3531                .new_transaction(
3532                    lock_keys![LockKey::object(store_a_id, store_a.root_directory_object_id())],
3533                    Options::default(),
3534                )
3535                .await
3536                .expect("new_transaction failed");
3537            let handle = dir_a
3538                .create_child_file(&mut transaction, name)
3539                .await
3540                .expect("create_child_file failed");
3541            transaction.commit().await.expect("commit failed");
3542            let mut buf = handle.allocate_buffer(4096).await;
3543            buf.fill(0xcd);
3544            handle.write_or_append(Some(0), buf.as_ref()).await.expect("write failed");
3545            file_ids.push(handle.object_id());
3546        }
3547
3548        fs.journal().force_compact().await.expect("force_compact failed");
3549        fs.journal().stop_compactions().await;
3550
3551        // 1. Borrow in `store_a` before `BeginFlush` by deleting and tombstoning `file1`.
3552        {
3553            let mut transaction = fs
3554                .root_store()
3555                .new_transaction(
3556                    lock_keys![
3557                        LockKey::object(store_a_id, store_a.root_directory_object_id()),
3558                        LockKey::object(store_a_id, file_ids[0]),
3559                    ],
3560                    Options { reservation: ReservationOptions::Borrowed, ..Default::default() },
3561                )
3562                .await
3563                .expect("new_transaction failed");
3564            replace_child(&mut transaction, None, (&dir_a, "file1"))
3565                .await
3566                .expect("replace_child failed");
3567            transaction.commit().await.expect("commit failed");
3568            fs.tombstone_object(store_a_id, file_ids[0], None)
3569                .await
3570                .expect("tombstone_object failed");
3571        }
3572
3573        let (pre_borrowed, _) = fs.object_manager().store_borrowed(store_a_id).unwrap();
3574        assert!(pre_borrowed > 0);
3575
3576        // 2. Hold the lock on `store_a`'s StoreInfo object in `root_store` so `store_a.flush()`
3577        // commits `BeginFlush`, writes the new layer file, and pauses right before `EndFlush`.
3578        let store_info_lock = fs
3579            .lock_manager()
3580            .write_lock(lock_keys![LockKey::object(
3581                fs.root_store().store_object_id(),
3582                store_a.store_info_handle_object_id().unwrap(),
3583            )])
3584            .await;
3585
3586        let store_a_clone = store_a.clone();
3587        let flush_task = fasync::Task::spawn(async move {
3588            store_a_clone.flush().await.expect("store_a flush failed");
3589        });
3590
3591        // Wait until `BeginFlush` has sealed the mutable layer of `store_a`.
3592        while !store_a.tree().immutable_layer_set().layers.iter().any(|l| l.handle().is_none()) {
3593            fasync::Timer::new(MonotonicDuration::from_millis(1)).await;
3594        }
3595
3596        // 3. While `store_a` is between `BeginFlush` and `EndFlush`, borrow in `store_a` (deleting
3597        // `file2`) and commit a funded transaction in `store_b`.
3598        {
3599            let mut transaction = fs
3600                .root_store()
3601                .new_transaction(
3602                    lock_keys![
3603                        LockKey::object(store_a_id, store_a.root_directory_object_id()),
3604                        LockKey::object(store_a_id, file_ids[1]),
3605                    ],
3606                    Options { reservation: ReservationOptions::Borrowed, ..Default::default() },
3607                )
3608                .await
3609                .expect("new_transaction failed");
3610            replace_child(&mut transaction, None, (&dir_a, "file2"))
3611                .await
3612                .expect("replace_child failed");
3613            transaction.commit().await.expect("commit failed");
3614            fs.tombstone_object(store_a_id, file_ids[1], None)
3615                .await
3616                .expect("tombstone_object failed");
3617
3618            let mut transaction = fs
3619                .root_store()
3620                .new_transaction(
3621                    lock_keys![LockKey::object(store_b_id, store_b.root_directory_object_id())],
3622                    Options::default(),
3623                )
3624                .await
3625                .expect("new_transaction failed");
3626            dir_b
3627                .create_child_file(&mut transaction, "b_file")
3628                .await
3629                .expect("create_child_file failed");
3630            transaction.commit().await.expect("commit failed");
3631        }
3632
3633        let (total_borrowed_mid, _) = fs.object_manager().store_borrowed(store_a_id).unwrap();
3634        let post_borrowed = total_borrowed_mid - pre_borrowed;
3635        assert!(post_borrowed > 0);
3636
3637        // Allow `EndFlush` to complete.
3638        std::mem::drop(store_info_lock);
3639        flush_task.await;
3640
3641        // Pre-`BeginFlush` borrowed space must be cleared by the full compaction, while
3642        // post-`BeginFlush` borrowed space must remain borrowed.
3643        let (b_a_after, req_a_after) = fs.object_manager().store_borrowed(store_a_id).unwrap();
3644        assert_eq!(b_a_after, post_borrowed);
3645        let (b_b_after, req_b_after) = fs.object_manager().store_borrowed(store_b_id).unwrap();
3646        assert_eq!(b_b_after, 0);
3647
3648        // 4. Snapshot before a new super-block is written (so `EndFlush` is replayed after the
3649        // super-block) and verify replay produces the exact same reservations across all buckets.
3650        fs.sync(SyncOptions { flush_device: true, ..Default::default() })
3651            .await
3652            .expect("sync failed");
3653        let expected_map_before_sb = fs.object_manager().reserved_metadata_space();
3654        let snapshot_before_sb = fs.device().snapshot().expect("snapshot failed");
3655
3656        // 5. Write a new super-block (where `journal_file_offsets[store_a_id]` points to the
3657        // post-`BeginFlush` transaction prior to `EndFlush`), commit one more transaction, and
3658        // snapshot again to verify replay when `EndFlush` precedes
3659        // `super_block_journal_file_offset`.
3660        fs.journal().write_super_block().await.expect("write_super_block failed");
3661        {
3662            let mut transaction = fs
3663                .root_store()
3664                .new_transaction(
3665                    lock_keys![LockKey::object(store_a_id, store_a.root_directory_object_id())],
3666                    Options::default(),
3667                )
3668                .await
3669                .expect("new_transaction failed");
3670            dir_a
3671                .create_child_file(&mut transaction, "after_sb")
3672                .await
3673                .expect("create_child_file failed");
3674            transaction.commit().await.expect("commit failed");
3675        }
3676        fs.sync(SyncOptions { flush_device: true, ..Default::default() })
3677            .await
3678            .expect("sync failed");
3679        let expected_map_after_sb = fs.object_manager().reserved_metadata_space();
3680        let expected_store_a_after_sb = fs.object_manager().store_borrowed(store_a_id);
3681        let expected_store_b_after_sb = fs.object_manager().store_borrowed(store_b_id);
3682        let snapshot_after_sb = fs.device().snapshot().expect("snapshot failed");
3683        fs.close().await.expect("close failed");
3684
3685        {
3686            let fs2 = FxFilesystem::open(snapshot_before_sb).await.expect("open failed");
3687            assert_eq!(fs2.object_manager().reserved_metadata_space(), expected_map_before_sb);
3688            assert_eq!(
3689                fs2.object_manager().store_borrowed(store_a_id),
3690                Some((b_a_after, req_a_after))
3691            );
3692            assert_eq!(
3693                fs2.object_manager().store_borrowed(store_b_id),
3694                Some((b_b_after, req_b_after))
3695            );
3696            fsck(fs2.clone()).await.expect("fsck failed");
3697            fs2.close().await.expect("close failed");
3698        }
3699
3700        {
3701            let fs3 = FxFilesystem::open(snapshot_after_sb).await.expect("open failed");
3702            assert_eq!(fs3.object_manager().reserved_metadata_space(), expected_map_after_sb);
3703            assert_eq!(fs3.object_manager().store_borrowed(store_a_id), expected_store_a_after_sb);
3704            assert_eq!(fs3.object_manager().store_borrowed(store_b_id), expected_store_b_after_sb);
3705            fsck(fs3.clone()).await.expect("fsck failed");
3706            fs3.close().await.expect("close failed");
3707        }
3708    }
3709
3710    async fn populate_store_with_base_layer(store: &ObjectStore) {
3711        let store_id = store.store_object_id();
3712        let dir_id = store.root_directory_object_id();
3713
3714        // Populate `store` with > 512 KiB (`DEFAULT_RECLAIM_SIZE`) of inline extended attributes
3715        // so minor compactions keep their base layer.
3716        for batch in 0..28 {
3717            let mut transaction = store
3718                .new_transaction(lock_keys![LockKey::object(store_id, dir_id)], Options::default())
3719                .await
3720                .expect("new_transaction failed");
3721            for i in 0..50 {
3722                let idx = batch * 50 + i;
3723                transaction.add(
3724                    store_id,
3725                    Mutation::replace_or_insert_object(
3726                        ObjectKey::extended_attribute(
3727                            dir_id,
3728                            format!("attr_{idx:04}_{:<200}", idx).into_bytes(),
3729                        ),
3730                        ObjectValue::inline_extended_attribute(vec![0x55; 200]),
3731                    ),
3732                );
3733            }
3734            transaction.commit().await.expect("commit failed");
3735        }
3736        store
3737            .flush_with_reason(FlushReason::Journal(ForceMajor::True))
3738            .await
3739            .expect("flush failed");
3740    }
3741
3742    #[fuchsia::test]
3743    async fn test_per_store_selective_major_compaction_isolation() {
3744        let device = DeviceHolder::new(FakeDevice::new(16384, 4096));
3745        let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
3746        let root_volume = root_volume(fs.clone()).await.expect("root_volume failed");
3747
3748        let store_a = root_volume
3749            .new_volume("store_a", NewChildStoreOptions::default())
3750            .await
3751            .expect("new_volume failed");
3752        let store_a_id = store_a.store_object_id();
3753        let root_dir_a = store_a.root_directory_object_id();
3754
3755        let store_b = root_volume
3756            .new_volume("store_b", NewChildStoreOptions::default())
3757            .await
3758            .expect("new_volume failed");
3759        let store_b_id = store_b.store_object_id();
3760        let root_dir_b = store_b.root_directory_object_id();
3761
3762        populate_store_with_base_layer(&store_a).await;
3763        populate_store_with_base_layer(&store_b).await;
3764        fs.journal().force_compact().await.expect("force_compact failed");
3765        fs.journal().stop_compactions().await;
3766        assert_eq!(store_a.load_store_info().await.unwrap().layers.len(), 1);
3767        assert_eq!(store_b.load_store_info().await.unwrap().layers.len(), 1);
3768
3769        // Add a second layer to `store_b` via a funded transaction and minor flush.
3770        {
3771            let mut transaction = store_b
3772                .new_transaction(
3773                    lock_keys![LockKey::object(store_b_id, root_dir_b)],
3774                    Options::default(),
3775                )
3776                .await
3777                .expect("new_transaction failed");
3778            transaction.add(
3779                store_b_id,
3780                Mutation::replace_or_insert_object(
3781                    ObjectKey::extended_attribute(root_dir_b, b"extra".to_vec()),
3782                    ObjectValue::inline_extended_attribute(vec![0x77; 200]),
3783                ),
3784            );
3785            transaction.commit().await.expect("commit failed");
3786            store_b
3787                .flush_with_reason(FlushReason::Journal(ForceMajor::False))
3788                .await
3789                .expect("minor flush failed");
3790        }
3791        assert_eq!(store_b.load_store_info().await.unwrap().layers.len(), 2);
3792        let (b_b_before, req_b_before) = fs.object_manager().store_borrowed(store_b_id).unwrap();
3793        assert_eq!(b_b_before, 0);
3794
3795        // Delete extended attributes in `store_a` using `Borrowed` until `B_A >= R_A / 2`.
3796        for batch in 0..28 {
3797            let mut transaction = store_a
3798                .new_transaction(
3799                    lock_keys![LockKey::object(store_a_id, root_dir_a)],
3800                    Options {
3801                        skip_journal_checks: true,
3802                        reservation: ReservationOptions::Borrowed,
3803                        ..Default::default()
3804                    },
3805                )
3806                .await
3807                .expect("new_transaction failed");
3808            for i in 0..50 {
3809                let idx = batch * 50 + i;
3810                transaction.add(
3811                    store_a_id,
3812                    Mutation::replace_or_insert_object(
3813                        ObjectKey::extended_attribute(
3814                            root_dir_a,
3815                            format!("attr_{idx:04}_{:<200}", idx).into_bytes(),
3816                        ),
3817                        ObjectValue::None,
3818                    ),
3819                );
3820            }
3821            transaction.commit().await.expect("commit failed");
3822            let (b_a, req_a) = fs.object_manager().store_borrowed(store_a_id).unwrap();
3823            if b_a >= req_a - req_a / 2 {
3824                break;
3825            }
3826        }
3827        let (b_a_depleted, req_a_depleted) =
3828            fs.object_manager().store_borrowed(store_a_id).unwrap();
3829        assert!(b_a_depleted >= req_a_depleted - req_a_depleted / 2);
3830
3831        // `store_b`'s borrowed space must remain completely isolated from `store_a`'s borrowing.
3832        assert_eq!(fs.object_manager().store_borrowed(store_b_id), Some((0, req_b_before)));
3833
3834        // Flushing with `ForceMajor::False` must force a major compaction on `store_a` (resetting
3835        // `B_A == 0` and merging its layers into 1) while leaving `store_b` untouched (still 2
3836        // layers).
3837        fs.object_manager()
3838            .flush(FlushReason::Journal(ForceMajor::False))
3839            .await
3840            .expect("flush failed");
3841        assert_eq!(store_a.load_store_info().await.unwrap().layers.len(), 1);
3842        let (b_a_restored, _) = fs.object_manager().store_borrowed(store_a_id).unwrap();
3843        assert_eq!(b_a_restored, 0);
3844        assert_eq!(store_b.load_store_info().await.unwrap().layers.len(), 2);
3845
3846        fsck(fs.clone()).await.expect("fsck failed");
3847        fs.close().await.expect("close failed");
3848    }
3849
3850    #[fuchsia::test]
3851    async fn test_shortfall_major_compaction_does_not_cycle() {
3852        let device = DeviceHolder::new(FakeDevice::new(16384, 4096));
3853        let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
3854        let root_volume = root_volume(fs.clone()).await.expect("root_volume failed");
3855
3856        let store_b = root_volume
3857            .new_volume("store_b", NewChildStoreOptions::default())
3858            .await
3859            .expect("new_volume failed");
3860        let store_b_id = store_b.store_object_id();
3861        let root_dir_b = store_b.root_directory_object_id();
3862
3863        populate_store_with_base_layer(&store_b).await;
3864        fs.journal().force_compact().await.expect("force_compact failed");
3865        fs.journal().stop_compactions().await;
3866
3867        // Add a second layer to `store_b` via a funded transaction and minor flush.
3868        {
3869            let mut transaction = store_b
3870                .new_transaction(
3871                    lock_keys![LockKey::object(store_b_id, root_dir_b)],
3872                    Options::default(),
3873                )
3874                .await
3875                .expect("new_transaction failed");
3876            transaction.add(
3877                store_b_id,
3878                Mutation::replace_or_insert_object(
3879                    ObjectKey::extended_attribute(root_dir_b, b"extra".to_vec()),
3880                    ObjectValue::inline_extended_attribute(vec![0x77; 200]),
3881                ),
3882            );
3883            transaction.commit().await.expect("commit failed");
3884            store_b
3885                .flush_with_reason(FlushReason::Journal(ForceMajor::False))
3886                .await
3887                .expect("minor flush failed");
3888        }
3889        assert_eq!(store_b.load_store_info().await.unwrap().layers.len(), 2);
3890        let (_, req_b_before) = fs.object_manager().store_borrowed(store_b_id).unwrap();
3891
3892        // Simulate a shortfall in `M_total` alongside `B_B >= R_B / 2` on `store_b` (which has 2
3893        // layers of live data and no tombstones, so compacting yields no space).
3894        let current_total = fs.object_manager().metadata_reservation().amount();
3895        let reduction = current_total / 2;
3896        fs.object_manager().metadata_reservation().give_back(reduction);
3897        fs.object_manager().set_store_borrowed(store_b_id, req_b_before);
3898
3899        // 1) Flushing with `ForceMajor::False` must major-compact `store_b` down to 1 layer and
3900        // reset `B_B == 0` even though `M_total` is short and compacting yielded no space.
3901        fs.journal().force_compact().await.expect("force_compact failed");
3902        let info_b_after_major = store_b.load_store_info().await.unwrap();
3903        assert_eq!(info_b_after_major.layers.len(), 1);
3904        let (b_b_restored, _) = fs.object_manager().store_borrowed(store_b_id).unwrap();
3905        assert_eq!(b_b_restored, 0);
3906
3907        // 2) Commit a single transaction on `store_b` while `M_total` is still short and flush
3908        // with `ForceMajor::False`. Because `B_B` stayed `0` across
3909        // `reconcile_metadata_reservation`, this flush must perform a minor compaction (creating a
3910        // 2nd layer) rather than cycling on major compactions.
3911        {
3912            let mut transaction = store_b
3913                .new_transaction(
3914                    lock_keys![LockKey::object(store_b_id, root_dir_b)],
3915                    Options::default(),
3916                )
3917                .await
3918                .expect("new_transaction failed");
3919            transaction.add(
3920                store_b_id,
3921                Mutation::replace_or_insert_object(
3922                    ObjectKey::extended_attribute(root_dir_b, b"after_shortfall".to_vec()),
3923                    ObjectValue::inline_extended_attribute(vec![0x88; 200]),
3924                ),
3925            );
3926            transaction.commit().await.expect("commit failed");
3927        }
3928        fs.object_manager()
3929            .flush(FlushReason::Journal(ForceMajor::False))
3930            .await
3931            .expect("flush failed");
3932        assert_eq!(
3933            store_b.load_store_info().await.unwrap().layers.len(),
3934            2,
3935            "Single transaction after shortfall major compaction must not trigger major compaction"
3936        );
3937
3938        fs.allocator()
3939            .reserve(None, reduction)
3940            .unwrap()
3941            .move_to(fs.object_manager().metadata_reservation(), reduction);
3942        fsck(fs.clone()).await.expect("fsck failed");
3943        fs.close().await.expect("close failed");
3944    }
3945
3946    #[fuchsia::test]
3947    async fn test_no_op_major_compaction_guard() {
3948        let device = DeviceHolder::new(FakeDevice::new(16384, 4096));
3949        let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
3950        let root_volume = root_volume(fs.clone()).await.expect("root_volume failed");
3951
3952        let store_a = root_volume
3953            .new_volume("store_a", NewChildStoreOptions::default())
3954            .await
3955            .expect("new_volume failed");
3956        let store_a_id = store_a.store_object_id();
3957
3958        populate_store_with_base_layer(&store_a).await;
3959        fs.journal().force_compact().await.expect("force_compact failed");
3960        fs.journal().stop_compactions().await;
3961
3962        // If `B_A >= R_A / 2` when `store_a` already has only 1 layer and no unflushed journal
3963        // mutations, `flush(FlushReason::Journal(ForceMajor::False))` must not re-compact
3964        // `store_a`.
3965        let layer_oid_a_before = store_a.load_store_info().await.unwrap().layers[0];
3966        let (_, req_a) = fs.object_manager().store_borrowed(store_a_id).unwrap();
3967        fs.object_manager().set_store_borrowed(store_a_id, req_a);
3968        fs.object_manager()
3969            .flush(FlushReason::Journal(ForceMajor::False))
3970            .await
3971            .expect("flush failed");
3972        assert_eq!(store_a.load_store_info().await.unwrap().layers, vec![layer_oid_a_before]);
3973
3974        fs.object_manager().set_store_borrowed(store_a_id, 0);
3975        fsck(fs.clone()).await.expect("fsck failed");
3976        fs.close().await.expect("close failed");
3977    }
3978
3979    #[fuchsia::test]
3980    async fn test_minor_compaction_replay_and_delete_volume_prunes_borrowed() {
3981        let device = DeviceHolder::new(FakeDevice::new(16384, 4096));
3982        let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
3983        let root_volume = root_volume(fs.clone()).await.expect("root_volume failed");
3984
3985        let store_b = root_volume
3986            .new_volume("store_b", NewChildStoreOptions::default())
3987            .await
3988            .expect("new_volume failed");
3989        let store_b_id = store_b.store_object_id();
3990        let root_dir_b = store_b.root_directory_object_id();
3991
3992        populate_store_with_base_layer(&store_b).await;
3993        fs.journal().force_compact().await.expect("force_compact failed");
3994        fs.journal().stop_compactions().await;
3995
3996        // Commit a small borrowing transaction on `store_b` (which has 1 base layer > 512 KiB),
3997        // perform a minor flush (`layers.len() > 1`, which creates a new minor layer and preserves
3998        // `B_B > 0`), and create + delete a volume `store_c`. Verify that `DeleteVolume` does not
3999        // leave `store_c` in `store_borrowed` and that replaying the journal across the minor
4000        // `EndFlush` preserves `store_b`'s borrowed space.
4001        {
4002            let mut transaction = store_b
4003                .new_transaction(
4004                    lock_keys![LockKey::object(store_b_id, root_dir_b)],
4005                    Options {
4006                        skip_journal_checks: true,
4007                        reservation: ReservationOptions::Borrowed,
4008                        ..Default::default()
4009                    },
4010                )
4011                .await
4012                .expect("new_transaction failed");
4013            transaction.add(
4014                store_b_id,
4015                Mutation::replace_or_insert_object(
4016                    ObjectKey::extended_attribute(root_dir_b, b"attr_0000_0".to_vec()),
4017                    ObjectValue::None,
4018                ),
4019            );
4020            transaction.commit().await.expect("commit failed");
4021        }
4022        let (b_b_minor, _) = fs.object_manager().store_borrowed(store_b_id).unwrap();
4023        assert!(b_b_minor > 0);
4024        store_b
4025            .flush_with_reason(FlushReason::Journal(ForceMajor::False))
4026            .await
4027            .expect("minor flush failed");
4028        assert!(store_b.load_store_info().await.unwrap().layers.len() > 1);
4029        assert_eq!(fs.object_manager().store_borrowed(store_b_id).unwrap().0, b_b_minor);
4030
4031        let store_c = root_volume
4032            .new_volume("store_c", NewChildStoreOptions::default())
4033            .await
4034            .expect("new_volume failed");
4035        let store_c_id = store_c.store_object_id();
4036        std::mem::drop(store_c);
4037        let (_, delete_txn) = root_volume
4038            .acquire_transaction_for_remove_volume("store_c", [], false, Options::default())
4039            .await
4040            .expect("acquire_transaction_for_remove_volume failed");
4041        root_volume
4042            .delete_volume("store_c", delete_txn, || {})
4043            .await
4044            .expect("delete_volume failed");
4045        assert!(fs.object_manager().store_borrowed(store_c_id).is_none());
4046
4047        fs.sync(SyncOptions { flush_device: true, ..Default::default() })
4048            .await
4049            .expect("sync failed");
4050        let expected_map = fs.object_manager().reserved_metadata_space();
4051        assert!(!expected_map.contains_key(&store_c_id));
4052        let snapshot = fs.device().snapshot().expect("snapshot failed");
4053        fs.close().await.expect("close failed");
4054
4055        let fs2 = FxFilesystem::open(snapshot).await.expect("open failed");
4056        assert_eq!(fs2.object_manager().reserved_metadata_space(), expected_map);
4057        fsck(fs2.clone()).await.expect("fsck failed");
4058        fs2.close().await.expect("close failed");
4059    }
4060
4061    #[fuchsia::test]
4062    async fn test_legacy_borrowed_proportional_distribution() {
4063        let device = DeviceHolder::new(FakeDevice::new(8192, 4096));
4064        let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
4065        let root_volume = root_volume(fs.clone()).await.expect("root_volume failed");
4066
4067        let crypt_a = Arc::new(new_insecure_crypt());
4068        let store_a = root_volume
4069            .new_volume(
4070                "store_a",
4071                NewChildStoreOptions {
4072                    options: StoreOptions {
4073                        crypt: Some(crypt_a.clone()),
4074                        ..StoreOptions::default()
4075                    },
4076                    ..NewChildStoreOptions::default()
4077                },
4078            )
4079            .await
4080            .expect("new_volume failed");
4081        let store_a_id = store_a.store_object_id();
4082        let root_dir_a = store_a.root_directory_object_id();
4083
4084        let store_b = root_volume
4085            .new_volume("store_b", NewChildStoreOptions::default())
4086            .await
4087            .expect("new_volume failed");
4088        let store_b_id = store_b.store_object_id();
4089        let root_dir_b = store_b.root_directory_object_id();
4090
4091        // Give `store_a` more metadata than `store_b`.
4092        for i in 0..16 {
4093            let mut transaction = store_a
4094                .new_transaction(
4095                    lock_keys![LockKey::object(store_a_id, root_dir_a)],
4096                    Options::default(),
4097                )
4098                .await
4099                .expect("new_transaction failed");
4100            transaction.add(
4101                store_a_id,
4102                Mutation::replace_or_insert_object(
4103                    ObjectKey::extended_attribute(root_dir_a, format!("attr_{i}").into_bytes()),
4104                    ObjectValue::inline_extended_attribute(vec![0xaa; 1024]),
4105                ),
4106            );
4107            transaction.commit().await.expect("commit failed");
4108        }
4109        {
4110            let mut transaction = store_b
4111                .new_transaction(
4112                    lock_keys![LockKey::object(store_b_id, root_dir_b)],
4113                    Options::default(),
4114                )
4115                .await
4116                .expect("new_transaction failed");
4117            transaction.add(
4118                store_b_id,
4119                Mutation::replace_or_insert_object(
4120                    ObjectKey::extended_attribute(root_dir_b, b"attr_0".to_vec()),
4121                    ObjectValue::inline_extended_attribute(vec![0xbb; 1024]),
4122                ),
4123            );
4124            transaction.commit().await.expect("commit failed");
4125        }
4126
4127        fs.journal().force_compact().await.expect("force_compact failed");
4128        fs.journal().stop_compactions().await;
4129
4130        // Write an additional mutation to `store_a`, lock `store_a`, and flush it while locked so
4131        // `store_a` has both layer files and a non-empty `encrypted_mutations` file.
4132        {
4133            let mut transaction = store_a
4134                .new_transaction(
4135                    lock_keys![LockKey::object(store_a_id, root_dir_a)],
4136                    Options::default(),
4137                )
4138                .await
4139                .expect("new_transaction failed");
4140            transaction.add(
4141                store_a_id,
4142                Mutation::replace_or_insert_object(
4143                    ObjectKey::extended_attribute(root_dir_a, b"locked_attr".to_vec()),
4144                    ObjectValue::inline_extended_attribute(vec![0xcc; 1024]),
4145                ),
4146            );
4147            transaction.commit().await.expect("commit failed");
4148        }
4149        store_a.lock().await.expect("lock failed");
4150        store_a.flush().await.expect("locked flush failed");
4151        fs.journal().write_super_block().await.expect("write_super_block failed");
4152        fs.journal().write_super_block().await.expect("write_super_block failed");
4153
4154        // After the super-block, commit:
4155        // 1) A funded (`Commit`) transaction on `store_b`.
4156        // 2) A V60 borrowing (`CommitBorrowed`) transaction on `store_b`.
4157        {
4158            let mut transaction = store_b
4159                .new_transaction(
4160                    lock_keys![LockKey::object(store_b_id, root_dir_b)],
4161                    Options::default(),
4162                )
4163                .await
4164                .expect("new_transaction failed");
4165            transaction.add(
4166                store_b_id,
4167                Mutation::replace_or_insert_object(
4168                    ObjectKey::extended_attribute(root_dir_b, b"funded_attr".to_vec()),
4169                    ObjectValue::inline_extended_attribute(vec![0xdd; 64]),
4170                ),
4171            );
4172            transaction.commit().await.expect("commit failed");
4173        }
4174        let offset_before_legacy_borrowed = fs.object_manager().last_end_offset();
4175        {
4176            let mut transaction = store_b
4177                .new_transaction(
4178                    lock_keys![LockKey::object(store_b_id, root_dir_b)],
4179                    Options {
4180                        skip_journal_checks: true,
4181                        reservation: ReservationOptions::Borrowed,
4182                        ..Default::default()
4183                    },
4184                )
4185                .await
4186                .expect("new_transaction failed");
4187            transaction.add(
4188                store_b_id,
4189                Mutation::replace_or_insert_object(
4190                    ObjectKey::extended_attribute(root_dir_b, b"legacy_borrowed_attr".to_vec()),
4191                    ObjectValue::None,
4192                ),
4193            );
4194            transaction.commit().await.expect("commit failed");
4195        }
4196        let offset_after_legacy_borrowed = fs.object_manager().last_end_offset();
4197
4198        let (_, req_a) = fs.object_manager().store_borrowed(store_a_id).unwrap();
4199        let (_, req_b) = fs.object_manager().store_borrowed(store_b_id).unwrap();
4200        assert!(req_a > req_b);
4201
4202        // Compute `legacy_required` (`max_i(1.5 * L_i) + 4 * J_total + 1_048_576`), where locked
4203        // `store_a` includes `encrypted_mutations_size * 3`.
4204        let (_, min_checkpoint) = fs.object_manager().journal_file_offsets();
4205        let min_offset = min_checkpoint.unwrap().file_offset;
4206        let journal_usage = offset_after_legacy_borrowed - min_offset;
4207        let info_a = store_a.load_store_info().await.unwrap();
4208        let mut store_a_layer_size = 0;
4209        for &oid in &info_a.layers {
4210            store_a_layer_size += fs.root_store().get_file_size(oid).await.unwrap();
4211        }
4212        assert_ne!(info_a.encrypted_mutations_object_id, INVALID_OBJECT_ID);
4213        store_a_layer_size +=
4214            fs.root_store().get_file_size(info_a.encrypted_mutations_object_id).await.unwrap() * 3;
4215        let max_layer_size = [
4216            fs.allocator()
4217                .tree()
4218                .immutable_layer_set()
4219                .layers
4220                .iter()
4221                .map(|l| l.handle().map(ReadObjectHandle::get_size).unwrap_or(0))
4222                .sum::<u64>(),
4223            fs.root_store()
4224                .tree()
4225                .immutable_layer_set()
4226                .layers
4227                .iter()
4228                .map(|l| l.handle().map(ReadObjectHandle::get_size).unwrap_or(0))
4229                .sum::<u64>(),
4230            store_a_layer_size,
4231            store_b
4232                .tree()
4233                .immutable_layer_set()
4234                .layers
4235                .iter()
4236                .map(|l| l.handle().map(ReadObjectHandle::get_size).unwrap_or(0))
4237                .sum::<u64>(),
4238        ]
4239        .into_iter()
4240        .max()
4241        .unwrap();
4242        assert_eq!(max_layer_size, store_a_layer_size);
4243        let legacy_required = max_layer_size * 3 / 2 + journal_usage * 4 + 1_048_576;
4244        let new_required = fs.object_manager().required_reservation();
4245        assert!(new_required > legacy_required);
4246
4247        let initial_legacy_borrowed = 1_048_576 / 4;
4248        let expected_final_legacy_borrowed = initial_legacy_borrowed
4249            + (offset_after_legacy_borrowed - offset_before_legacy_borrowed) * 4;
4250
4251        fs.sync(SyncOptions { flush_device: true, ..Default::default() })
4252            .await
4253            .expect("sync failed");
4254        let snapshot_legacy = fs.device().snapshot().expect("snapshot failed");
4255        fs.close().await.expect("close failed");
4256
4257        // Seed `ReplayMetadataReservation::Borrowed` when `store_a` is opened in
4258        // `on_replay_complete` (right before `init_metadata_reservation`) so
4259        // `init_metadata_reservation` runs in `< V61` migration mode with `store_b`'s unflushed
4260        // borrowing in `journal_checkpoints`.
4261        let fs2 = FxFilesystemBuilder::new()
4262            .on_new_store(move |store| {
4263                if store.store_object_id() == store_a_id {
4264                    store
4265                        .filesystem()
4266                        .ensure_local()
4267                        .object_manager()
4268                        .init_replay_metadata_reservation(
4269                            Version { major: 59, minor: 0 },
4270                            &HashMap::from_iter([(0, expected_final_legacy_borrowed)]),
4271                        );
4272                }
4273            })
4274            .open(snapshot_legacy)
4275            .await
4276            .expect("open failed");
4277
4278        let expected_reservation = legacy_required - expected_final_legacy_borrowed;
4279        assert_eq!(fs2.object_manager().metadata_reservation().amount(), expected_reservation);
4280        assert_eq!(
4281            fs2.object_manager().borrowed_metadata_space(),
4282            new_required - expected_reservation
4283        );
4284        let (b_a, r_a) = fs2.object_manager().store_borrowed(store_a_id).unwrap();
4285        let (b_b, r_b) = fs2.object_manager().store_borrowed(store_b_id).unwrap();
4286        assert!(b_a > 0 && b_a < r_a);
4287        assert!(b_b > 0 && b_b < r_b);
4288        fsck(fs2.clone()).await.expect("fsck failed");
4289        fs2.close().await.expect("close failed");
4290    }
4291
4292    #[fuchsia::test]
4293    async fn test_edge_cases_shrink_minor_layer_both_checkpoints_and_system_compaction() {
4294        let device = DeviceHolder::new(FakeDevice::new(16384, 4096));
4295        let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
4296        let root_volume = root_volume(fs.clone()).await.expect("root_volume failed");
4297
4298        let store_a = root_volume
4299            .new_volume("store_a", NewChildStoreOptions::default())
4300            .await
4301            .expect("new_volume failed");
4302        let store_a_id = store_a.store_object_id();
4303        let root_dir_a = store_a.root_directory_object_id();
4304
4305        // Populate `store_a` with > 512 KiB so minor compactions keep its base layer.
4306        for batch in 0..28 {
4307            let mut transaction = store_a
4308                .new_transaction(
4309                    lock_keys![LockKey::object(store_a_id, root_dir_a)],
4310                    Options::default(),
4311                )
4312                .await
4313                .expect("new_transaction failed");
4314            for i in 0..50 {
4315                let idx = batch * 50 + i;
4316                transaction.add(
4317                    store_a_id,
4318                    Mutation::replace_or_insert_object(
4319                        ObjectKey::extended_attribute(
4320                            root_dir_a,
4321                            format!("base_{idx:04}_{:<200}", idx).into_bytes(),
4322                        ),
4323                        ObjectValue::inline_extended_attribute(vec![0x55; 200]),
4324                    ),
4325                );
4326            }
4327            transaction.commit().await.expect("commit failed");
4328        }
4329        store_a
4330            .flush_with_reason(FlushReason::Journal(ForceMajor::True))
4331            .await
4332            .expect("major flush failed");
4333        fs.journal().force_compact().await.expect("force_compact failed");
4334        fs.journal().stop_compactions().await;
4335        assert_eq!(store_a.load_store_info().await.unwrap().layers.len(), 1);
4336
4337        // 1) Exercise `Checkpoints::Both(x, y)` merging into `Checkpoints::Old(x + y)` on repeated
4338        // `BeginFlush`: commit borrowing transaction `x`, then `Mutation::BeginFlush`, then
4339        // borrowing transaction `y`, and then minor-flush `store_a`.
4340        {
4341            let mut transaction = store_a
4342                .new_transaction(
4343                    lock_keys![LockKey::object(store_a_id, root_dir_a)],
4344                    Options {
4345                        skip_journal_checks: true,
4346                        reservation: ReservationOptions::Borrowed,
4347                        ..Default::default()
4348                    },
4349                )
4350                .await
4351                .expect("new_transaction failed");
4352            for i in 0..20 {
4353                transaction.add(
4354                    store_a_id,
4355                    Mutation::replace_or_insert_object(
4356                        ObjectKey::extended_attribute(
4357                            root_dir_a,
4358                            format!("minor_attr_{i:02}").into_bytes(),
4359                        ),
4360                        ObjectValue::inline_extended_attribute(vec![0xaa; 1024]),
4361                    ),
4362                );
4363            }
4364            transaction.commit().await.expect("commit failed");
4365
4366            let mut begin_txn = fs
4367                .root_store()
4368                .new_transaction(
4369                    lock_keys![],
4370                    Options {
4371                        skip_journal_checks: true,
4372                        reservation: ReservationOptions::Borrowed,
4373                        ..Default::default()
4374                    },
4375                )
4376                .await
4377                .expect("new_transaction failed");
4378            begin_txn.add(store_a_id, Mutation::BeginFlush);
4379            begin_txn.commit().await.expect("commit failed");
4380
4381            let mut transaction = store_a
4382                .new_transaction(
4383                    lock_keys![LockKey::object(store_a_id, root_dir_a)],
4384                    Options {
4385                        skip_journal_checks: true,
4386                        reservation: ReservationOptions::Borrowed,
4387                        ..Default::default()
4388                    },
4389                )
4390                .await
4391                .expect("new_transaction failed");
4392            for i in 20..40 {
4393                transaction.add(
4394                    store_a_id,
4395                    Mutation::replace_or_insert_object(
4396                        ObjectKey::extended_attribute(
4397                            root_dir_a,
4398                            format!("minor_attr_{i:02}").into_bytes(),
4399                        ),
4400                        ObjectValue::inline_extended_attribute(vec![0xaa; 1024]),
4401                    ),
4402                );
4403            }
4404            transaction.commit().await.expect("commit failed");
4405        }
4406        store_a
4407            .flush_with_reason(FlushReason::Journal(ForceMajor::False))
4408            .await
4409            .expect("minor flush failed");
4410        assert_eq!(store_a.load_store_info().await.unwrap().layers.len(), 2);
4411        let (layer_borrowed_after_grow, _) =
4412            fs.object_manager().store_borrowed(store_a_id).unwrap();
4413        assert!(layer_borrowed_after_grow > 0);
4414
4415        // 2) Exercise pre-super-block partitioning of `B_A` into `store_borrowed` (flushed layer)
4416        // and `journal_checkpoints` (unflushed journal): commit an unflushed borrowing transaction,
4417        // write a super-block without flushing `store_a`, remount, and verify `store_borrowed`
4418        // matches before and after a minor flush.
4419        {
4420            let mut transaction = store_a
4421                .new_transaction(
4422                    lock_keys![LockKey::object(store_a_id, root_dir_a)],
4423                    Options {
4424                        skip_journal_checks: true,
4425                        reservation: ReservationOptions::Borrowed,
4426                        ..Default::default()
4427                    },
4428                )
4429                .await
4430                .expect("new_transaction failed");
4431            transaction.add(
4432                store_a_id,
4433                Mutation::replace_or_insert_object(
4434                    ObjectKey::extended_attribute(root_dir_a, b"unflushed_before_sb".to_vec()),
4435                    ObjectValue::None,
4436                ),
4437            );
4438            transaction.commit().await.expect("commit failed");
4439        }
4440        let (total_b_with_unflushed, _) = fs.object_manager().store_borrowed(store_a_id).unwrap();
4441        assert!(total_b_with_unflushed > layer_borrowed_after_grow);
4442        fs.journal().write_super_block().await.expect("write_super_block failed");
4443        {
4444            let snapshot = fs.device().snapshot().expect("snapshot failed");
4445            let fs_replay = FxFilesystem::open(snapshot).await.expect("open failed");
4446            assert_eq!(
4447                fs_replay.object_manager().store_borrowed(store_a_id).unwrap().0,
4448                total_b_with_unflushed
4449            );
4450            fs_replay.close().await.expect("close failed");
4451        }
4452
4453        // 3) Exercise minor compaction shrinking a store's layer size (`new_size < old_size` in
4454        // `store_borrowed_for_flush`): replace the 40 large (1024-byte) attributes in Layer 1 with
4455        // `ObjectValue::None` in a funded transaction and minor-flush `store_a`.
4456        {
4457            let mut transaction = store_a
4458                .new_transaction(
4459                    lock_keys![LockKey::object(store_a_id, root_dir_a)],
4460                    Options::default(),
4461                )
4462                .await
4463                .expect("new_transaction failed");
4464            for i in 0..40 {
4465                transaction.add(
4466                    store_a_id,
4467                    Mutation::replace_or_insert_object(
4468                        ObjectKey::extended_attribute(
4469                            root_dir_a,
4470                            format!("minor_attr_{i:02}").into_bytes(),
4471                        ),
4472                        ObjectValue::None,
4473                    ),
4474                );
4475            }
4476            transaction.commit().await.expect("commit failed");
4477        }
4478        let res_before_shrink = fs.object_manager().reservation(store_a_id).unwrap();
4479        store_a
4480            .flush_with_reason(FlushReason::Journal(ForceMajor::False))
4481            .await
4482            .expect("minor flush failed");
4483        assert_eq!(store_a.load_store_info().await.unwrap().layers.len(), 2);
4484        let res_after_shrink = fs.object_manager().reservation(store_a_id).unwrap();
4485        assert!(res_after_shrink < res_before_shrink);
4486        let (layer_borrowed_after_shrink, _) =
4487            fs.object_manager().store_borrowed(store_a_id).unwrap();
4488        assert_eq!(
4489            layer_borrowed_after_shrink,
4490            layer_borrowed_after_grow.saturating_sub(res_before_shrink - res_after_shrink)
4491        );
4492
4493        // 4) Exercise system bucket (`root_store`) forced major compaction when
4494        // `b_sys >= sys_headroom / 2`: reset `store_a` borrowed to 0 while keeping 2 layers on
4495        // `store_a`, commit a mutation on `root_store` so `root_store` can compact, simulate
4496        // system borrowing by giving back reservation space, and verify a journal flush forces
4497        // `root_store` to compact without compacting `store_a`.
4498        fs.object_manager().set_store_borrowed(store_a_id, 0);
4499        {
4500            let root_store = fs.root_store();
4501            let root_id = root_store.store_object_id();
4502            let graveyard_id = root_store.graveyard_directory_object_id();
4503            let mut transaction = root_store
4504                .new_transaction(
4505                    lock_keys![LockKey::object(root_id, graveyard_id)],
4506                    Options::default(),
4507                )
4508                .await
4509                .expect("new_transaction failed");
4510            transaction.add(
4511                root_id,
4512                Mutation::replace_or_insert_object(
4513                    ObjectKey::extended_attribute(graveyard_id, b"sys_test".to_vec()),
4514                    ObjectValue::None,
4515                ),
4516            );
4517            transaction.commit().await.expect("commit failed");
4518        }
4519        let current_res = fs.object_manager().metadata_reservation().amount();
4520        fs.object_manager().metadata_reservation().give_back(current_res / 2);
4521        fs.object_manager()
4522            .flush(FlushReason::Journal(ForceMajor::False))
4523            .await
4524            .expect("flush failed");
4525        assert_eq!(
4526            store_a.load_store_info().await.unwrap().layers.len(),
4527            2,
4528            "System borrowing must not force major compaction on child store_a when B_A == 0"
4529        );
4530        let restored = fs.allocator().reserve(None, current_res / 2).unwrap();
4531        restored.move_to(fs.object_manager().metadata_reservation(), current_res / 2);
4532        fsck(fs.clone()).await.expect("fsck failed");
4533        fs.close().await.expect("close failed");
4534    }
4535
4536    #[fuchsia::test]
4537    async fn test_locked_store_isolation_and_minor_flush_reservation() {
4538        let device = DeviceHolder::new(FakeDevice::new(16384, 4096));
4539        let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
4540        let root_volume = root_volume(fs.clone()).await.expect("root_volume failed");
4541
4542        let crypt_a = Arc::new(new_insecure_crypt());
4543        let store_a = root_volume
4544            .new_volume(
4545                "store_a",
4546                NewChildStoreOptions {
4547                    options: StoreOptions {
4548                        crypt: Some(crypt_a.clone()),
4549                        ..StoreOptions::default()
4550                    },
4551                    ..NewChildStoreOptions::default()
4552                },
4553            )
4554            .await
4555            .expect("new_volume failed");
4556        let store_a_id = store_a.store_object_id();
4557        let root_dir_a = store_a.root_directory_object_id();
4558
4559        let crypt_b = Arc::new(new_insecure_crypt());
4560        let store_b = root_volume
4561            .new_volume(
4562                "store_b",
4563                NewChildStoreOptions {
4564                    options: StoreOptions {
4565                        crypt: Some(crypt_b.clone()),
4566                        ..StoreOptions::default()
4567                    },
4568                    ..NewChildStoreOptions::default()
4569                },
4570            )
4571            .await
4572            .expect("new_volume failed");
4573        let store_b_id = store_b.store_object_id();
4574        let root_dir_b = store_b.root_directory_object_id();
4575
4576        // Populate both encrypted stores so each has a base layer > 512 KiB.
4577        for (store, store_id, dir_id) in
4578            [(&store_a, store_a_id, root_dir_a), (&store_b, store_b_id, root_dir_b)]
4579        {
4580            for batch in 0..28 {
4581                let mut transaction = store
4582                    .new_transaction(
4583                        lock_keys![LockKey::object(store_id, dir_id)],
4584                        Options::default(),
4585                    )
4586                    .await
4587                    .expect("new_transaction failed");
4588                for i in 0..50 {
4589                    let idx = batch * 50 + i;
4590                    transaction.add(
4591                        store_id,
4592                        Mutation::replace_or_insert_object(
4593                            ObjectKey::extended_attribute(
4594                                dir_id,
4595                                format!("attr_{idx:04}_{:<200}", idx).into_bytes(),
4596                            ),
4597                            ObjectValue::inline_extended_attribute(vec![0x55; 200]),
4598                        ),
4599                    );
4600                }
4601                transaction.commit().await.expect("commit failed");
4602            }
4603        }
4604
4605        store_a
4606            .flush_with_reason(FlushReason::Journal(ForceMajor::True))
4607            .await
4608            .expect("flush failed");
4609        store_b
4610            .flush_with_reason(FlushReason::Journal(ForceMajor::True))
4611            .await
4612            .expect("flush failed");
4613        fs.journal().force_compact().await.expect("force_compact failed");
4614        fs.journal().stop_compactions().await;
4615
4616        // Add a 2nd layer to `store_b` via a funded transaction and minor flush.
4617        {
4618            let mut transaction = store_b
4619                .new_transaction(
4620                    lock_keys![LockKey::object(store_b_id, root_dir_b)],
4621                    Options::default(),
4622                )
4623                .await
4624                .expect("new_transaction failed");
4625            transaction.add(
4626                store_b_id,
4627                Mutation::replace_or_insert_object(
4628                    ObjectKey::extended_attribute(root_dir_b, b"extra_1".to_vec()),
4629                    ObjectValue::inline_extended_attribute(vec![0x77; 200]),
4630                ),
4631            );
4632            transaction.commit().await.expect("commit failed");
4633            store_b
4634                .flush_with_reason(FlushReason::Journal(ForceMajor::False))
4635                .await
4636                .expect("minor flush failed");
4637        }
4638        fs.journal().force_compact().await.expect("force_compact failed");
4639        assert_eq!(store_a.load_store_info().await.unwrap().layers.len(), 1);
4640        assert_eq!(store_b.load_store_info().await.unwrap().layers.len(), 2);
4641        assert_eq!(fs.object_manager().borrowed_metadata_space(), 0);
4642
4643        // 1) Minor flush reservation retention test:
4644        // Commit borrowing deletions on `store_a`, perform a minor flush (`ForceMajor::False`),
4645        // and compact the journal (`reconcile_metadata_reservation`). When `store_a` subsequently
4646        // performs a major compaction (`ForceMajor::True`) and compacts the journal, `M_total` must
4647        // be fully restored (`borrowed_metadata_space() == 0`).
4648        {
4649            let mut transaction = store_a
4650                .new_transaction(
4651                    lock_keys![LockKey::object(store_a_id, root_dir_a)],
4652                    Options {
4653                        skip_journal_checks: true,
4654                        reservation: ReservationOptions::Borrowed,
4655                        ..Default::default()
4656                    },
4657                )
4658                .await
4659                .expect("new_transaction failed");
4660            for i in 0..50 {
4661                transaction.add(
4662                    store_a_id,
4663                    Mutation::replace_or_insert_object(
4664                        ObjectKey::extended_attribute(
4665                            root_dir_a,
4666                            format!("temp_{i:04}_{:<200}", i).into_bytes(),
4667                        ),
4668                        ObjectValue::None,
4669                    ),
4670                );
4671            }
4672            transaction.commit().await.expect("commit failed");
4673            store_a
4674                .flush_with_reason(FlushReason::Journal(ForceMajor::False))
4675                .await
4676                .expect("minor flush failed");
4677            assert_eq!(store_a.load_store_info().await.unwrap().layers.len(), 2);
4678        }
4679        fs.journal().force_compact().await.expect("force_compact failed");
4680        store_a
4681            .flush_with_reason(FlushReason::Journal(ForceMajor::True))
4682            .await
4683            .expect("major flush failed");
4684        fs.journal().force_compact().await.expect("force_compact failed");
4685        assert_eq!(store_a.load_store_info().await.unwrap().layers.len(), 1);
4686        assert_eq!(fs.object_manager().store_borrowed(store_a_id).unwrap().0, 0);
4687        assert_eq!(
4688            fs.object_manager().borrowed_metadata_space(),
4689            0,
4690            "reconcile_metadata_reservation must not give away M_total during a minor flush"
4691        );
4692
4693        // 2) Locked store isolation test:
4694        // Commit borrowing deletions on `store_a`, flush the first batch as a minor compaction
4695        // while unlocked, and lock `store_a` while the second batch is still in the journal so that
4696        // `force_compact` flushes it via `flush_locked()`.
4697        {
4698            let mut transaction = store_a
4699                .new_transaction(
4700                    lock_keys![LockKey::object(store_a_id, root_dir_a)],
4701                    Options {
4702                        skip_journal_checks: true,
4703                        reservation: ReservationOptions::Borrowed,
4704                        ..Default::default()
4705                    },
4706                )
4707                .await
4708                .expect("new_transaction failed");
4709            for i in 0..50 {
4710                transaction.add(
4711                    store_a_id,
4712                    Mutation::replace_or_insert_object(
4713                        ObjectKey::extended_attribute(
4714                            root_dir_a,
4715                            format!("temp_{i:04}_{:<200}", i).into_bytes(),
4716                        ),
4717                        ObjectValue::None,
4718                    ),
4719                );
4720            }
4721            transaction.commit().await.expect("commit failed");
4722            store_a
4723                .flush_with_reason(FlushReason::Journal(ForceMajor::False))
4724                .await
4725                .expect("minor flush failed");
4726            assert_eq!(store_a.load_store_info().await.unwrap().layers.len(), 2);
4727
4728            let mut transaction = store_a
4729                .new_transaction(
4730                    lock_keys![LockKey::object(store_a_id, root_dir_a)],
4731                    Options {
4732                        skip_journal_checks: true,
4733                        reservation: ReservationOptions::Borrowed,
4734                        ..Default::default()
4735                    },
4736                )
4737                .await
4738                .expect("new_transaction failed");
4739            for i in 50..100 {
4740                transaction.add(
4741                    store_a_id,
4742                    Mutation::replace_or_insert_object(
4743                        ObjectKey::extended_attribute(
4744                            root_dir_a,
4745                            format!("temp_{i:04}_{:<200}", i).into_bytes(),
4746                        ),
4747                        ObjectValue::None,
4748                    ),
4749                );
4750            }
4751            transaction.commit().await.expect("commit failed");
4752        }
4753
4754        store_a.lock().await.expect("lock failed");
4755        fs.journal().force_compact().await.expect("force_compact failed");
4756
4757        let (b_a_locked, req_a_locked) = fs.object_manager().store_borrowed(store_a_id).unwrap();
4758        assert!(b_a_locked > 0);
4759        // Simulate `store_a` having borrowed up to its full `req_A` budget before being locked.
4760        fs.object_manager().set_store_borrowed(store_a_id, req_a_locked);
4761
4762        // While `store_a` is locked with maximal borrowed space, `store_b` (which has 2 layers)
4763        // must remain completely isolated and not be forced into a major compaction.
4764        fs.object_manager()
4765            .flush(FlushReason::Journal(ForceMajor::False))
4766            .await
4767            .expect("flush failed");
4768        assert_eq!(
4769            store_b.load_store_info().await.unwrap().layers.len(),
4770            2,
4771            "Locked store_a must not force major compactions on unlocked store_b"
4772        );
4773        assert_eq!(fs.object_manager().store_borrowed(store_b_id).unwrap().0, 0);
4774
4775        // Unlock `store_a`: because `B_A >= req_A / 2`, a journal flush must now force a major
4776        // compaction on `store_a`, purging its transient attributes and resetting `B_A` to 0
4777        // without touching `store_b`'s 2 layers.
4778        store_a.unlock(crypt_a).await.expect("unlock failed");
4779        fs.object_manager()
4780            .flush(FlushReason::Journal(ForceMajor::False))
4781            .await
4782            .expect("flush failed");
4783        assert_eq!(store_a.load_store_info().await.unwrap().layers.len(), 1);
4784        assert_eq!(fs.object_manager().store_borrowed(store_a_id).unwrap().0, 0);
4785        assert_eq!(store_b.load_store_info().await.unwrap().layers.len(), 2);
4786
4787        // Compact the journal and verify that all metadata reservation space borrowed by `store_a`
4788        // during its minor/locked flushes was retained and restored
4789        // (`borrowed_metadata_space == 0`).
4790        fs.journal().force_compact().await.expect("force_compact failed");
4791        assert_eq!(fs.object_manager().borrowed_metadata_space(), 0);
4792
4793        fsck(fs.clone()).await.expect("fsck failed");
4794        fs.close().await.expect("close failed");
4795    }
4796}
4797
4798#[cfg(fuzz)]
4799mod fuzz {
4800    use fuzz::fuzz;
4801
4802    #[fuzz]
4803    fn fuzz_journal_bytes(input: Vec<u8>) {
4804        use crate::filesystem::FxFilesystem;
4805        use fuchsia_async as fasync;
4806        use std::io::Write;
4807        use storage_device::DeviceHolder;
4808        use storage_device::fake_device::FakeDevice;
4809
4810        fasync::SendExecutorBuilder::new().num_threads(4).build().run(async move {
4811            let device = DeviceHolder::new(FakeDevice::new(32768, 512));
4812            let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
4813            fs.journal().inner.lock().writer.write_all(&input).expect("write failed");
4814            fs.close().await.expect("close failed");
4815            let device = fs.take_device().await;
4816            device.reopen(false);
4817            if let Ok(fs) = FxFilesystem::open(device).await {
4818                // `close()` can fail if there were objects to be tombstoned. If the said object is
4819                // corrupted, there will be an error when we compact the journal.
4820                let _ = fs.close().await;
4821            }
4822        });
4823    }
4824
4825    #[fuzz]
4826    fn fuzz_journal(input: Vec<super::JournalRecord>) {
4827        use crate::filesystem::FxFilesystem;
4828        use fuchsia_async as fasync;
4829        use storage_device::DeviceHolder;
4830        use storage_device::fake_device::FakeDevice;
4831
4832        fasync::SendExecutorBuilder::new().num_threads(4).build().run(async move {
4833            let device = DeviceHolder::new(FakeDevice::new(32768, 512));
4834            let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
4835            {
4836                let mut inner = fs.journal().inner.lock();
4837                for record in &input {
4838                    let _ = inner.writer.write_record(record);
4839                }
4840            }
4841            fs.close().await.expect("close failed");
4842            let device = fs.take_device().await;
4843            device.reopen(false);
4844            if let Ok(fs) = FxFilesystem::open(device).await {
4845                // `close()` can fail if there were objects to be tombstoned. If the said object is
4846                // corrupted, there will be an error when we compact the journal.
4847                let _ = fs.close().await;
4848            }
4849        });
4850    }
4851}