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, ChecksumsV38};
26use crate::errors::FxfsError;
27use crate::filesystem::{ApplyContext, ApplyMode, FxFilesystem, SyncOptions};
28use crate::log::*;
29use crate::lsm_tree::cache::NullCache;
30use crate::lsm_tree::types::Layer;
31use crate::object_handle::{ObjectHandle as _, ReadObjectHandle};
32use crate::object_store::allocator::Allocator;
33use crate::object_store::data_object_handle::OverwriteOptions;
34use crate::object_store::extent_record::{ExtentMode, ExtentValue};
35use crate::object_store::graveyard::Graveyard;
36use crate::object_store::journal::bootstrap_handle::BootstrapObjectHandle;
37use crate::object_store::journal::checksum_list::ChecksumList;
38use crate::object_store::journal::reader::{JournalReader, ReadResult};
39use crate::object_store::journal::super_block::{
40    SuperBlockHeader, SuperBlockInstance, SuperBlockManager,
41};
42use crate::object_store::journal::writer::JournalWriter;
43use crate::object_store::object_manager::ObjectManager;
44use crate::object_store::object_record::{AttributeKey, ObjectKey, ObjectKeyData, ObjectValue};
45use crate::object_store::transaction::{
46    AllocatorMutation, LockKey, Mutation, MutationV40, MutationV41, MutationV43, MutationV46,
47    MutationV47, MutationV49, MutationV50, MutationV54, MutationV55, MutationV56,
48    ObjectStoreMutation, Options, TRANSACTION_MAX_JOURNAL_USAGE, Transaction, TxnMutation,
49    lock_keys,
50};
51use crate::object_store::{
52    AssocObj, AttributeId, DataObjectHandle, Extent, HandleOptions, HandleOwner, INVALID_OBJECT_ID,
53    Item, ItemRef, NewChildStoreOptions, ObjectStore, ReservedId,
54};
55use crate::range::RangeExt;
56use crate::round::{round_div, round_down};
57use crate::serialized_types::{
58    LATEST_VERSION, Migrate, Version, Versioned, migrate_nodefault, migrate_to_version,
59};
60use anyhow::{Context, Error, anyhow, bail, ensure};
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 static_assertions::const_assert;
70use std::clone::Clone;
71use std::collections::HashSet;
72use std::num::NonZero;
73use std::ops::{Bound, Range};
74use std::sync::atomic::{AtomicBool, Ordering};
75use std::sync::{Arc, OnceLock};
76use std::task::{Poll, Waker};
77use storage_device::Device;
78
79// The journal file is written to in blocks of this size.
80pub const BLOCK_SIZE: u64 = 4096;
81
82// The journal file is extended by this amount when necessary.
83const CHUNK_SIZE: u64 = 131_072;
84const_assert!(CHUNK_SIZE > TRANSACTION_MAX_JOURNAL_USAGE);
85
86// See the comment for the `reclaim_size` member of Inner.
87pub const DEFAULT_RECLAIM_SIZE: u64 = 524_288;
88
89// Temporary space that should be reserved for the journal.  For example: space that is currently
90// used in the journal file but cannot be deallocated yet because we are flushing.
91pub const RESERVED_SPACE: u64 = 1_048_576;
92
93// Whenever the journal is replayed (i.e. the system is unmounted and remounted), we reset the
94// journal stream, at which point any half-complete transactions are discarded.  We indicate a
95// journal reset by XORing the previous block's checksum with this mask, and using that value as a
96// seed for the next journal block.
97const RESET_XOR: u64 = 0xffffffffffffffff;
98
99// To keep track of offsets within a journal file, we need both the file offset and the check-sum of
100// the preceding block, since the check-sum of the preceding block is an input to the check-sum of
101// every block.
102pub type JournalCheckpoint = JournalCheckpointV32;
103
104#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize, TypeFingerprint)]
105pub struct JournalCheckpointV32 {
106    pub file_offset: u64,
107
108    // Starting check-sum for block that contains file_offset i.e. the checksum for the previous
109    // block.
110    pub checksum: Checksum,
111
112    // If versioned, the version of elements stored in the journal. e.g. JournalRecord version.
113    // This can change across reset events so we store it along with the offset and checksum to
114    // know which version to deserialize.
115    pub version: Version,
116}
117
118pub type JournalRecord = JournalRecordV56;
119
120#[allow(clippy::large_enum_variant)]
121#[derive(Clone, Debug, Serialize, Deserialize, TypeFingerprint, Versioned)]
122#[cfg_attr(fuzz, derive(arbitrary::Arbitrary))]
123pub enum JournalRecordV56 {
124    EndBlock,
125    Mutation {
126        object_id: u64,
127        mutation: MutationV56,
128    },
129    /// Commits records in the transaction.
130    Commit,
131    /// Discard all mutations with offsets greater than or equal to the given offset.
132    Discard(u64),
133    /// Indicates the device was flushed at the given journal offset.
134    /// Note that this really means that at this point in the journal offset, we can be certain that
135    /// there's no remaining buffered data in the block device; the buffers and the disk contents are
136    /// consistent.
137    /// We insert one of these records *after* a flush along with the *next* transaction to go
138    /// through.  If that never comes (either due to graceful or hard shutdown), the journal reset
139    /// on the next mount will serve the same purpose and count as a flush, although it is necessary
140    /// to defensively flush the device before replaying the journal (if possible, i.e. not
141    /// read-only) in case the block device connection was reused.
142    DidFlushDevice(u64),
143    /// Checksums for a data range written by this transaction. A transaction is only valid if these
144    /// checksums are right. The range is the device offset the checksums are for.
145    ///
146    /// A boolean indicates whether this range is being written to for the first time. For overwrite
147    /// extents, we only check the checksums for a block if it has been written to for the first
148    /// time since the last flush, because otherwise we can't roll it back anyway so it doesn't
149    /// matter. For copy-on-write extents, the bool is always true.
150    DataChecksums(Range<u64>, crate::checksum::ChecksumsV38, bool),
151}
152
153#[allow(clippy::large_enum_variant)]
154#[derive(Migrate, Clone, Debug, Serialize, Deserialize, TypeFingerprint, Versioned)]
155#[migrate_to_version(JournalRecordV56)]
156pub enum JournalRecordV55 {
157    EndBlock,
158    Mutation { object_id: u64, mutation: MutationV55 },
159    Commit,
160    Discard(u64),
161    DidFlushDevice(u64),
162    DataChecksums(Range<u64>, crate::checksum::ChecksumsV38, bool),
163}
164
165#[allow(clippy::large_enum_variant)]
166#[derive(Migrate, Serialize, Deserialize, TypeFingerprint, Versioned)]
167#[migrate_to_version(JournalRecordV55)]
168#[migrate_nodefault]
169pub enum JournalRecordV54 {
170    EndBlock,
171    Mutation { object_id: u64, mutation: MutationV54 },
172    Commit,
173    Discard(u64),
174    DidFlushDevice(u64),
175    DataChecksums(Range<u64>, crate::checksum::ChecksumsV38, bool),
176}
177
178#[allow(clippy::large_enum_variant)]
179#[derive(Migrate, Serialize, Deserialize, TypeFingerprint, Versioned)]
180#[migrate_to_version(JournalRecordV54)]
181#[migrate_nodefault]
182pub enum JournalRecordV50 {
183    EndBlock,
184    Mutation { object_id: u64, mutation: MutationV50 },
185    Commit,
186    Discard(u64),
187    DidFlushDevice(u64),
188    DataChecksums(Range<u64>, ChecksumsV38, bool),
189}
190
191#[allow(clippy::large_enum_variant)]
192#[derive(Migrate, Serialize, Deserialize, TypeFingerprint, Versioned)]
193#[migrate_to_version(JournalRecordV50)]
194pub enum JournalRecordV49 {
195    EndBlock,
196    Mutation { object_id: u64, mutation: MutationV49 },
197    Commit,
198    Discard(u64),
199    DidFlushDevice(u64),
200    DataChecksums(Range<u64>, ChecksumsV38, bool),
201}
202
203#[allow(clippy::large_enum_variant)]
204#[derive(Migrate, Serialize, Deserialize, TypeFingerprint, Versioned)]
205#[migrate_to_version(JournalRecordV49)]
206pub enum JournalRecordV47 {
207    EndBlock,
208    Mutation { object_id: u64, mutation: MutationV47 },
209    Commit,
210    Discard(u64),
211    DidFlushDevice(u64),
212    DataChecksums(Range<u64>, ChecksumsV38, bool),
213}
214
215#[allow(clippy::large_enum_variant)]
216#[derive(Migrate, Serialize, Deserialize, TypeFingerprint, Versioned)]
217#[migrate_to_version(JournalRecordV47)]
218pub enum JournalRecordV46 {
219    EndBlock,
220    Mutation { object_id: u64, mutation: MutationV46 },
221    Commit,
222    Discard(u64),
223    DidFlushDevice(u64),
224    DataChecksums(Range<u64>, ChecksumsV38, bool),
225}
226
227#[allow(clippy::large_enum_variant)]
228#[derive(Migrate, Serialize, Deserialize, TypeFingerprint, Versioned)]
229#[migrate_to_version(JournalRecordV46)]
230pub enum JournalRecordV43 {
231    EndBlock,
232    Mutation { object_id: u64, mutation: MutationV43 },
233    Commit,
234    Discard(u64),
235    DidFlushDevice(u64),
236    DataChecksums(Range<u64>, ChecksumsV38, bool),
237}
238
239#[derive(Migrate, Serialize, Deserialize, TypeFingerprint, Versioned)]
240#[migrate_to_version(JournalRecordV43)]
241pub enum JournalRecordV42 {
242    EndBlock,
243    Mutation { object_id: u64, mutation: MutationV41 },
244    Commit,
245    Discard(u64),
246    DidFlushDevice(u64),
247    DataChecksums(Range<u64>, ChecksumsV38, bool),
248}
249
250#[derive(Serialize, Deserialize, TypeFingerprint, Versioned)]
251pub enum JournalRecordV41 {
252    EndBlock,
253    Mutation { object_id: u64, mutation: MutationV41 },
254    Commit,
255    Discard(u64),
256    DidFlushDevice(u64),
257    DataChecksums(Range<u64>, ChecksumsV38),
258}
259
260impl From<JournalRecordV41> for JournalRecordV42 {
261    fn from(record: JournalRecordV41) -> Self {
262        match record {
263            JournalRecordV41::EndBlock => Self::EndBlock,
264            JournalRecordV41::Mutation { object_id, mutation } => {
265                Self::Mutation { object_id, mutation: mutation.into() }
266            }
267            JournalRecordV41::Commit => Self::Commit,
268            JournalRecordV41::Discard(offset) => Self::Discard(offset),
269            JournalRecordV41::DidFlushDevice(offset) => Self::DidFlushDevice(offset),
270            JournalRecordV41::DataChecksums(range, sums) => {
271                // At the time of writing the only extents written by real systems are CoW extents
272                // so the new bool is always true.
273                Self::DataChecksums(range, sums, true)
274            }
275        }
276    }
277}
278
279#[derive(Migrate, Serialize, Deserialize, TypeFingerprint, Versioned)]
280#[migrate_to_version(JournalRecordV41)]
281pub enum JournalRecordV40 {
282    EndBlock,
283    Mutation { object_id: u64, mutation: MutationV40 },
284    Commit,
285    Discard(u64),
286    DidFlushDevice(u64),
287    DataChecksums(Range<u64>, ChecksumsV38),
288}
289
290pub(super) fn journal_handle_options() -> HandleOptions {
291    HandleOptions { skip_journal_checks: true, ..Default::default() }
292}
293
294/// The journal records a stream of mutations that are to be applied to other objects.  At mount
295/// time, these records can be replayed into memory.  It provides a way to quickly persist changes
296/// without having to make a large number of writes; they can be deferred to a later time (e.g.
297/// when a sufficient number have been queued).  It also provides support for transactions, the
298/// ability to have mutations that are to be applied atomically together.
299pub struct Journal {
300    objects: Arc<ObjectManager>,
301    handle: OnceLock<DataObjectHandle<ObjectStore>>,
302    super_block_manager: SuperBlockManager,
303    inner: Mutex<Inner>,
304    writer_mutex: Mutex<()>,
305    sync_mutex: futures::lock::Mutex<()>,
306    trace: AtomicBool,
307
308    // This event is used when we are waiting for a compaction to free up journal space.
309    reclaim_event: Event,
310}
311
312struct Inner {
313    super_block_header: SuperBlockHeader,
314
315    // The offset that we can zero the journal up to now that it is no longer needed.
316    zero_offset: Option<u64>,
317
318    // The journal offset that we most recently flushed to the device.
319    device_flushed_offset: u64,
320
321    // If true, indicates a DidFlushDevice record is pending.
322    needs_did_flush_device: bool,
323
324    // The writer for the journal.
325    writer: JournalWriter,
326
327    // Set when a reset is encountered during a read.
328    // Used at write pre_commit() time to ensure we write a version first thing after a reset.
329    output_reset_version: bool,
330
331    // Waker for the flush task.
332    flush_waker: Option<Waker>,
333
334    // Indicates the journal has been terminated.
335    terminate: bool,
336
337    // Latched error indicating reason for journal termination if not graceful.
338    terminate_reason: Option<Error>,
339
340    // Disable compactions.
341    disable_compactions: bool,
342
343    // True if compactions are running.
344    compaction_running: bool,
345
346    // Waker for the sync task for when it's waiting for the flush task to finish.
347    sync_waker: Option<Waker>,
348
349    // The last offset we flushed to the journal file.
350    flushed_offset: u64,
351
352    // The last offset that should be considered valid in the journal file.  Most of the time, this
353    // will be the same as `flushed_offset` but at mount time, this could be less and will only be
354    // up to the end of the last valid transaction; it won't include transactions that follow that
355    // have been discarded.
356    valid_to: u64,
357
358    // If, after replaying, we have to discard a number of mutations (because they don't validate),
359    // this offset specifies where we need to discard back to.  This is so that when we next replay,
360    // we ignore those mutations and continue with new good mutations.
361    discard_offset: Option<u64>,
362
363    // In the steady state, the journal should fluctuate between being approximately half of this
364    // number and this number.  New super-blocks will be written every time about half of this
365    // amount is written to the journal.
366    reclaim_size: u64,
367
368    image_builder_mode: Option<SuperBlockInstance>,
369
370    // If true and `needs_barrier`, issue a pre-barrier on the first device write of each journal
371    // write (which happens in multiples of `BLOCK_SIZE`). This ensures that all the corresponding
372    // data writes make it to disk before the journal gets written to.
373    barriers_enabled: bool,
374
375    // If true, indicates that data write requests have been made to the device since the last
376    // journal write.
377    needs_barrier: bool,
378
379    // True if a compaction is being forced for reasons other than the journal being full.
380    forced_compaction: bool,
381}
382
383impl Inner {
384    fn terminate(&mut self, reason: Option<Error>) {
385        self.terminate = true;
386
387        if let Some(err) = reason {
388            error!(error:? = err; "Terminating journal");
389            // Log previous error if one was already set, otherwise latch the error.
390            if let Some(prev_err) = self.terminate_reason.as_ref() {
391                error!(error:? = prev_err; "Journal previously terminated");
392            } else {
393                self.terminate_reason = Some(err);
394            }
395        }
396
397        if let Some(waker) = self.flush_waker.take() {
398            waker.wake();
399        }
400        if let Some(waker) = self.sync_waker.take() {
401            waker.wake();
402        }
403    }
404}
405
406pub struct JournalOptions {
407    /// In the steady state, the journal should fluctuate between being approximately half of this
408    /// number and this number.  New super-blocks will be written every time about half of this
409    /// amount is written to the journal.
410    pub reclaim_size: u64,
411
412    // If true, issue a pre-barrier on the first device write of each journal write (which happens
413    // in multiples of `BLOCK_SIZE`). This ensures that all the corresponding data writes make it
414    // to disk before the journal gets written to.
415    pub barriers_enabled: bool,
416}
417
418impl Default for JournalOptions {
419    fn default() -> Self {
420        JournalOptions { reclaim_size: DEFAULT_RECLAIM_SIZE, barriers_enabled: false }
421    }
422}
423
424struct JournaledTransactions {
425    transactions: Vec<JournaledTransaction>,
426    device_flushed_offset: u64,
427}
428
429#[derive(Debug, Default)]
430pub struct JournaledTransaction {
431    pub checkpoint: JournalCheckpoint,
432    pub root_parent_mutations: Vec<Mutation>,
433    pub root_mutations: Vec<Mutation>,
434    /// List of (store_object_id, mutation).
435    pub non_root_mutations: Vec<(u64, Mutation)>,
436    pub end_offset: u64,
437    pub checksums: Vec<JournaledChecksums>,
438
439    /// Records offset + 1 of the matching begin_flush transaction. The +1 is because we want to
440    /// ignore the begin flush transaction; we don't need or want to replay it.
441    pub end_flush: Option<(/* store_id: */ u64, /* begin offset: */ u64)>,
442
443    /// The volume which was deleted in this transaction, if any.
444    pub volume_deleted: Option</* store_id: */ u64>,
445}
446
447impl JournaledTransaction {
448    fn new(checkpoint: JournalCheckpoint) -> Self {
449        Self { checkpoint, ..Default::default() }
450    }
451}
452
453const VOLUME_DELETED: u64 = u64::MAX;
454
455#[derive(Debug)]
456pub struct JournaledChecksums {
457    pub device_range: Range<u64>,
458    pub checksums: Checksums,
459    pub first_write: bool,
460}
461
462/// Handles for journal-like objects have some additional functionality to manage their extents,
463/// since during replay we need to add extents as we find them.
464pub trait JournalHandle: ReadObjectHandle {
465    /// The end offset of the last extent in the JournalHandle.  Used only for validating extents
466    /// (which will be skipped if None is returned).
467    /// Note this is equivalent in value to ReadObjectHandle::get_size, when present.
468    fn end_offset(&self) -> Option<u64>;
469    /// Adds an extent to the current end of the journal stream.
470    /// `added_offset` is the offset into the journal of the transaction which added this extent,
471    /// used for discard_extents.
472    fn push_extent(&mut self, added_offset: u64, device_range: Range<u64>);
473    /// Discards all extents which were added in a transaction at offset >= |discard_offset|.
474    fn discard_extents(&mut self, discard_offset: u64);
475}
476
477// Provide a stub implementation for DataObjectHandle so we can use it in
478// Journal::read_transactions.  Manual extent management is a NOP (which is OK since presumably the
479// DataObjectHandle already knows where its extents live).
480impl<S: HandleOwner> JournalHandle for DataObjectHandle<S> {
481    fn end_offset(&self) -> Option<u64> {
482        None
483    }
484    fn push_extent(&mut self, _added_offset: u64, _device_range: Range<u64>) {
485        // NOP
486    }
487    fn discard_extents(&mut self, _discard_offset: u64) {
488        // NOP
489    }
490}
491
492#[fxfs_trace::trace]
493impl Journal {
494    pub fn new(objects: Arc<ObjectManager>, options: JournalOptions) -> Journal {
495        let starting_checksum = rand::random_range(1..u64::MAX);
496        Journal {
497            objects: objects,
498            handle: OnceLock::new(),
499            super_block_manager: SuperBlockManager::new(),
500            inner: Mutex::new(Inner {
501                super_block_header: SuperBlockHeader::default(),
502                zero_offset: None,
503                device_flushed_offset: 0,
504                needs_did_flush_device: false,
505                writer: JournalWriter::new(BLOCK_SIZE as usize, starting_checksum),
506                output_reset_version: false,
507                flush_waker: None,
508                terminate: false,
509                terminate_reason: None,
510                disable_compactions: false,
511                compaction_running: false,
512                sync_waker: None,
513                flushed_offset: 0,
514                valid_to: 0,
515                discard_offset: None,
516                reclaim_size: options.reclaim_size,
517                image_builder_mode: None,
518                barriers_enabled: options.barriers_enabled,
519                needs_barrier: false,
520                forced_compaction: false,
521            }),
522            writer_mutex: Mutex::new(()),
523            sync_mutex: futures::lock::Mutex::new(()),
524            trace: AtomicBool::new(false),
525            reclaim_event: Event::new(),
526        }
527    }
528
529    pub fn set_trace(&self, trace: bool) {
530        let old_value = self.trace.swap(trace, Ordering::Relaxed);
531        if trace != old_value {
532            info!(trace; "J: trace");
533        }
534    }
535
536    pub fn set_image_builder_mode(&self, mode: Option<SuperBlockInstance>) {
537        self.inner.lock().image_builder_mode = mode;
538        if let Some(instance) = mode {
539            *self.super_block_manager.next_instance.lock() = instance;
540        }
541    }
542
543    pub fn image_builder_mode(&self) -> Option<SuperBlockInstance> {
544        self.inner.lock().image_builder_mode
545    }
546
547    #[cfg(feature = "migration")]
548    pub fn set_filesystem_uuid(&self, uuid: &[u8; 16]) -> Result<(), Error> {
549        ensure!(
550            self.inner.lock().image_builder_mode.is_some(),
551            "Can only set filesystem uuid in image builder mode."
552        );
553        self.inner.lock().super_block_header.guid.0 = uuid::Uuid::from_bytes(*uuid);
554        Ok(())
555    }
556
557    pub(crate) async fn read_superblocks(
558        &self,
559        device: Arc<dyn Device>,
560        block_size: u64,
561    ) -> Result<(SuperBlockHeader, ObjectStore), Error> {
562        self.super_block_manager.load(device, block_size).await
563    }
564
565    /// Used during replay to validate a mutation.  This should return false if the mutation is not
566    /// valid and should not be applied.  This could be for benign reasons: e.g. the device flushed
567    /// data out-of-order, or because of a malicious actor.
568    fn validate_mutation(&self, mutation: &Mutation, block_size: u64, device_size: u64) -> bool {
569        match mutation {
570            Mutation::ObjectStore(ObjectStoreMutation {
571                item:
572                    Item {
573                        key:
574                            ObjectKey {
575                                data: ObjectKeyData::Attribute(_, AttributeKey::Extent(extent)),
576                                ..
577                            },
578                        value: ObjectValue::Extent(ExtentValue::Some { device_offset, mode, .. }),
579                        ..
580                    },
581                ..
582            }) => {
583                if extent.is_empty() || !extent.is_aligned(block_size) {
584                    return false;
585                }
586                let len = extent.length().unwrap();
587                if let ExtentMode::Cow(checksums) = mode {
588                    if checksums.len() > 0 {
589                        if len % checksums.len() as u64 != 0 {
590                            return false;
591                        }
592                        if (len / checksums.len() as u64) % block_size != 0 {
593                            return false;
594                        }
595                    }
596                }
597                if *device_offset % block_size != 0
598                    || *device_offset >= device_size
599                    || device_size - *device_offset < len
600                {
601                    return false;
602                }
603            }
604            Mutation::ObjectStore(_) => {}
605            Mutation::EncryptedObjectStore(_) => {}
606            Mutation::Allocator(AllocatorMutation::Allocate { device_range, owner_object_id }) => {
607                return !device_range.is_empty()
608                    && *owner_object_id != INVALID_OBJECT_ID
609                    && device_range.end <= device_size;
610            }
611            Mutation::Allocator(AllocatorMutation::Deallocate {
612                device_range,
613                owner_object_id,
614            }) => {
615                return !device_range.is_empty()
616                    && *owner_object_id != INVALID_OBJECT_ID
617                    && device_range.end <= device_size;
618            }
619            Mutation::Allocator(AllocatorMutation::MarkForDeletion(owner_object_id)) => {
620                return *owner_object_id != INVALID_OBJECT_ID;
621            }
622            Mutation::Allocator(AllocatorMutation::SetLimit { owner_object_id, .. }) => {
623                return *owner_object_id != INVALID_OBJECT_ID;
624            }
625            Mutation::BeginFlush => {}
626            Mutation::EndFlush => {}
627            Mutation::DeleteVolume => {}
628            Mutation::UpdateBorrowed(_) => {}
629            Mutation::UpdateMutationsKey(_) => {}
630            Mutation::CreateInternalDir(owner_object_id) => {
631                return *owner_object_id != INVALID_OBJECT_ID;
632            }
633        }
634        true
635    }
636
637    // Assumes that `mutation` has been validated.
638    fn update_checksum_list(
639        &self,
640        journal_offset: u64,
641        mutation: &Mutation,
642        checksum_list: &mut ChecksumList,
643    ) -> Result<(), Error> {
644        match mutation {
645            Mutation::ObjectStore(_) => {}
646            Mutation::Allocator(AllocatorMutation::Deallocate { device_range, .. }) => {
647                checksum_list.mark_deallocated(journal_offset, device_range.clone().into());
648            }
649            _ => {}
650        }
651        Ok(())
652    }
653
654    /// Reads the latest super-block, and then replays journaled records.
655    #[trace]
656    pub async fn replay(
657        &self,
658        filesystem: Arc<FxFilesystem>,
659        on_new_allocator: Option<Box<dyn Fn(Arc<Allocator>) + Send + Sync>>,
660    ) -> Result<(), Error> {
661        let block_size = filesystem.block_size();
662
663        let (super_block, root_parent) =
664            self.super_block_manager.load(filesystem.device(), block_size).await?;
665
666        let root_parent = Arc::new(ObjectStore::attach_filesystem(root_parent, filesystem.clone()));
667
668        self.objects.set_root_parent_store(root_parent.clone());
669        let allocator =
670            Arc::new(Allocator::new(filesystem.clone(), super_block.allocator_object_id));
671        if let Some(on_new_allocator) = on_new_allocator {
672            on_new_allocator(allocator.clone());
673        }
674        self.objects.set_allocator(allocator.clone());
675        self.objects.set_borrowed_metadata_space(super_block.borrowed_metadata_space);
676        self.objects.set_last_end_offset(super_block.super_block_journal_file_offset);
677        {
678            let mut inner = self.inner.lock();
679            inner.super_block_header = super_block.clone();
680        }
681
682        let device = filesystem.device();
683
684        let mut handle;
685        {
686            let root_parent_layer = root_parent.tree().mutable_layer();
687            let mut iter = root_parent_layer
688                .seek(Bound::Included(&ObjectKey::attribute(
689                    super_block.journal_object_id,
690                    AttributeId::DATA,
691                    AttributeKey::Extent(Extent::search_key_from_offset(round_down(
692                        super_block.journal_checkpoint.file_offset,
693                        BLOCK_SIZE,
694                    ))),
695                )))
696                .await
697                .context("Failed to seek root parent store")?;
698            let start_offset = if let Some(ItemRef {
699                key:
700                    ObjectKey {
701                        data:
702                            ObjectKeyData::Attribute(AttributeId::DATA, AttributeKey::Extent(extent)),
703                        ..
704                    },
705                ..
706            }) = iter.get()
707            {
708                extent.start
709            } else {
710                0
711            };
712            handle = BootstrapObjectHandle::new_with_start_offset(
713                super_block.journal_object_id,
714                device.clone(),
715                start_offset,
716            );
717            while let Some(item) = iter.get() {
718                if !match item.into() {
719                    Some((
720                        object_id,
721                        AttributeId::DATA,
722                        extent,
723                        ExtentValue::Some { device_offset, .. },
724                    )) if object_id == super_block.journal_object_id => {
725                        if let Some(end_offset) = handle.end_offset() {
726                            if extent.start != end_offset {
727                                bail!(anyhow!(FxfsError::Inconsistent).context(format!(
728                                    "Unexpected journal extent {:?}, expected start: {}",
729                                    item, end_offset
730                                )));
731                            }
732                        }
733                        handle.push_extent(
734                            0, // We never discard extents from the root parent store.
735                            *device_offset
736                                ..*device_offset + extent.length().context("Invalid extent")?,
737                        );
738                        true
739                    }
740                    _ => false,
741                } {
742                    break;
743                }
744                iter.advance().await.context("Failed to advance root parent store iterator")?;
745            }
746        }
747
748        let mut reader = JournalReader::new(handle, &super_block.journal_checkpoint);
749        let JournaledTransactions { mut transactions, device_flushed_offset } = self
750            .read_transactions(&mut reader, None, INVALID_OBJECT_ID)
751            .await
752            .context("Reading transactions for replay")?;
753
754        // Validate all the mutations.
755        let mut checksum_list = ChecksumList::new(device_flushed_offset);
756        let mut valid_to = reader.journal_file_checkpoint().file_offset;
757        let device_size = device.size();
758        'bad_replay: for JournaledTransaction {
759            checkpoint,
760            root_parent_mutations,
761            root_mutations,
762            non_root_mutations,
763            checksums,
764            ..
765        } in &transactions
766        {
767            for JournaledChecksums { device_range, checksums, first_write } in checksums {
768                checksum_list
769                    .push(
770                        checkpoint.file_offset,
771                        device_range.clone(),
772                        checksums.maybe_as_ref().context("Malformed checksums")?,
773                        *first_write,
774                    )
775                    .context("Pushing journal checksum records to checksum list")?;
776            }
777            for mutation in root_parent_mutations
778                .iter()
779                .chain(root_mutations)
780                .chain(non_root_mutations.iter().map(|(_, m)| m))
781            {
782                if !self.validate_mutation(mutation, block_size, device_size) {
783                    info!(mutation:?; "Stopping replay at bad mutation");
784                    valid_to = checkpoint.file_offset;
785                    break 'bad_replay;
786                }
787                self.update_checksum_list(checkpoint.file_offset, &mutation, &mut checksum_list)?;
788            }
789        }
790
791        // Validate the checksums. Note if barriers are enabled, there will be no checksums in
792        // practice to verify.
793        let valid_to = checksum_list
794            .verify(device.as_ref(), valid_to)
795            .await
796            .context("Failed to validate checksums")?;
797
798        // Apply the mutations...
799
800        let mut last_checkpoint = reader.journal_file_checkpoint();
801        let mut journal_offsets = super_block.journal_file_offsets.clone();
802
803        // Start with the root-parent mutations, and also determine the journal offsets for all
804        // other objects.
805        for (
806            index,
807            JournaledTransaction {
808                checkpoint,
809                root_parent_mutations,
810                end_flush,
811                volume_deleted,
812                ..
813            },
814        ) in transactions.iter_mut().enumerate()
815        {
816            if checkpoint.file_offset >= valid_to {
817                last_checkpoint = checkpoint.clone();
818
819                // Truncate the transactions so we don't need to worry about them on the next pass.
820                transactions.truncate(index);
821                break;
822            }
823
824            let context = ApplyContext { mode: ApplyMode::Replay, checkpoint: checkpoint.clone() };
825            for mutation in root_parent_mutations.drain(..) {
826                self.objects
827                    .apply_mutation(
828                        super_block.root_parent_store_object_id,
829                        mutation,
830                        &context,
831                        AssocObj::None,
832                    )
833                    .context("Failed to replay root parent store mutations")?;
834            }
835
836            if let Some((object_id, journal_offset)) = end_flush {
837                journal_offsets.insert(*object_id, *journal_offset);
838            }
839
840            if let Some(object_id) = volume_deleted {
841                journal_offsets.insert(*object_id, VOLUME_DELETED);
842            }
843        }
844
845        // Now we can open the root store.
846        let root_store = ObjectStore::open(
847            &root_parent,
848            super_block.root_store_object_id,
849            Box::new(NullCache {}),
850        )
851        .await
852        .context("Unable to open root store")?;
853
854        ensure!(
855            !root_store.is_encrypted(),
856            anyhow!(FxfsError::Inconsistent).context("Root store is encrypted")
857        );
858        self.objects.set_root_store(root_store);
859
860        let root_store_offset =
861            journal_offsets.get(&super_block.root_store_object_id).copied().unwrap_or(0);
862
863        // Now replay the root store mutations.
864        for JournaledTransaction { checkpoint, root_mutations, .. } in &mut transactions {
865            if checkpoint.file_offset < root_store_offset {
866                continue;
867            }
868
869            let context = ApplyContext { mode: ApplyMode::Replay, checkpoint: checkpoint.clone() };
870            for mutation in root_mutations.drain(..) {
871                self.objects
872                    .apply_mutation(
873                        super_block.root_store_object_id,
874                        mutation,
875                        &context,
876                        AssocObj::None,
877                    )
878                    .context("Failed to replay root store mutations")?;
879            }
880        }
881
882        // Now we can open the allocator.
883        allocator.open().await.context("Failed to open allocator")?;
884
885        // Now replay all other mutations.
886        for JournaledTransaction { checkpoint, non_root_mutations, end_offset, .. } in transactions
887        {
888            self.objects
889                .replay_mutations(
890                    non_root_mutations,
891                    &journal_offsets,
892                    &ApplyContext { mode: ApplyMode::Replay, checkpoint },
893                    end_offset,
894                )
895                .await
896                .context("Failed to replay mutations")?;
897        }
898
899        allocator.on_replay_complete().await.context("Failed to complete replay for allocator")?;
900
901        let discarded_to =
902            if last_checkpoint.file_offset != reader.journal_file_checkpoint().file_offset {
903                Some(reader.journal_file_checkpoint().file_offset)
904            } else {
905                None
906            };
907
908        // Configure the journal writer so that we can continue.
909        {
910            if last_checkpoint.file_offset < super_block.super_block_journal_file_offset {
911                return Err(anyhow!(FxfsError::Inconsistent).context(format!(
912                    "journal replay cut short; journal finishes at {}, but super-block was \
913                     written at {}",
914                    last_checkpoint.file_offset, super_block.super_block_journal_file_offset
915                )));
916            }
917            let handle = ObjectStore::open_object(
918                &root_parent,
919                super_block.journal_object_id,
920                journal_handle_options(),
921                None,
922            )
923            .await
924            .with_context(|| {
925                format!(
926                    "Failed to open journal file (object id: {})",
927                    super_block.journal_object_id
928                )
929            })?;
930            let _ = self.handle.set(handle);
931            let mut inner = self.inner.lock();
932            reader.skip_to_end_of_block();
933            let mut writer_checkpoint = reader.journal_file_checkpoint();
934
935            // Make sure we don't accidentally use the reader from now onwards.
936            std::mem::drop(reader);
937
938            // Reset the stream to indicate that we've remounted the journal.
939            writer_checkpoint.checksum ^= RESET_XOR;
940            writer_checkpoint.version = LATEST_VERSION;
941            inner.flushed_offset = writer_checkpoint.file_offset;
942
943            // When we open the filesystem as writable, we flush the device.
944            inner.device_flushed_offset = inner.flushed_offset;
945
946            inner.writer.seek(writer_checkpoint);
947            inner.output_reset_version = true;
948            inner.valid_to = last_checkpoint.file_offset;
949            if last_checkpoint.file_offset < inner.flushed_offset {
950                inner.discard_offset = Some(last_checkpoint.file_offset);
951            }
952        }
953
954        self.objects
955            .on_replay_complete()
956            .await
957            .context("Failed to complete replay for object manager")?;
958
959        info!(checkpoint = last_checkpoint.file_offset, discarded_to; "replay complete");
960        Ok(())
961    }
962
963    async fn read_transactions(
964        &self,
965        reader: &mut JournalReader,
966        end_offset: Option<u64>,
967        object_id_filter: u64,
968    ) -> Result<JournaledTransactions, Error> {
969        let mut transactions = Vec::new();
970        let (mut device_flushed_offset, root_parent_store_object_id, root_store_object_id) = {
971            let super_block = &self.inner.lock().super_block_header;
972            (
973                super_block.super_block_journal_file_offset,
974                super_block.root_parent_store_object_id,
975                super_block.root_store_object_id,
976            )
977        };
978        let mut current_transaction = None;
979        let mut begin_flush_offsets = HashMap::default();
980        let mut stores_deleted = HashSet::new();
981        loop {
982            // Cache the checkpoint before we deserialize a record.
983            let checkpoint = reader.journal_file_checkpoint();
984            if let Some(end_offset) = end_offset {
985                if checkpoint.file_offset >= end_offset {
986                    break;
987                }
988            }
989            let result =
990                reader.deserialize().await.context("Failed to deserialize journal record")?;
991            match result {
992                ReadResult::Reset(_) => {
993                    if current_transaction.is_some() {
994                        current_transaction = None;
995                        transactions.pop();
996                    }
997                    let offset = reader.journal_file_checkpoint().file_offset;
998                    if offset > device_flushed_offset {
999                        device_flushed_offset = offset;
1000                    }
1001                }
1002                ReadResult::Some(record) => {
1003                    match record {
1004                        JournalRecord::EndBlock => {
1005                            reader.skip_to_end_of_block();
1006                        }
1007                        JournalRecord::Mutation { object_id, mutation } => {
1008                            let current_transaction = match current_transaction.as_mut() {
1009                                None => {
1010                                    transactions.push(JournaledTransaction::new(checkpoint));
1011                                    current_transaction = transactions.last_mut();
1012                                    current_transaction.as_mut().unwrap()
1013                                }
1014                                Some(transaction) => transaction,
1015                            };
1016
1017                            if stores_deleted.contains(&object_id) {
1018                                bail!(
1019                                    anyhow!(FxfsError::Inconsistent)
1020                                        .context("Encountered mutations for deleted store")
1021                                );
1022                            }
1023
1024                            match &mutation {
1025                                Mutation::BeginFlush => {
1026                                    begin_flush_offsets.insert(
1027                                        object_id,
1028                                        current_transaction.checkpoint.file_offset,
1029                                    );
1030                                }
1031                                Mutation::EndFlush => {
1032                                    if let Some(offset) = begin_flush_offsets.remove(&object_id) {
1033                                        if let Some(deleted_volume) =
1034                                            &current_transaction.volume_deleted
1035                                        {
1036                                            if *deleted_volume == object_id {
1037                                                bail!(anyhow!(FxfsError::Inconsistent).context(
1038                                                    "Multiple EndFlush/DeleteVolume mutations in a \
1039                                                    single transaction for the same object"
1040                                                ));
1041                                            }
1042                                        }
1043                                        // The +1 is because we don't want to replay the transaction
1044                                        // containing the begin flush; we don't need or want to
1045                                        // replay it.
1046                                        if current_transaction
1047                                            .end_flush
1048                                            .replace((object_id, offset + 1))
1049                                            .is_some()
1050                                        {
1051                                            bail!(anyhow!(FxfsError::Inconsistent).context(
1052                                                "Multiple EndFlush mutations in a \
1053                                                 single transaction"
1054                                            ));
1055                                        }
1056                                    }
1057                                }
1058                                Mutation::DeleteVolume => {
1059                                    if let Some((flushed_object, _)) =
1060                                        &current_transaction.end_flush
1061                                    {
1062                                        if *flushed_object == object_id {
1063                                            bail!(anyhow!(FxfsError::Inconsistent).context(
1064                                                "Multiple EndFlush/DeleteVolume mutations in a \
1065                                                    single transaction for the same object"
1066                                            ));
1067                                        }
1068                                    }
1069                                    if current_transaction
1070                                        .volume_deleted
1071                                        .replace(object_id)
1072                                        .is_some()
1073                                    {
1074                                        bail!(anyhow!(FxfsError::Inconsistent).context(
1075                                            "Multiple DeleteVolume mutations in a single \
1076                                             transaction"
1077                                        ));
1078                                    }
1079                                    stores_deleted.insert(object_id);
1080                                }
1081                                _ => {}
1082                            }
1083
1084                            // If this mutation doesn't need to be applied, don't bother adding it
1085                            // to the transaction.
1086                            if (object_id_filter == INVALID_OBJECT_ID
1087                                || object_id_filter == object_id)
1088                                && self.should_apply(object_id, &current_transaction.checkpoint)
1089                            {
1090                                if object_id == root_parent_store_object_id {
1091                                    current_transaction.root_parent_mutations.push(mutation);
1092                                } else if object_id == root_store_object_id {
1093                                    current_transaction.root_mutations.push(mutation);
1094                                } else {
1095                                    current_transaction
1096                                        .non_root_mutations
1097                                        .push((object_id, mutation));
1098                                }
1099                            }
1100                        }
1101                        JournalRecord::DataChecksums(device_range, checksums, first_write) => {
1102                            let current_transaction = match current_transaction.as_mut() {
1103                                None => {
1104                                    transactions.push(JournaledTransaction::new(checkpoint));
1105                                    current_transaction = transactions.last_mut();
1106                                    current_transaction.as_mut().unwrap()
1107                                }
1108                                Some(transaction) => transaction,
1109                            };
1110                            current_transaction.checksums.push(JournaledChecksums {
1111                                device_range,
1112                                checksums,
1113                                first_write,
1114                            });
1115                        }
1116                        JournalRecord::Commit => {
1117                            if let Some(&mut JournaledTransaction {
1118                                ref checkpoint,
1119                                ref root_parent_mutations,
1120                                ref mut end_offset,
1121                                ..
1122                            }) = current_transaction.take()
1123                            {
1124                                for mutation in root_parent_mutations {
1125                                    // Snoop the mutations for any that might apply to the journal
1126                                    // file so that we can pass them to the reader so that it can
1127                                    // read the journal file.
1128                                    if let Mutation::ObjectStore(ObjectStoreMutation {
1129                                        item:
1130                                            Item {
1131                                                key:
1132                                                    ObjectKey {
1133                                                        object_id,
1134                                                        data:
1135                                                            ObjectKeyData::Attribute(
1136                                                                AttributeId::DATA,
1137                                                                AttributeKey::Extent(extent),
1138                                                            ),
1139                                                        ..
1140                                                    },
1141                                                value:
1142                                                    ObjectValue::Extent(ExtentValue::Some {
1143                                                        device_offset,
1144                                                        ..
1145                                                    }),
1146                                                ..
1147                                            },
1148                                        ..
1149                                    }) = mutation
1150                                    {
1151                                        // Add the journal extents we find on the way to our
1152                                        // reader.
1153                                        let handle = reader.handle();
1154                                        if *object_id != handle.object_id() {
1155                                            continue;
1156                                        }
1157                                        if let Some(end_offset) = handle.end_offset() {
1158                                            if extent.start != end_offset {
1159                                                bail!(anyhow!(FxfsError::Inconsistent).context(
1160                                                    format!(
1161                                                        "Unexpected journal extent {:?} -> {}, \
1162                                                           expected start: {}",
1163                                                        *extent, device_offset, end_offset,
1164                                                    )
1165                                                ));
1166                                            }
1167                                        }
1168                                        handle.push_extent(
1169                                            checkpoint.file_offset,
1170                                            *device_offset
1171                                                ..*device_offset
1172                                                    + extent.length().context("Invalid extent")?,
1173                                        );
1174                                    }
1175                                }
1176                                *end_offset = reader.journal_file_checkpoint().file_offset;
1177                            }
1178                        }
1179                        JournalRecord::Discard(offset) => {
1180                            if offset == 0 {
1181                                bail!(
1182                                    anyhow!(FxfsError::Inconsistent)
1183                                        .context("Invalid offset for Discard")
1184                                );
1185                            }
1186                            if let Some(transaction) = current_transaction.as_ref() {
1187                                if transaction.checkpoint.file_offset < offset {
1188                                    // Odd, but OK.
1189                                    continue;
1190                                }
1191                            }
1192                            current_transaction = None;
1193                            while let Some(transaction) = transactions.last() {
1194                                if transaction.checkpoint.file_offset < offset {
1195                                    break;
1196                                }
1197                                transactions.pop();
1198                            }
1199                            reader.handle().discard_extents(offset);
1200                        }
1201                        JournalRecord::DidFlushDevice(offset) => {
1202                            if offset > device_flushed_offset {
1203                                device_flushed_offset = offset;
1204                            }
1205                        }
1206                    }
1207                }
1208                // This is expected when we reach the end of the journal stream.
1209                ReadResult::ChecksumMismatch => break,
1210            }
1211        }
1212
1213        // Discard any uncommitted transaction.
1214        if current_transaction.is_some() {
1215            transactions.pop();
1216        }
1217
1218        Ok(JournaledTransactions { transactions, device_flushed_offset })
1219    }
1220
1221    /// Creates an empty filesystem with the minimum viable objects (including a root parent and
1222    /// root store but no further child stores).
1223    pub async fn init_empty(&self, filesystem: Arc<FxFilesystem>) -> Result<(), Error> {
1224        // The following constants are only used at format time. When mounting, the recorded values
1225        // in the superblock should be used.  The root parent store does not have a parent, but
1226        // needs an object ID to be registered with ObjectManager, so it cannot collide (i.e. have
1227        // the same object ID) with any objects in the root store that use the journal to track
1228        // mutations.
1229        const INIT_ROOT_PARENT_STORE_OBJECT_ID: u64 = 3;
1230        const INIT_ROOT_STORE_OBJECT_ID: u64 = 4;
1231        const INIT_ALLOCATOR_OBJECT_ID: u64 = 5;
1232
1233        info!(device_size = filesystem.device().size(); "Formatting");
1234
1235        let checkpoint = JournalCheckpoint {
1236            version: LATEST_VERSION,
1237            ..self.inner.lock().writer.journal_file_checkpoint()
1238        };
1239
1240        let mut current_generation = 1;
1241        if filesystem.options().image_builder_mode.is_some() {
1242            // Note that in non-image_builder_mode we write both superblocks when we format
1243            // (in FxFilesystemBuilder::open). In image_builder_mode we only write once at the end
1244            // as part of finalize(), which is why we must make sure the generation we write is
1245            // newer than any existing generation.
1246
1247            // Note: This should is the *filesystem* block size, not the device block size which
1248            // is currently always 4096 (https://fxbug.dev/42063349)
1249            let block_size = filesystem.block_size();
1250            match self.read_superblocks(filesystem.device(), block_size).await {
1251                Ok((super_block, _)) => {
1252                    log::info!(
1253                        "Found existing superblock with generation {}. Bumping by 1.",
1254                        super_block.generation
1255                    );
1256                    current_generation = super_block.generation.wrapping_add(1);
1257                }
1258                Err(_) => {
1259                    // TODO(https://fxbug.dev/463757813): It's not unusual to fail to read
1260                    // superblocks when we're formatting a new filesystem but we should probably
1261                    // fail the format if we get an IO error.
1262                }
1263            }
1264        }
1265
1266        let root_parent = ObjectStore::new_empty(
1267            None,
1268            INIT_ROOT_PARENT_STORE_OBJECT_ID,
1269            filesystem.clone(),
1270            Box::new(NullCache {}),
1271        );
1272        self.objects.set_root_parent_store(root_parent.clone());
1273
1274        let allocator = Arc::new(Allocator::new(filesystem.clone(), INIT_ALLOCATOR_OBJECT_ID));
1275        self.objects.set_allocator(allocator.clone());
1276        self.objects.init_metadata_reservation()?;
1277
1278        let journal_handle;
1279        let super_block_a_handle;
1280        let super_block_b_handle;
1281        let root_store;
1282        let mut transaction = root_parent
1283            .new_transaction(
1284                lock_keys![],
1285                Options { skip_journal_checks: true, ..Default::default() },
1286            )
1287            .await?;
1288        root_store = root_parent
1289            .new_child_store(
1290                &mut transaction,
1291                NewChildStoreOptions { object_id: INIT_ROOT_STORE_OBJECT_ID, ..Default::default() },
1292                Box::new(NullCache {}),
1293            )
1294            .await
1295            .context("new_child_store")?;
1296        self.objects.set_root_store(root_store.clone());
1297
1298        allocator.create(&mut transaction).await?;
1299
1300        // Create the super-block objects...
1301        super_block_a_handle = ObjectStore::create_object_with_id(
1302            &root_store,
1303            &mut transaction,
1304            ReservedId::new(&root_store, NonZero::new(SuperBlockInstance::A.object_id()).unwrap()),
1305            HandleOptions::default(),
1306            None,
1307        )
1308        .context("create super block")?;
1309        root_store.update_last_object_id(SuperBlockInstance::A.object_id());
1310        super_block_a_handle
1311            .extend(&mut transaction, SuperBlockInstance::A.first_extent())
1312            .await
1313            .context("extend super block")?;
1314        super_block_b_handle = ObjectStore::create_object_with_id(
1315            &root_store,
1316            &mut transaction,
1317            ReservedId::new(&root_store, NonZero::new(SuperBlockInstance::B.object_id()).unwrap()),
1318            HandleOptions::default(),
1319            None,
1320        )
1321        .context("create super block")?;
1322        root_store.update_last_object_id(SuperBlockInstance::B.object_id());
1323        super_block_b_handle
1324            .extend(&mut transaction, SuperBlockInstance::B.first_extent())
1325            .await
1326            .context("extend super block")?;
1327
1328        // the journal object...
1329        journal_handle = ObjectStore::create_object(
1330            &root_parent,
1331            &mut transaction,
1332            journal_handle_options(),
1333            None,
1334        )
1335        .await
1336        .context("create journal")?;
1337        if self.inner.lock().image_builder_mode.is_none() {
1338            let mut file_range = 0..self.chunk_size();
1339            journal_handle
1340                .preallocate_range(&mut transaction, &mut file_range)
1341                .await
1342                .context("preallocate journal")?;
1343            if file_range.start < file_range.end {
1344                bail!("preallocate_range returned too little space");
1345            }
1346        }
1347
1348        // Write the root store object info.
1349        root_store.create(&mut transaction).await?;
1350
1351        // The root parent graveyard.
1352        root_parent.set_graveyard_directory_object_id(
1353            Graveyard::create(&mut transaction, &root_parent).await?,
1354        );
1355
1356        transaction.commit().await?;
1357
1358        self.inner.lock().super_block_header = SuperBlockHeader::new(
1359            current_generation,
1360            root_parent.store_object_id(),
1361            root_parent.graveyard_directory_object_id(),
1362            root_store.store_object_id(),
1363            allocator.object_id(),
1364            journal_handle.object_id(),
1365            checkpoint,
1366            /* earliest_version: */ LATEST_VERSION,
1367        );
1368
1369        // Initialize the journal writer.
1370        let _ = self.handle.set(journal_handle);
1371        Ok(())
1372    }
1373
1374    /// Normally we allocate the journal when creating the filesystem.
1375    /// This is used image_builder_mode when journal allocation is done last.
1376    pub async fn allocate_journal(&self) -> Result<(), Error> {
1377        let handle = self.handle.get().unwrap();
1378        let mut transaction = handle
1379            .store()
1380            .new_transaction(
1381                lock_keys![LockKey::object(handle.store().store_object_id(), handle.object_id()),],
1382                Options { skip_journal_checks: true, ..Default::default() },
1383            )
1384            .await?;
1385        let mut file_range = 0..self.chunk_size();
1386        self.handle
1387            .get()
1388            .unwrap()
1389            .preallocate_range(&mut transaction, &mut file_range)
1390            .await
1391            .context("preallocate journal")?;
1392        if file_range.start < file_range.end {
1393            bail!("preallocate_range returned too little space");
1394        }
1395        transaction.commit().await?;
1396        Ok(())
1397    }
1398
1399    pub async fn init_superblocks(&self) -> Result<(), Error> {
1400        // Overwrite both superblocks.
1401        for _ in 0..2 {
1402            self.write_super_block().await?;
1403        }
1404        Ok(())
1405    }
1406
1407    /// Takes a snapshot of all journaled transactions which affect |object_id| since its last
1408    /// flush.
1409    /// The caller is responsible for locking; it must ensure that the journal is not trimmed during
1410    /// this call.  For example, a Flush lock could be held on the object in question (assuming that
1411    /// object has data to flush and is registered with ObjectManager).
1412    pub async fn read_transactions_for_object(
1413        &self,
1414        object_id: u64,
1415    ) -> Result<Vec<JournaledTransaction>, Error> {
1416        let handle = self.handle.get().expect("No journal handle");
1417        // Reopen the handle since JournalReader needs an owned handle.
1418        let handle = ObjectStore::open_object(
1419            handle.owner(),
1420            handle.object_id(),
1421            journal_handle_options(),
1422            None,
1423        )
1424        .await?;
1425
1426        let checkpoint = match self.objects.journal_checkpoint(object_id) {
1427            Some(checkpoint) => checkpoint,
1428            None => return Ok(vec![]),
1429        };
1430        let mut reader = JournalReader::new(handle, &checkpoint);
1431        // Record the current end offset and only read to there, so we don't accidentally read any
1432        // partially flushed blocks.
1433        let end_offset = self.inner.lock().valid_to;
1434        Ok(self.read_transactions(&mut reader, Some(end_offset), object_id).await?.transactions)
1435    }
1436
1437    /// Commits a transaction.  This is not thread safe; the caller must take appropriate locks.
1438    pub async fn commit(&self, transaction: &mut Transaction<'_>) -> Result<u64, Error> {
1439        if transaction.is_empty() {
1440            return Ok(self.inner.lock().writer.journal_file_checkpoint().file_offset);
1441        }
1442
1443        self.pre_commit(transaction).await?;
1444        Ok(self.write_and_apply_mutations(transaction))
1445    }
1446
1447    // Before we commit, we might need to extend the journal or write pending records to the
1448    // journal.
1449    async fn pre_commit(&self, _transaction: &Transaction<'_>) -> Result<(), Error> {
1450        let handle;
1451
1452        let (size, zero_offset) = {
1453            let mut inner = self.inner.lock();
1454
1455            // If this is the first write after a RESET, we need to output version first.
1456            if std::mem::take(&mut inner.output_reset_version) {
1457                LATEST_VERSION.serialize_into(&mut inner.writer)?;
1458            }
1459
1460            if let Some(discard_offset) = inner.discard_offset {
1461                JournalRecord::Discard(discard_offset).serialize_into(&mut inner.writer)?;
1462                inner.discard_offset = None;
1463            }
1464
1465            if inner.needs_did_flush_device {
1466                let offset = inner.device_flushed_offset;
1467                JournalRecord::DidFlushDevice(offset).serialize_into(&mut inner.writer)?;
1468                inner.needs_did_flush_device = false;
1469            }
1470
1471            handle = match self.handle.get() {
1472                None => return Ok(()),
1473                Some(x) => x,
1474            };
1475
1476            let file_offset = inner.writer.journal_file_checkpoint().file_offset;
1477
1478            let size = handle.get_size();
1479            let size = if file_offset + self.chunk_size() > size { Some(size) } else { None };
1480
1481            if size.is_none()
1482                && inner.zero_offset.is_none()
1483                && !self.objects.needs_borrow_for_journal(file_offset)
1484            {
1485                return Ok(());
1486            }
1487
1488            (size, inner.zero_offset)
1489        };
1490
1491        let mut transaction = handle
1492            .new_transaction_with_options(Options {
1493                skip_journal_checks: true,
1494                borrow_metadata_space: true,
1495                allocator_reservation: Some(self.objects.metadata_reservation()),
1496                ..Default::default()
1497            })
1498            .await?;
1499        if let Some(size) = size {
1500            handle
1501                .preallocate_range(&mut transaction, &mut (size..size + self.chunk_size()))
1502                .await?;
1503        }
1504        if let Some(zero_offset) = zero_offset {
1505            handle.zero(&mut transaction, 0..zero_offset).await?;
1506        }
1507
1508        // We can't use regular transaction commit, because that can cause re-entrancy issues, so
1509        // instead we just apply the transaction directly here.
1510        self.write_and_apply_mutations(&mut transaction);
1511
1512        let mut inner = self.inner.lock();
1513
1514        // Make sure the transaction to extend the journal made it to the journal within the old
1515        // size, since otherwise, it won't be possible to replay.
1516        if let Some(size) = size {
1517            assert!(inner.writer.journal_file_checkpoint().file_offset < size);
1518        }
1519
1520        if inner.zero_offset == zero_offset {
1521            inner.zero_offset = None;
1522        }
1523
1524        Ok(())
1525    }
1526
1527    // Determines whether a mutation at the given checkpoint should be applied.  During replay, not
1528    // all records should be applied because the object store or allocator might already contain the
1529    // mutation.  After replay, that obviously isn't the case and we want to apply all mutations.
1530    fn should_apply(&self, object_id: u64, journal_file_checkpoint: &JournalCheckpoint) -> bool {
1531        let super_block_header = &self.inner.lock().super_block_header;
1532        let offset = super_block_header
1533            .journal_file_offsets
1534            .get(&object_id)
1535            .cloned()
1536            .unwrap_or(super_block_header.super_block_journal_file_offset);
1537        journal_file_checkpoint.file_offset >= offset
1538    }
1539
1540    /// Flushes previous writes to the device and then writes out a new super-block.
1541    /// Callers must ensure that we do not make concurrent calls.
1542    async fn write_super_block(&self) -> Result<(), Error> {
1543        let root_parent_store = self.objects.root_parent_store();
1544
1545        // We need to flush previous writes to the device since the new super-block we are writing
1546        // relies on written data being observable, and we also need to lock the root parent store
1547        // so that no new entries are written to it whilst we are writing the super-block, and for
1548        // that we use the write lock.
1549        let old_layers;
1550        let old_super_block_offset;
1551        let mut new_super_block_header;
1552        let checkpoint;
1553        let borrowed;
1554
1555        {
1556            let _sync_guard = debug_assert_not_too_long!(self.sync_mutex.lock());
1557            {
1558                let _write_guard = self.writer_mutex.lock();
1559                (checkpoint, borrowed) = self.pad_to_block()?;
1560                old_layers = super_block::compact_root_parent(&*root_parent_store)?;
1561            }
1562            self.flush_device(checkpoint.file_offset)
1563                .await
1564                .context("flush failed when writing superblock")?;
1565        }
1566
1567        new_super_block_header = self.inner.lock().super_block_header.clone();
1568
1569        old_super_block_offset = new_super_block_header.journal_checkpoint.file_offset;
1570
1571        let (journal_file_offsets, min_checkpoint) = self.objects.journal_file_offsets();
1572
1573        new_super_block_header.generation = new_super_block_header.generation.wrapping_add(1);
1574        new_super_block_header.super_block_journal_file_offset = checkpoint.file_offset;
1575        new_super_block_header.journal_checkpoint = min_checkpoint.unwrap_or(checkpoint);
1576        new_super_block_header.journal_checkpoint.version = LATEST_VERSION;
1577        new_super_block_header.journal_file_offsets = journal_file_offsets;
1578        new_super_block_header.borrowed_metadata_space = borrowed;
1579
1580        self.super_block_manager
1581            .save(
1582                new_super_block_header.clone(),
1583                self.objects.root_parent_store().filesystem(),
1584                old_layers,
1585            )
1586            .await?;
1587        {
1588            let mut inner = self.inner.lock();
1589            inner.super_block_header = new_super_block_header;
1590            inner.zero_offset = Some(round_down(old_super_block_offset, BLOCK_SIZE));
1591        }
1592
1593        Ok(())
1594    }
1595
1596    /// Flushes any buffered journal data to the device.  Note that this does not flush the device
1597    /// unless the flush_device option is set, in which case data should have been persisted to
1598    /// lower layers.  If a precondition is supplied, it is evaluated and the sync will be skipped
1599    /// if it returns false.  This allows callers to check a condition whilst a lock is held.  If a
1600    /// sync is performed, this function returns the checkpoint that was flushed and the amount of
1601    /// borrowed metadata space at the point it was flushed.
1602    pub async fn sync(
1603        &self,
1604        options: SyncOptions<'_>,
1605    ) -> Result<Option<(JournalCheckpoint, u64)>, Error> {
1606        let _guard = debug_assert_not_too_long!(self.sync_mutex.lock());
1607
1608        let (checkpoint, borrowed) = {
1609            if let Some(precondition) = options.precondition {
1610                if !precondition() {
1611                    return Ok(None);
1612                }
1613            }
1614
1615            // This guard is required so that we don't insert an EndBlock record in the middle of a
1616            // transaction.
1617            let _guard = self.writer_mutex.lock();
1618
1619            self.pad_to_block()?
1620        };
1621
1622        if options.flush_device {
1623            self.flush_device(checkpoint.file_offset).await.context("sync: flush failed")?;
1624        }
1625
1626        Ok(Some((checkpoint, borrowed)))
1627    }
1628
1629    // Returns the checkpoint as it was prior to padding.  This is done because the super block
1630    // needs to record where the last transaction ends and it's the next transaction that pays the
1631    // price of the padding.
1632    fn pad_to_block(&self) -> Result<(JournalCheckpoint, u64), Error> {
1633        let mut inner = self.inner.lock();
1634        let checkpoint = inner.writer.journal_file_checkpoint();
1635        if checkpoint.file_offset % BLOCK_SIZE != 0 {
1636            JournalRecord::EndBlock.serialize_into(&mut inner.writer)?;
1637            inner.writer.pad_to_block()?;
1638            if let Some(waker) = inner.flush_waker.take() {
1639                waker.wake();
1640            }
1641        }
1642        Ok((checkpoint, self.objects.borrowed_metadata_space()))
1643    }
1644
1645    async fn flush_device(&self, checkpoint_offset: u64) -> Result<(), Error> {
1646        assert!(
1647            self.inner.lock().image_builder_mode.is_none(),
1648            "flush_device called in image builder mode"
1649        );
1650        debug_assert_not_too_long!(poll_fn(|ctx| {
1651            let mut inner = self.inner.lock();
1652            if inner.flushed_offset >= checkpoint_offset {
1653                Poll::Ready(Ok(()))
1654            } else if inner.terminate {
1655                let context = inner
1656                    .terminate_reason
1657                    .as_ref()
1658                    .map(|e| format!("Journal closed with error: {:?}", e))
1659                    .unwrap_or_else(|| "Journal closed".to_string());
1660                Poll::Ready(Err(anyhow!(FxfsError::JournalFlushError).context(context)))
1661            } else {
1662                inner.sync_waker = Some(ctx.waker().clone());
1663                Poll::Pending
1664            }
1665        }))?;
1666
1667        let needs_flush = self.inner.lock().device_flushed_offset < checkpoint_offset;
1668        if needs_flush {
1669            let trace = self.trace.load(Ordering::Relaxed);
1670            if trace {
1671                info!("J: start flush device");
1672            }
1673            self.handle.get().unwrap().flush_device().await?;
1674            if trace {
1675                info!("J: end flush device");
1676            }
1677
1678            // We need to write a DidFlushDevice record at some point, but if we are in the
1679            // process of shutting down the filesystem, we want to leave the journal clean to
1680            // avoid there being log messages complaining about unwritten journal data, so we
1681            // queue it up so that the next transaction will trigger this record to be written.
1682            // If we are shutting down, that will never happen but since the DidFlushDevice
1683            // message is purely advisory (it reduces the number of checksums we have to verify
1684            // during replay), it doesn't matter if it isn't written.
1685            {
1686                let mut inner = self.inner.lock();
1687                inner.device_flushed_offset = checkpoint_offset;
1688                inner.needs_did_flush_device = true;
1689            }
1690
1691            // Tell the allocator that we flushed the device so that it can now start using
1692            // space that was deallocated.
1693            self.objects.allocator().did_flush_device(checkpoint_offset);
1694            if trace {
1695                info!("J: did flush device");
1696            }
1697        }
1698
1699        Ok(())
1700    }
1701
1702    /// Returns a copy of the super-block header.
1703    pub fn super_block_header(&self) -> SuperBlockHeader {
1704        self.inner.lock().super_block_header.clone()
1705    }
1706
1707    /// Waits for there to be sufficient space in the journal.
1708    pub async fn check_journal_space(&self) -> Result<(), Error> {
1709        loop {
1710            debug_assert_not_too_long!({
1711                let inner = self.inner.lock();
1712                if inner.terminate {
1713                    // If the flush error is set, this will never make progress, since we can't
1714                    // extend the journal any more.
1715                    let context = inner
1716                        .terminate_reason
1717                        .as_ref()
1718                        .map(|e| format!("Journal closed with error: {:?}", e))
1719                        .unwrap_or_else(|| "Journal closed".to_string());
1720                    break Err(anyhow!(FxfsError::JournalFlushError).context(context));
1721                }
1722                if self.objects.last_end_offset()
1723                    - inner.super_block_header.journal_checkpoint.file_offset
1724                    < inner.reclaim_size
1725                {
1726                    break Ok(());
1727                }
1728                if inner.image_builder_mode.is_some() {
1729                    break Ok(());
1730                }
1731                if inner.disable_compactions {
1732                    break Err(
1733                        anyhow!(FxfsError::JournalFlushError).context("Compactions disabled")
1734                    );
1735                }
1736                self.reclaim_event.listen()
1737            });
1738        }
1739    }
1740
1741    fn chunk_size(&self) -> u64 {
1742        CHUNK_SIZE
1743    }
1744
1745    fn write_and_apply_mutations(&self, transaction: &mut Transaction<'_>) -> u64 {
1746        let checkpoint_before;
1747        let checkpoint_after;
1748        {
1749            let _guard = self.writer_mutex.lock();
1750            checkpoint_before = {
1751                let mut inner = self.inner.lock();
1752                if transaction.includes_write() {
1753                    inner.needs_barrier = true;
1754                }
1755                let checkpoint = inner.writer.journal_file_checkpoint();
1756                for TxnMutation { object_id, mutation, .. } in transaction.mutations() {
1757                    self.objects.write_mutation(
1758                        *object_id,
1759                        mutation,
1760                        Writer(*object_id, &mut inner.writer),
1761                    );
1762                }
1763                checkpoint
1764            };
1765            let maybe_mutation =
1766                self.objects.apply_transaction(transaction, &checkpoint_before).expect(
1767                    "apply_transaction should not fail in live mode; \
1768                     filesystem will be in an inconsistent state",
1769                );
1770            checkpoint_after = {
1771                let mut inner = self.inner.lock();
1772                if let Some(mutation) = maybe_mutation {
1773                    inner
1774                        .writer
1775                        .write_record(&JournalRecord::Mutation { object_id: 0, mutation })
1776                        .unwrap();
1777                }
1778                for (device_range, checksums, first_write) in
1779                    transaction.take_checksums().into_iter()
1780                {
1781                    inner
1782                        .writer
1783                        .write_record(&JournalRecord::DataChecksums(
1784                            device_range,
1785                            Checksums::fletcher(checksums),
1786                            first_write,
1787                        ))
1788                        .unwrap();
1789                }
1790                inner.writer.write_record(&JournalRecord::Commit).unwrap();
1791
1792                inner.writer.journal_file_checkpoint()
1793            };
1794        }
1795        self.objects.did_commit_transaction(
1796            transaction,
1797            &checkpoint_before,
1798            checkpoint_after.file_offset,
1799        );
1800
1801        if let Some(waker) = self.inner.lock().flush_waker.take() {
1802            waker.wake();
1803        }
1804
1805        checkpoint_before.file_offset
1806    }
1807
1808    /// This task will flush journal data to the device when there is data that needs flushing, and
1809    /// trigger compactions when short of journal space.  It will return after the terminate method
1810    /// has been called, or an error is encountered with either flushing or compaction.
1811    pub async fn flush_task(self: Arc<Self>) {
1812        let mut flush_fut = None;
1813        let mut compact_fut = None;
1814        let mut flush_error = false;
1815        poll_fn(|ctx| {
1816            loop {
1817                {
1818                    let mut inner = self.inner.lock();
1819                    if flush_fut.is_none() && !flush_error && self.handle.get().is_some() {
1820                        let flushable = inner.writer.flushable_bytes();
1821                        if flushable > 0 {
1822                            flush_fut = Some(Box::pin(self.flush(flushable)));
1823                        }
1824                    }
1825                    if inner.terminate && flush_fut.is_none() && compact_fut.is_none() {
1826                        return Poll::Ready(());
1827                    }
1828                    // `journal_bytes` refers to bytes in the journal that haven't yet been flushed
1829                    // to the layer files. It increases with each transaction and decreases when
1830                    // compactions complete. The flush task is woken whenever a transaction is
1831                    // committed and we should see this metric updated regularly.
1832                    let journal_bytes = self.objects.last_end_offset()
1833                        - inner.super_block_header.journal_checkpoint.file_offset;
1834                    fxfs_trace::counter!("journal-bytes", 0, "total" => journal_bytes);
1835                    // The / 2 is here because after compacting, we cannot reclaim the space until
1836                    // the _next_ time we flush the device since the super-block is not guaranteed
1837                    // to persist until then.
1838                    if compact_fut.is_none()
1839                        && !inner.terminate
1840                        && !inner.disable_compactions
1841                        && inner.image_builder_mode.is_none()
1842                        && journal_bytes > inner.reclaim_size / 2
1843                    {
1844                        compact_fut = Some(Box::pin(self.compact()));
1845                        inner.compaction_running = true;
1846                    }
1847                    inner.flush_waker = Some(ctx.waker().clone());
1848                }
1849                let mut pending = true;
1850                if let Some(fut) = flush_fut.as_mut() {
1851                    if let Poll::Ready(result) = fut.poll_unpin(ctx) {
1852                        if let Err(e) = result {
1853                            self.inner.lock().terminate(Some(e.context("Flush error")));
1854                            self.reclaim_event.notify(usize::MAX);
1855                            flush_error = true;
1856                        }
1857                        flush_fut = None;
1858                        pending = false;
1859                    }
1860                }
1861                if let Some(fut) = compact_fut.as_mut() {
1862                    if let Poll::Ready(result) = fut.poll_unpin(ctx) {
1863                        let mut inner = self.inner.lock();
1864                        if let Err(e) = result {
1865                            inner.terminate(Some(e.context("Compaction error")));
1866                        }
1867                        compact_fut = None;
1868                        inner.compaction_running = false;
1869                        self.reclaim_event.notify(usize::MAX);
1870                        pending = false;
1871                        fxfs_trace::counter!(
1872                            "journal-bytes",
1873                            0,
1874                            "total" => self.objects.last_end_offset()
1875                                - inner.super_block_header.journal_checkpoint.file_offset
1876                        );
1877                    }
1878                }
1879                if pending {
1880                    return Poll::Pending;
1881                }
1882            }
1883        })
1884        .await;
1885    }
1886
1887    /// Returns a yielder that can be used for compactions.
1888    pub fn get_compaction_yielder(&self) -> CompactionYielder<'_> {
1889        CompactionYielder::new(self)
1890    }
1891
1892    async fn flush(&self, amount: usize) -> Result<(), Error> {
1893        let handle = self.handle.get().unwrap();
1894        let mut buf = handle.allocate_buffer(amount).await;
1895        let (offset, len, barrier_on_first_write) = {
1896            let mut inner = self.inner.lock();
1897            let offset = inner.writer.take_flushable(buf.as_mut());
1898            let barrier_on_first_write = inner.needs_barrier && inner.barriers_enabled;
1899            // Reset `needs_barrier` before instead of after the overwrite in case a txn commit
1900            // that contains data happens during the overwrite.
1901            inner.needs_barrier = false;
1902            (offset, buf.len() as u64, barrier_on_first_write)
1903        };
1904        self.handle
1905            .get()
1906            .unwrap()
1907            .overwrite(
1908                offset,
1909                buf.as_mut(),
1910                OverwriteOptions { barrier_on_first_write, ..Default::default() },
1911            )
1912            .await?;
1913
1914        let mut inner = self.inner.lock();
1915        if let Some(waker) = inner.sync_waker.take() {
1916            waker.wake();
1917        }
1918        inner.flushed_offset = offset + len;
1919        inner.valid_to = inner.flushed_offset;
1920        Ok(())
1921    }
1922
1923    #[trace]
1924    async fn compact(&self) -> Result<(), Error> {
1925        assert!(
1926            self.inner.lock().image_builder_mode.is_none(),
1927            "compact called in image builder mode"
1928        );
1929        let bytes_before = self.objects.compaction_bytes_written();
1930        let _measure = crate::metrics::DurationMeasureScope::new(
1931            &crate::metrics::lsm_tree_metrics().journal_compaction_time,
1932        );
1933        crate::metrics::lsm_tree_metrics().journal_compactions_total.add(1);
1934        let trace = self.trace.load(Ordering::Relaxed);
1935        debug!("Compaction starting");
1936        if trace {
1937            info!("J: start compaction");
1938        }
1939        let earliest_version = self.objects.flush().await.context("Failed to flush objects")?;
1940        self.inner.lock().super_block_header.earliest_version = earliest_version;
1941        self.write_super_block().await.context("Failed to write superblock")?;
1942        if trace {
1943            info!("J: end compaction");
1944        }
1945        debug!("Compaction finished");
1946        let bytes_after = self.objects.compaction_bytes_written();
1947        crate::metrics::lsm_tree_metrics()
1948            .journal_compaction_bytes_written
1949            .add(bytes_after.saturating_sub(bytes_before));
1950        Ok(())
1951    }
1952
1953    /// This should generally NOT be called externally. It is public to allow use by FIDL service
1954    /// fxfs.Debug.
1955    pub async fn force_compact(&self) -> Result<(), Error> {
1956        self.inner.lock().forced_compaction = true;
1957        scopeguard::defer! { self.inner.lock().forced_compaction = false; }
1958        self.compact().await
1959    }
1960
1961    pub async fn stop_compactions(&self) {
1962        loop {
1963            debug_assert_not_too_long!({
1964                let mut inner = self.inner.lock();
1965                inner.disable_compactions = true;
1966                if !inner.compaction_running {
1967                    return;
1968                }
1969                self.reclaim_event.listen()
1970            });
1971        }
1972    }
1973
1974    /// Creates a lazy inspect node named `str` under `parent` which will yield statistics for the
1975    /// journal when queried.
1976    pub fn track_statistics(self: &Arc<Self>, parent: &fuchsia_inspect::Node, name: &str) {
1977        let this = Arc::downgrade(self);
1978        parent.record_lazy_child(name, move || {
1979            let this_clone = this.clone();
1980            async move {
1981                let inspector = fuchsia_inspect::Inspector::default();
1982                if let Some(this) = this_clone.upgrade() {
1983                    let (journal_min, journal_max, journal_reclaim_size) = {
1984                        // TODO(https://fxbug.dev/42069513): Push-back or rate-limit to prevent DoS.
1985                        let inner = this.inner.lock();
1986                        (
1987                            round_down(
1988                                inner.super_block_header.journal_checkpoint.file_offset,
1989                                BLOCK_SIZE,
1990                            ),
1991                            inner.flushed_offset,
1992                            inner.reclaim_size,
1993                        )
1994                    };
1995                    let root = inspector.root();
1996                    root.record_uint("journal_min_offset", journal_min);
1997                    root.record_uint("journal_max_offset", journal_max);
1998                    root.record_uint("journal_size", journal_max - journal_min);
1999                    root.record_uint("journal_reclaim_size", journal_reclaim_size);
2000
2001                    // TODO(https://fxbug.dev/42068224): Post-compute rather than manually computing metrics.
2002                    if let Some(x) = round_div(
2003                        100 * (journal_max - journal_min),
2004                        this.objects.allocator().get_disk_bytes(),
2005                    ) {
2006                        root.record_uint("journal_size_to_disk_size_percent", x);
2007                    }
2008                }
2009                Ok(inspector)
2010            }
2011            .boxed()
2012        });
2013    }
2014
2015    /// Terminate all journal activity.
2016    pub fn terminate(&self) {
2017        self.inner.lock().terminate(/*reason*/ None);
2018        self.reclaim_event.notify(usize::MAX);
2019    }
2020}
2021
2022/// Wrapper to allow records to be written to the journal.
2023pub struct Writer<'a>(u64, &'a mut JournalWriter);
2024
2025impl Writer<'_> {
2026    pub fn write(&mut self, mutation: Mutation) {
2027        self.1.write_record(&JournalRecord::Mutation { object_id: self.0, mutation }).unwrap();
2028    }
2029}
2030
2031#[cfg(target_os = "fuchsia")]
2032mod yielder {
2033    use super::Journal;
2034    use crate::lsm_tree::Yielder;
2035    use fuchsia_async as fasync;
2036
2037    /// CompactionYielder uses fuchsia-async to yield if other tasks are being polled, which should
2038    /// be a proxy for how busy the system is.  We can afford to delay compactions for a small
2039    /// amount of time but not so long that we end up blocking new transactions.
2040    pub struct CompactionYielder<'a> {
2041        journal: &'a Journal,
2042        low_priority_task: Option<fasync::LowPriorityTask>,
2043    }
2044
2045    impl<'a> CompactionYielder<'a> {
2046        pub fn new(journal: &'a Journal) -> Self {
2047            Self { journal, low_priority_task: None }
2048        }
2049    }
2050
2051    impl Yielder for CompactionYielder<'_> {
2052        async fn yield_now(&mut self) {
2053            // We will wait for the executor to be idle for 4ms, but no longer than 16ms.  We need
2054            // to cap the maximum amount of time we wait in case we've reached a point where
2055            // compaction is now urgent or else we could block new transactions.
2056            const IDLE_PERIOD: zx::MonotonicDuration = zx::MonotonicDuration::from_millis(4);
2057            const MAX_YIELD_DURATION: zx::MonotonicDuration =
2058                zx::MonotonicDuration::from_millis(16);
2059
2060            {
2061                let inner = self.journal.inner.lock();
2062                if inner.forced_compaction {
2063                    return;
2064                }
2065                let outstanding = self.journal.objects.last_end_offset()
2066                    - inner.super_block_header.journal_checkpoint.file_offset;
2067                let half_reclaim_size = inner.reclaim_size / 2;
2068                if outstanding
2069                    .checked_sub(half_reclaim_size)
2070                    .is_some_and(|x| x >= half_reclaim_size / 2)
2071                {
2072                    // If we have got to the point where we have used up 3/4 of reclaim size in the
2073                    // journal, do not delay any further.  If we continue to yield we will get to
2074                    // the point where we block new transactions.
2075                    self.low_priority_task = None;
2076                    return;
2077                }
2078            }
2079
2080            self.low_priority_task
2081                .get_or_insert_with(|| fasync::LowPriorityTask::new())
2082                .wait_until_idle_for(
2083                    IDLE_PERIOD,
2084                    fasync::MonotonicInstant::after(MAX_YIELD_DURATION),
2085                )
2086                .await;
2087        }
2088    }
2089}
2090
2091#[cfg(not(target_os = "fuchsia"))]
2092mod yielder {
2093    use super::Journal;
2094    use crate::lsm_tree::Yielder;
2095
2096    #[expect(dead_code)]
2097    pub struct CompactionYielder<'a>(&'a Journal);
2098
2099    impl<'a> CompactionYielder<'a> {
2100        pub fn new(journal: &'a Journal) -> Self {
2101            Self(journal)
2102        }
2103    }
2104
2105    impl Yielder for CompactionYielder<'_> {
2106        async fn yield_now(&mut self) {}
2107    }
2108}
2109
2110pub use yielder::*;
2111
2112#[cfg(test)]
2113mod tests {
2114    use super::SuperBlockInstance;
2115    use crate::filesystem::{FxFilesystem, FxFilesystemBuilder, SyncOptions};
2116    use crate::fsck::fsck;
2117    use crate::object_handle::{ObjectHandle, ReadObjectHandle, WriteObjectHandle};
2118    use crate::object_store::directory::Directory;
2119    use crate::object_store::transaction::Options;
2120    use crate::object_store::volume::root_volume;
2121    use crate::object_store::{
2122        HandleOptions, LockKey, NewChildStoreOptions, ObjectStore, StoreOptions, lock_keys,
2123    };
2124    #[cfg(target_os = "fuchsia")]
2125    use fuchsia_async::TestExecutor;
2126    use fuchsia_async::{self as fasync, MonotonicDuration};
2127    use storage_device::DeviceHolder;
2128    use storage_device::fake_device::FakeDevice;
2129
2130    const TEST_DEVICE_BLOCK_SIZE: u32 = 512;
2131
2132    #[fuchsia::test]
2133    async fn test_replay() {
2134        const TEST_DATA: &[u8] = b"hello";
2135
2136        let device = DeviceHolder::new(FakeDevice::new(8192, TEST_DEVICE_BLOCK_SIZE));
2137
2138        let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
2139
2140        let object_id = {
2141            let root_store = fs.root_store();
2142            let root_directory =
2143                Directory::open(&root_store, root_store.root_directory_object_id())
2144                    .await
2145                    .expect("open failed");
2146            let mut transaction = fs
2147                .root_store()
2148                .new_transaction(
2149                    lock_keys![LockKey::object(
2150                        root_store.store_object_id(),
2151                        root_store.root_directory_object_id(),
2152                    )],
2153                    Options::default(),
2154                )
2155                .await
2156                .expect("new_transaction failed");
2157            let handle = root_directory
2158                .create_child_file(&mut transaction, "test")
2159                .await
2160                .expect("create_child_file failed");
2161
2162            transaction.commit().await.expect("commit failed");
2163            let mut buf = handle.allocate_buffer(TEST_DATA.len()).await;
2164            buf.as_mut_slice().copy_from_slice(TEST_DATA);
2165            handle.write_or_append(Some(0), buf.as_ref()).await.expect("write failed");
2166            // As this is the first sync, this will actually trigger a new super-block, but normally
2167            // this would not be the case.
2168            fs.sync(SyncOptions::default()).await.expect("sync failed");
2169            handle.object_id()
2170        };
2171
2172        {
2173            fs.close().await.expect("Close failed");
2174            let device = fs.take_device().await;
2175            device.reopen(false);
2176            let fs = FxFilesystem::open(device).await.expect("open failed");
2177            let handle = ObjectStore::open_object(
2178                &fs.root_store(),
2179                object_id,
2180                HandleOptions::default(),
2181                None,
2182            )
2183            .await
2184            .expect("open_object failed");
2185            let mut buf = handle.allocate_buffer(TEST_DEVICE_BLOCK_SIZE as usize).await;
2186            assert_eq!(handle.read(0, buf.as_mut()).await.expect("read failed"), TEST_DATA.len());
2187            assert_eq!(&buf.as_slice()[..TEST_DATA.len()], TEST_DATA);
2188            fsck(fs.clone()).await.expect("fsck failed");
2189            fs.close().await.expect("Close failed");
2190        }
2191    }
2192
2193    #[fuchsia::test]
2194    async fn test_reset() {
2195        const TEST_DATA: &[u8] = b"hello";
2196
2197        let device = DeviceHolder::new(FakeDevice::new(32768, TEST_DEVICE_BLOCK_SIZE));
2198
2199        let mut object_ids = Vec::new();
2200
2201        let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
2202        {
2203            let root_store = fs.root_store();
2204            let root_directory =
2205                Directory::open(&root_store, root_store.root_directory_object_id())
2206                    .await
2207                    .expect("open failed");
2208            let mut transaction = fs
2209                .root_store()
2210                .new_transaction(
2211                    lock_keys![LockKey::object(
2212                        root_store.store_object_id(),
2213                        root_store.root_directory_object_id(),
2214                    )],
2215                    Options::default(),
2216                )
2217                .await
2218                .expect("new_transaction failed");
2219            let handle = root_directory
2220                .create_child_file(&mut transaction, "test")
2221                .await
2222                .expect("create_child_file failed");
2223            transaction.commit().await.expect("commit failed");
2224            let mut buf = handle.allocate_buffer(TEST_DATA.len()).await;
2225            buf.as_mut_slice().copy_from_slice(TEST_DATA);
2226            handle.write_or_append(Some(0), buf.as_ref()).await.expect("write failed");
2227            fs.sync(SyncOptions::default()).await.expect("sync failed");
2228            object_ids.push(handle.object_id());
2229
2230            // Create a lot of objects but don't sync at the end. This should leave the filesystem
2231            // with a half finished transaction that cannot be replayed.
2232            for i in 0..1000 {
2233                let mut transaction = fs
2234                    .root_store()
2235                    .new_transaction(
2236                        lock_keys![LockKey::object(
2237                            root_store.store_object_id(),
2238                            root_store.root_directory_object_id(),
2239                        )],
2240                        Options::default(),
2241                    )
2242                    .await
2243                    .expect("new_transaction failed");
2244                let handle = root_directory
2245                    .create_child_file(&mut transaction, &format!("{}", i))
2246                    .await
2247                    .expect("create_child_file failed");
2248                transaction.commit().await.expect("commit failed");
2249                let mut buf = handle.allocate_buffer(TEST_DATA.len()).await;
2250                buf.as_mut_slice().copy_from_slice(TEST_DATA);
2251                handle.write_or_append(Some(0), buf.as_ref()).await.expect("write failed");
2252                object_ids.push(handle.object_id());
2253            }
2254        }
2255        fs.close().await.expect("fs close failed");
2256        let device = fs.take_device().await;
2257        device.reopen(false);
2258        let fs = FxFilesystem::open(device).await.expect("open failed");
2259        fsck(fs.clone()).await.expect("fsck failed");
2260        {
2261            let root_store = fs.root_store();
2262            // Check the first two objects which should exist.
2263            for &object_id in &object_ids[0..1] {
2264                let handle = ObjectStore::open_object(
2265                    &root_store,
2266                    object_id,
2267                    HandleOptions::default(),
2268                    None,
2269                )
2270                .await
2271                .expect("open_object failed");
2272                let mut buf = handle.allocate_buffer(TEST_DEVICE_BLOCK_SIZE as usize).await;
2273                assert_eq!(
2274                    handle.read(0, buf.as_mut()).await.expect("read failed"),
2275                    TEST_DATA.len()
2276                );
2277                assert_eq!(&buf.as_slice()[..TEST_DATA.len()], TEST_DATA);
2278            }
2279
2280            // Write one more object and sync.
2281            let root_directory =
2282                Directory::open(&root_store, root_store.root_directory_object_id())
2283                    .await
2284                    .expect("open failed");
2285            let mut transaction = fs
2286                .root_store()
2287                .new_transaction(
2288                    lock_keys![LockKey::object(
2289                        root_store.store_object_id(),
2290                        root_store.root_directory_object_id(),
2291                    )],
2292                    Options::default(),
2293                )
2294                .await
2295                .expect("new_transaction failed");
2296            let handle = root_directory
2297                .create_child_file(&mut transaction, "test2")
2298                .await
2299                .expect("create_child_file failed");
2300            transaction.commit().await.expect("commit failed");
2301            let mut buf = handle.allocate_buffer(TEST_DATA.len()).await;
2302            buf.as_mut_slice().copy_from_slice(TEST_DATA);
2303            handle.write_or_append(Some(0), buf.as_ref()).await.expect("write failed");
2304            fs.sync(SyncOptions::default()).await.expect("sync failed");
2305            object_ids.push(handle.object_id());
2306        }
2307
2308        fs.close().await.expect("close failed");
2309        let device = fs.take_device().await;
2310        device.reopen(false);
2311        let fs = FxFilesystem::open(device).await.expect("open failed");
2312        {
2313            fsck(fs.clone()).await.expect("fsck failed");
2314
2315            // Check the first two and the last objects.
2316            for &object_id in object_ids[0..1].iter().chain(object_ids.last().cloned().iter()) {
2317                let handle = ObjectStore::open_object(
2318                    &fs.root_store(),
2319                    object_id,
2320                    HandleOptions::default(),
2321                    None,
2322                )
2323                .await
2324                .unwrap_or_else(|e| {
2325                    panic!("open_object failed (object_id: {}): {:?}", object_id, e)
2326                });
2327                let mut buf = handle.allocate_buffer(TEST_DEVICE_BLOCK_SIZE as usize).await;
2328                assert_eq!(
2329                    handle.read(0, buf.as_mut()).await.expect("read failed"),
2330                    TEST_DATA.len()
2331                );
2332                assert_eq!(&buf.as_slice()[..TEST_DATA.len()], TEST_DATA);
2333            }
2334        }
2335        fs.close().await.expect("close failed");
2336    }
2337
2338    #[fuchsia::test]
2339    async fn test_discard() {
2340        let device = {
2341            let device = DeviceHolder::new(FakeDevice::new(8192, 4096));
2342            let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
2343            let root_volume = root_volume(fs.clone()).await.expect("root_volume failed");
2344
2345            let store = root_volume
2346                .new_volume("test", NewChildStoreOptions::default())
2347                .await
2348                .expect("new_volume failed");
2349            let root_directory = Directory::open(&store, store.root_directory_object_id())
2350                .await
2351                .expect("open failed");
2352
2353            // Create enough data so that another journal extent is used.
2354            let mut i = 0;
2355            loop {
2356                let mut transaction = fs
2357                    .root_store()
2358                    .new_transaction(
2359                        lock_keys![LockKey::object(
2360                            store.store_object_id(),
2361                            store.root_directory_object_id()
2362                        )],
2363                        Options::default(),
2364                    )
2365                    .await
2366                    .expect("new_transaction failed");
2367                root_directory
2368                    .create_child_file(&mut transaction, &format!("a {i}"))
2369                    .await
2370                    .expect("create_child_file failed");
2371                if transaction.commit().await.expect("commit failed") > super::CHUNK_SIZE {
2372                    break;
2373                }
2374                i += 1;
2375            }
2376
2377            // Compact and then disable compactions.
2378            fs.journal().force_compact().await.expect("compact failed");
2379            fs.journal().stop_compactions().await;
2380
2381            // Keep going until we need another journal extent.
2382            let mut i = 0;
2383            loop {
2384                let mut transaction = fs
2385                    .root_store()
2386                    .new_transaction(
2387                        lock_keys![LockKey::object(
2388                            store.store_object_id(),
2389                            store.root_directory_object_id()
2390                        )],
2391                        Options::default(),
2392                    )
2393                    .await
2394                    .expect("new_transaction failed");
2395                root_directory
2396                    .create_child_file(&mut transaction, &format!("b {i}"))
2397                    .await
2398                    .expect("create_child_file failed");
2399                if transaction.commit().await.expect("commit failed") > 2 * super::CHUNK_SIZE {
2400                    break;
2401                }
2402                i += 1;
2403            }
2404
2405            // Allow the journal to flush, but we don't want to sync.
2406            fasync::Timer::new(MonotonicDuration::from_millis(10)).await;
2407            // Because we're not gracefully closing the filesystem, a Discard record will be
2408            // emitted.
2409            fs.device().snapshot().expect("snapshot failed")
2410        };
2411
2412        let fs = FxFilesystem::open(device).await.expect("open failed");
2413
2414        {
2415            let root_volume = root_volume(fs.clone()).await.expect("root_volume failed");
2416
2417            let store =
2418                root_volume.volume("test", StoreOptions::default()).await.expect("volume failed");
2419
2420            let root_directory = Directory::open(&store, store.root_directory_object_id())
2421                .await
2422                .expect("open failed");
2423
2424            // Write one more transaction.
2425            let mut transaction = fs
2426                .root_store()
2427                .new_transaction(
2428                    lock_keys![LockKey::object(
2429                        store.store_object_id(),
2430                        store.root_directory_object_id()
2431                    )],
2432                    Options::default(),
2433                )
2434                .await
2435                .expect("new_transaction failed");
2436            root_directory
2437                .create_child_file(&mut transaction, &format!("d"))
2438                .await
2439                .expect("create_child_file failed");
2440            transaction.commit().await.expect("commit failed");
2441        }
2442
2443        fs.close().await.expect("close failed");
2444        let device = fs.take_device().await;
2445        device.reopen(false);
2446
2447        let fs = FxFilesystem::open(device).await.expect("open failed");
2448        fsck(fs.clone()).await.expect("fsck failed");
2449        fs.close().await.expect("close failed");
2450    }
2451
2452    #[fuchsia::test]
2453    async fn test_use_existing_generation() {
2454        let device = DeviceHolder::new(FakeDevice::new(8192, 4096));
2455
2456        // First format should be generation 1.
2457        let fs = FxFilesystemBuilder::new()
2458            .format(true)
2459            .image_builder_mode(Some(SuperBlockInstance::A))
2460            .open(device)
2461            .await
2462            .expect("open failed");
2463        fs.enable_allocations();
2464        let generation0 = fs.super_block_header().generation;
2465        assert_eq!(generation0, 1);
2466        fs.close().await.expect("close failed");
2467        let device = fs.take_device().await;
2468        device.reopen(false);
2469
2470        // Format the device normally (again, generation 1).
2471        let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
2472        let generation1 = fs.super_block_header().generation;
2473        {
2474            let root_volume = crate::object_store::volume::root_volume(fs.clone())
2475                .await
2476                .expect("root_volume failed");
2477            root_volume
2478                .new_volume("test", crate::object_store::NewChildStoreOptions::default())
2479                .await
2480                .expect("new_volume failed");
2481        }
2482        fs.close().await.expect("close failed");
2483        let device = fs.take_device().await;
2484        device.reopen(false);
2485
2486        // Format again with image_builder_mode.
2487        let fs = FxFilesystemBuilder::new()
2488            .format(true)
2489            .image_builder_mode(Some(SuperBlockInstance::A))
2490            .open(device)
2491            .await
2492            .expect("open failed");
2493        fs.enable_allocations();
2494        let generation2 = fs.super_block_header().generation;
2495        assert!(
2496            generation2 > generation1,
2497            "generation2 ({}) should be greater than generation1 ({})",
2498            generation2,
2499            generation1
2500        );
2501        fs.close().await.expect("close failed");
2502    }
2503
2504    #[fuchsia::test]
2505    async fn test_image_builder_mode_generation_bump_512_byte_block() {
2506        let device = DeviceHolder::new(FakeDevice::new(16384, 512));
2507
2508        // Format initial filesystem (generation 1)
2509        let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
2510        let generation1 = fs.super_block_header().generation;
2511        fs.close().await.expect("close failed");
2512        let device = fs.take_device().await;
2513        device.reopen(false);
2514
2515        // Format again with image_builder_mode (should bump generation)
2516        let fs = FxFilesystemBuilder::new()
2517            .format(true)
2518            .image_builder_mode(Some(SuperBlockInstance::A))
2519            .open(device)
2520            .await
2521            .expect("open failed");
2522
2523        fs.enable_allocations();
2524        let generation2 = fs.super_block_header().generation;
2525        assert!(
2526            generation2 > generation1,
2527            "Expected generation bump, got {} vs {}",
2528            generation2,
2529            generation1
2530        );
2531        fs.close().await.expect("close failed");
2532    }
2533
2534    #[fuchsia::test]
2535    #[cfg(target_os = "fuchsia")]
2536    fn test_low_priority_compaction() {
2537        let mut executor = TestExecutor::new_with_fake_time();
2538        let mut fut = std::pin::pin!(async {
2539            use std::sync::Arc;
2540            use std::sync::atomic::{AtomicBool, Ordering};
2541
2542            let device = DeviceHolder::new(FakeDevice::new(16384, TEST_DEVICE_BLOCK_SIZE));
2543            let fs = FxFilesystemBuilder::new()
2544                .journal_options(super::JournalOptions {
2545                    reclaim_size: 65536,
2546                    ..Default::default()
2547                })
2548                .format(true)
2549                .open(device)
2550                .await
2551                .expect("open failed");
2552
2553            let _low = fasync::LowPriorityTask::new();
2554
2555            // Add some data to the tree.
2556            {
2557                let root_store = fs.root_store();
2558                let root_directory =
2559                    Directory::open(&root_store, root_store.root_directory_object_id())
2560                        .await
2561                        .expect("open failed");
2562                for i in 0..100 {
2563                    let mut transaction = fs
2564                        .root_store()
2565                        .new_transaction(
2566                            lock_keys![LockKey::object(
2567                                root_store.store_object_id(),
2568                                root_store.root_directory_object_id(),
2569                            )],
2570                            Options::default(),
2571                        )
2572                        .await
2573                        .expect("new_transaction failed");
2574                    root_directory
2575                        .create_child_file(&mut transaction, &format!("test{}", i))
2576                        .await
2577                        .expect("create_child_file failed");
2578                    transaction.commit().await.expect("commit failed");
2579                }
2580            }
2581
2582            // Spawn a task that polls every 1ms.
2583            let stop = Arc::new(AtomicBool::new(false));
2584            let stop_clone = stop.clone();
2585            let _normal_task = fasync::Task::spawn(async move {
2586                while !stop_clone.load(Ordering::Relaxed) {
2587                    fasync::Timer::new(fasync::MonotonicInstant::after(
2588                        MonotonicDuration::from_millis(1),
2589                    ))
2590                    .await;
2591                }
2592            });
2593
2594            // Trigger journal compaction.
2595            // We can do this by writing more data until outstanding > reclaim_size / 2.
2596            {
2597                let root_store = fs.root_store();
2598                let root_directory =
2599                    Directory::open(&root_store, root_store.root_directory_object_id())
2600                        .await
2601                        .expect("open failed");
2602                let mut i = 0;
2603                loop {
2604                    let mut transaction = fs
2605                        .root_store()
2606                        .new_transaction(
2607                            lock_keys![LockKey::object(
2608                                root_store.store_object_id(),
2609                                root_store.root_directory_object_id(),
2610                            )],
2611                            Options::default(),
2612                        )
2613                        .await
2614                        .expect("new_transaction failed");
2615                    root_directory
2616                        .create_child_file(&mut transaction, &format!("trigger{i}"))
2617                        .await
2618                        .expect("create_child_file failed");
2619                    transaction.commit().await.expect("commit failed");
2620
2621                    if fs.journal().inner.lock().compaction_running {
2622                        break;
2623                    }
2624                    TestExecutor::advance_to(fasync::MonotonicInstant::after(
2625                        MonotonicDuration::from_millis(1),
2626                    ))
2627                    .await;
2628                    i += 1;
2629                }
2630            }
2631
2632            // Compaction should now be running. Because of our 1ms poller, it should be yielding.
2633            // It will yield for 16ms at each point.
2634            for _ in 0..10 {
2635                TestExecutor::advance_to(fasync::MonotonicInstant::after(
2636                    MonotonicDuration::from_millis(1),
2637                ))
2638                .await;
2639                assert!(fs.journal().inner.lock().compaction_running);
2640            }
2641
2642            // Stop the normal task.
2643            stop.store(true, Ordering::Relaxed);
2644            TestExecutor::advance_to(fasync::MonotonicInstant::after(
2645                MonotonicDuration::from_millis(1),
2646            ))
2647            .await;
2648
2649            // Compaction should still be running because it hasn't been 4 ms since the normal task
2650            // finished.
2651            assert!(fs.journal().inner.lock().compaction_running);
2652
2653            // For the next 3ms, compaction should still be running.
2654            for _ in 0..3 {
2655                TestExecutor::advance_to(fasync::MonotonicInstant::after(
2656                    MonotonicDuration::from_millis(1),
2657                ))
2658                .await;
2659                assert!(fs.journal().inner.lock().compaction_running);
2660            }
2661
2662            // 1 more ms and compaction should be unblocked.
2663            TestExecutor::advance_to(fasync::MonotonicInstant::after(
2664                MonotonicDuration::from_millis(1),
2665            ))
2666            .await;
2667
2668            // When the executor next stalls, compaction should be done.
2669            let _ = TestExecutor::poll_until_stalled(std::future::pending::<()>()).await;
2670            assert!(!fs.journal().inner.lock().compaction_running);
2671
2672            fs.close().await.expect("Close failed");
2673        });
2674        assert!(executor.run_until_stalled(&mut fut).is_ready());
2675    }
2676
2677    #[fuchsia::test]
2678    #[cfg(target_os = "fuchsia")]
2679    fn test_low_priority_compaction_deadline() {
2680        let mut executor = TestExecutor::new_with_fake_time();
2681        let mut fut = std::pin::pin!(async {
2682            use std::sync::Arc;
2683            use std::sync::atomic::{AtomicBool, Ordering};
2684
2685            let device = DeviceHolder::new(FakeDevice::new(216384, TEST_DEVICE_BLOCK_SIZE));
2686            let fs = FxFilesystemBuilder::new()
2687                .journal_options(super::JournalOptions {
2688                    reclaim_size: 65536,
2689                    ..Default::default()
2690                })
2691                .format(true)
2692                .open(device)
2693                .await
2694                .expect("open failed");
2695
2696            let _low = fasync::LowPriorityTask::new();
2697
2698            // Add some data to the tree.
2699            {
2700                let root_store = fs.root_store();
2701                let root_directory =
2702                    Directory::open(&root_store, root_store.root_directory_object_id())
2703                        .await
2704                        .expect("open failed");
2705                for i in 0..10 {
2706                    let mut transaction = fs
2707                        .root_store()
2708                        .new_transaction(
2709                            lock_keys![LockKey::object(
2710                                root_store.store_object_id(),
2711                                root_store.root_directory_object_id(),
2712                            )],
2713                            Options::default(),
2714                        )
2715                        .await
2716                        .expect("new_transaction failed");
2717                    root_directory
2718                        .create_child_file(&mut transaction, &format!("test{}", i))
2719                        .await
2720                        .expect("create_child_file failed");
2721                    transaction.commit().await.expect("commit failed");
2722                }
2723            }
2724
2725            // Spawn a task that polls every 3ms.
2726            let stop = Arc::new(AtomicBool::new(false));
2727            let stop_clone = stop.clone();
2728            let _normal_task = fasync::Task::spawn(async move {
2729                while !stop_clone.load(Ordering::Relaxed) {
2730                    fasync::Timer::new(fasync::MonotonicInstant::after(
2731                        MonotonicDuration::from_millis(3),
2732                    ))
2733                    .await;
2734                }
2735            });
2736
2737            // Trigger journal compaction.
2738            {
2739                let root_store = fs.root_store();
2740                let root_directory =
2741                    Directory::open(&root_store, root_store.root_directory_object_id())
2742                        .await
2743                        .expect("open failed");
2744                let mut i = 0;
2745                loop {
2746                    let mut transaction = fs
2747                        .root_store()
2748                        .new_transaction(
2749                            lock_keys![LockKey::object(
2750                                root_store.store_object_id(),
2751                                root_store.root_directory_object_id(),
2752                            )],
2753                            Options::default(),
2754                        )
2755                        .await
2756                        .expect("new_transaction failed");
2757                    root_directory
2758                        .create_child_file(&mut transaction, &format!("trigger{i}"))
2759                        .await
2760                        .expect("create_child_file failed");
2761                    transaction.commit().await.expect("commit failed");
2762
2763                    if fs.journal().inner.lock().compaction_running {
2764                        break;
2765                    }
2766                    TestExecutor::advance_to(fasync::MonotonicInstant::after(
2767                        MonotonicDuration::from_millis(1),
2768                    ))
2769                    .await;
2770                    i += 1;
2771                }
2772            }
2773
2774            // Advance time in 20ms increments. Each increment should allow compaction to make progress
2775            // on one item (since MAX_YIELD_DURATION is 16ms).
2776            let mut count = 0;
2777            for _ in 0..1000 {
2778                TestExecutor::advance_to(fasync::MonotonicInstant::after(
2779                    MonotonicDuration::from_millis(20),
2780                ))
2781                .await;
2782                if !fs.journal().inner.lock().compaction_running {
2783                    break;
2784                }
2785                count += 1;
2786            }
2787            assert!(!fs.journal().inner.lock().compaction_running);
2788
2789            // Make sure it took a few iterations to complete.  It's difficult to know what the exact
2790            // number should be.
2791            assert!(count > 200);
2792
2793            stop.store(true, Ordering::Relaxed);
2794            fs.close().await.expect("Close failed");
2795        });
2796        assert!(executor.run_until_stalled(&mut fut).is_ready());
2797    }
2798
2799    #[fuchsia::test]
2800    #[cfg(target_os = "fuchsia")]
2801    fn test_low_priority_compaction_no_yielding_when_full() {
2802        let mut executor = TestExecutor::new_with_fake_time();
2803        let mut fut = std::pin::pin!(async {
2804            use std::sync::Arc;
2805            use std::sync::atomic::{AtomicBool, Ordering};
2806
2807            let reclaim_size = 65536;
2808            let device = DeviceHolder::new(FakeDevice::new(216384, TEST_DEVICE_BLOCK_SIZE));
2809            let fs = FxFilesystemBuilder::new()
2810                .journal_options(super::JournalOptions { reclaim_size, ..Default::default() })
2811                .format(true)
2812                .open(device)
2813                .await
2814                .expect("open failed");
2815
2816            let _low = fasync::LowPriorityTask::new();
2817
2818            // Add some data to the tree.
2819            {
2820                let root_store = fs.root_store();
2821                let root_directory =
2822                    Directory::open(&root_store, root_store.root_directory_object_id())
2823                        .await
2824                        .expect("open failed");
2825                for i in 0..10 {
2826                    let mut transaction = fs
2827                        .root_store()
2828                        .new_transaction(
2829                            lock_keys![LockKey::object(
2830                                root_store.store_object_id(),
2831                                root_store.root_directory_object_id(),
2832                            )],
2833                            Options::default(),
2834                        )
2835                        .await
2836                        .expect("new_transaction failed");
2837                    root_directory
2838                        .create_child_file(&mut transaction, &format!("test{}", i))
2839                        .await
2840                        .expect("create_child_file failed");
2841                    transaction.commit().await.expect("commit failed");
2842                }
2843            }
2844
2845            // Spawn a task that polls every 3ms.
2846            let stop = Arc::new(AtomicBool::new(false));
2847            let stop_clone = stop.clone();
2848            let _normal_task = fasync::Task::spawn(async move {
2849                while !stop_clone.load(Ordering::Relaxed) {
2850                    fasync::Timer::new(fasync::MonotonicInstant::after(
2851                        MonotonicDuration::from_millis(3),
2852                    ))
2853                    .await;
2854                }
2855            });
2856
2857            // Trigger journal compaction, but this time we fill it up to 3/4 full.
2858            {
2859                let root_store = fs.root_store();
2860                let root_directory =
2861                    Directory::open(&root_store, root_store.root_directory_object_id())
2862                        .await
2863                        .expect("open failed");
2864                let mut i = 0;
2865                loop {
2866                    let mut transaction = fs
2867                        .root_store()
2868                        .new_transaction(
2869                            lock_keys![LockKey::object(
2870                                root_store.store_object_id(),
2871                                root_store.root_directory_object_id(),
2872                            )],
2873                            Options::default(),
2874                        )
2875                        .await
2876                        .expect("new_transaction failed");
2877                    root_directory
2878                        .create_child_file(&mut transaction, &format!("trigger{i}"))
2879                        .await
2880                        .expect("create_child_file failed");
2881                    transaction.commit().await.expect("commit failed");
2882
2883                    let outstanding = {
2884                        let inner = fs.journal().inner.lock();
2885                        fs.journal().objects.last_end_offset()
2886                            - inner.super_block_header.journal_checkpoint.file_offset
2887                    };
2888                    if outstanding >= reclaim_size * 7 / 8 {
2889                        break;
2890                    }
2891                    // We don't advance time here to try and reach 3/4 before compaction can yield.
2892                    i += 1;
2893                }
2894            }
2895
2896            // Advancing by 4ms should be enough to wake compaction up.
2897            TestExecutor::advance_to(fasync::MonotonicInstant::after(
2898                MonotonicDuration::from_millis(4),
2899            ))
2900            .await;
2901
2902            // When the executor next stalls, compaction should be done.
2903            let _ = TestExecutor::poll_until_stalled(std::future::pending::<()>()).await;
2904            assert!(!fs.journal().inner.lock().compaction_running);
2905
2906            stop.store(true, Ordering::Relaxed);
2907            fs.close().await.expect("Close failed");
2908        });
2909        assert!(executor.run_until_stalled(&mut fut).is_ready());
2910    }
2911}
2912
2913#[cfg(fuzz)]
2914mod fuzz {
2915    use fuzz::fuzz;
2916
2917    #[fuzz]
2918    fn fuzz_journal_bytes(input: Vec<u8>) {
2919        use crate::filesystem::FxFilesystem;
2920        use fuchsia_async as fasync;
2921        use std::io::Write;
2922        use storage_device::DeviceHolder;
2923        use storage_device::fake_device::FakeDevice;
2924
2925        fasync::SendExecutorBuilder::new().num_threads(4).build().run(async move {
2926            let device = DeviceHolder::new(FakeDevice::new(32768, 512));
2927            let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
2928            fs.journal().inner.lock().writer.write_all(&input).expect("write failed");
2929            fs.close().await.expect("close failed");
2930            let device = fs.take_device().await;
2931            device.reopen(false);
2932            if let Ok(fs) = FxFilesystem::open(device).await {
2933                // `close()` can fail if there were objects to be tombstoned. If the said object is
2934                // corrupted, there will be an error when we compact the journal.
2935                let _ = fs.close().await;
2936            }
2937        });
2938    }
2939
2940    #[fuzz]
2941    fn fuzz_journal(input: Vec<super::JournalRecord>) {
2942        use crate::filesystem::FxFilesystem;
2943        use fuchsia_async as fasync;
2944        use storage_device::DeviceHolder;
2945        use storage_device::fake_device::FakeDevice;
2946
2947        fasync::SendExecutorBuilder::new().num_threads(4).build().run(async move {
2948            let device = DeviceHolder::new(FakeDevice::new(32768, 512));
2949            let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
2950            {
2951                let mut inner = fs.journal().inner.lock();
2952                for record in &input {
2953                    let _ = inner.writer.write_record(record);
2954                }
2955            }
2956            fs.close().await.expect("close failed");
2957            let device = fs.take_device().await;
2958            device.reopen(false);
2959            if let Ok(fs) = FxFilesystem::open(device).await {
2960                // `close()` can fail if there were objects to be tombstoned. If the said object is
2961                // corrupted, there will be an error when we compact the journal.
2962                let _ = fs.close().await;
2963            }
2964        });
2965    }
2966}