Skip to main content

fxfs/
filesystem.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
5use crate::fsck::{FsckOptions, fsck_volume_with_options, fsck_with_options};
6use crate::hooks::HooksHandle;
7use crate::log::*;
8use crate::metrics;
9use crate::object_handle::LayerObject;
10use crate::object_store::allocator::{Allocator, Reservation};
11use crate::object_store::directory::Directory;
12use crate::object_store::graveyard::Graveyard;
13use crate::object_store::journal::super_block::{SuperBlockHeader, SuperBlockInstance};
14use crate::object_store::journal::{self, Journal, JournalCheckpoint, JournalOptions};
15use crate::object_store::object_manager::ObjectManager;
16use crate::object_store::transaction::{
17    self, AssocObj, LockKey, LockKeys, LockManager, Mutation, ObjectMutationIterator, Transaction,
18    TransactionConcurrencyLimiter, TransactionSink, WriteGuard, lock_keys,
19};
20use crate::object_store::volume::{VOLUMES_DIRECTORY, root_volume};
21use crate::object_store::{
22    AttributeId, DataObjectHandle, NewChildStoreOptions, ObjectStore, StoreOptions,
23};
24use crate::range::RangeExt;
25use crate::serialized_types::{LATEST_VERSION, Version};
26use anyhow::{Context, Error, anyhow, bail};
27use async_trait::async_trait;
28use fuchsia_async as fasync;
29use fuchsia_async::condition::Condition;
30use fuchsia_inspect::{Inspector, LazyNode, NumericProperty as _, UintProperty};
31use fuchsia_sync::Mutex;
32use futures::future::BoxFuture;
33use futures::stream::BoxStream;
34use futures::{FutureExt, Stream};
35use fxfs_crypto::{Crypt, UnwrappedKey};
36use fxfs_trace::{TraceFutureExt, trace_future_args};
37use static_assertions::const_assert;
38use std::pin::pin;
39use std::sync::atomic::{AtomicBool, Ordering};
40use std::sync::{Arc, OnceLock, Weak};
41use std::task::Poll;
42use std::time::{Duration, Instant};
43use storage_device::{Device, DeviceHolder};
44use storage_units::BlockSize;
45
46pub const MIN_BLOCK_SIZE: BlockSize = BlockSize::SIZE_4KIB;
47pub const MAX_BLOCK_SIZE: BlockSize = BlockSize::SIZE_64KIB;
48
49// Whilst Fxfs could support up to u64::MAX, off_t is i64 so allowing files larger than that becomes
50// difficult to deal with via the POSIX APIs. Additionally, PagedObjectHandle only sees data get
51// modified in page chunks so to prevent writes at i64::MAX the entire page containing i64::MAX
52// needs to be excluded.
53pub const MAX_FILE_SIZE: u64 = i64::MAX as u64 - 4095;
54const_assert!(9223372036854771712 == MAX_FILE_SIZE);
55
56use futures::stream::StreamExt;
57
58// The maximum number of transactions that can be in-flight at any time.
59pub(crate) const MAX_IN_FLIGHT_TRANSACTIONS: u64 = 4;
60
61// Start trimming 1 hour after boot.  The idea here is to wait until the initial flurry of
62// activity during boot is finished.  This is a rough heuristic and may need to change later if
63// performance is affected.
64const TRIM_AFTER_BOOT_TIMER: Duration = Duration::from_secs(60 * 60);
65
66// After the initial trim, perform another trim every 24 hours.
67const TRIM_INTERVAL_TIMER: Duration = Duration::from_secs(60 * 60 * 24);
68
69/// How often to clean the transfer buffer.
70// TODO(https://fxbug.dev/489725256) Configure the task to run when fxfs is idle.
71const CLEAN_TRANSFER_BUFFER_INTERVAL: Duration = Duration::from_secs(60);
72
73/// An opaque token representing an active wake lease. When held, it prevents the system from
74/// suspending. Dropping this token releases the lease.
75#[derive(Debug)]
76pub struct WakeLease {
77    #[cfg(target_os = "fuchsia")]
78    _lease: zx::Handle,
79}
80
81#[cfg(target_os = "fuchsia")]
82impl WakeLease {
83    pub fn new<H: TryInto<zx::Handle>>(lease: H) -> Self
84    where
85        H::Error: std::fmt::Debug,
86    {
87        Self { _lease: lease.try_into().expect("lease handle must be valid") }
88    }
89}
90
91pub trait PowerManager: Send + Sync {
92    /// Returns a stream of battery status changes (true if using battery).
93    fn watch_battery(self: Arc<Self>) -> BoxStream<'static, (bool, Option<WakeLease>)>;
94}
95
96/// Services registration of layer files with a platform-specific layer pager.
97#[async_trait]
98pub trait LayerPager: Send + Sync + 'static {
99    async fn open_layer(
100        &self,
101        handle: DataObjectHandle<ObjectStore>,
102        unwrapped_key: Option<UnwrappedKey>,
103    ) -> Result<Arc<dyn LayerObject>, Error>;
104}
105
106/// Holds information on an Fxfs Filesystem
107pub struct Info {
108    pub total_bytes: u64,
109    pub used_bytes: u64,
110}
111
112pub type PostCommitHook = Option<Box<dyn Fn() -> BoxFuture<'static, ()> + Send + Sync>>;
113
114pub struct Options {
115    /// True if the filesystem is read-only.
116    pub read_only: bool,
117
118    /// Hooks for filesystem events (e.g. pre_commit, before_commit).
119    pub hooks: Arc<HooksHandle>,
120
121    /// A callback that runs after every transaction has been committed.  This will be called whilst
122    /// a lock is held which will block more transactions from being committed.
123    pub post_commit_hook: PostCommitHook,
124
125    /// If true, don't do an initial reap of the graveyard at mount time.  This is useful for
126    /// testing.
127    pub skip_initial_reap: bool,
128
129    // The first duration is how long after the filesystem has been mounted to perform an initial
130    // trim.  The second is the interval to repeat trimming thereafter.  If set to None, no trimming
131    // is done.
132    // Default values are (5 minutes, 24 hours).
133    pub trim_config: Option<(Duration, Duration)>,
134
135    // If set, journal will not be used for writes. The user must call 'close' when finished.
136    // The provided superblock instance will be written upon close().
137    pub image_builder_mode: Option<SuperBlockInstance>,
138
139    // If true, the filesystem will use the hardware's inline crypto engine to write encrypted
140    // data. Requires the block device to support inline encryption and for `barriers_enabled` to
141    // be true.
142    // TODO(https://fxbug.dev/393196849): For now, this flag only prevents the filesystem from
143    // computing checksums. Update this comment when the filesystem actually uses inline
144    // encryption.
145    pub inline_crypto_enabled: bool,
146
147    // Configures the filesystem to use barriers instead of checksums to ensure consistency.
148    // Checksums may be computed and stored in extent records but will no longer be stored in the
149    // journal. The journal will use barriers to enforce proper ordering between data and metadata
150    // writes. Must be true if `inline_crypto_enabled` is true.
151    pub barriers_enabled: bool,
152
153    /// If set, this will be used to check for charger status before trimming.
154    pub power_manager: Option<Arc<dyn PowerManager>>,
155
156    /// How long to wait after being placed on a charger before starting a trim.
157    pub trim_charger_wait: Duration,
158
159    /// If true, allows writing Type 3 delivery blobs.
160    /// NOTE: Type 3 delivery blobs are currently UNSTABLE / EXPERIMENTAL and subject to change.
161    pub allow_type3_blobs: bool,
162}
163
164impl Default for Options {
165    fn default() -> Self {
166        Options {
167            read_only: false,
168            hooks: Arc::<HooksHandle>::default(),
169            post_commit_hook: None,
170            skip_initial_reap: false,
171            trim_config: Some((TRIM_AFTER_BOOT_TIMER, TRIM_INTERVAL_TIMER)),
172            image_builder_mode: None,
173            inline_crypto_enabled: false,
174            barriers_enabled: false,
175            power_manager: None,
176            trim_charger_wait: Duration::from_secs(10),
177            allow_type3_blobs: false,
178        }
179    }
180}
181
182/// The context in which a transaction is being applied.
183pub struct ApplyContext<'a, 'b> {
184    /// The mode indicates whether the transaction is being replayed.
185    pub mode: ApplyMode<'a, 'b>,
186
187    /// The transaction checkpoint for this mutation.
188    pub checkpoint: JournalCheckpoint,
189}
190
191/// A transaction can be applied during replay or on a live running system (in which case a
192/// transaction object will be available).
193pub enum ApplyMode<'a, 'b> {
194    Replay,
195    Live(&'a Transaction<'b>),
196}
197
198impl ApplyMode<'_, '_> {
199    pub fn is_replay(&self) -> bool {
200        matches!(self, ApplyMode::Replay)
201    }
202
203    pub fn is_live(&self) -> bool {
204        matches!(self, ApplyMode::Live(_))
205    }
206}
207
208#[derive(Copy, Clone, Debug, PartialEq, Eq)]
209pub enum ForceMajor {
210    True,
211    False,
212}
213
214#[derive(Copy, Clone, Debug, PartialEq, Eq)]
215pub enum FlushReason {
216    /// Journal memory or space pressure.
217    Journal(ForceMajor),
218
219    /// Clean up an encrypted mutations object after mount.
220    EncryptedMutations,
221
222    /// Upgrade old layer files to the latest version after mount. This performs a full compaction.
223    UpgradeVersion,
224}
225
226/// Objects that use journaling to track mutations (`Allocator` and `ObjectStore`) implement this.
227/// This is primarily used by `ObjectManager` and `SuperBlock` with flush calls used in a few tests.
228#[async_trait]
229pub trait JournalingObject: Send + Sync {
230    /// This method get called when the transaction commits, which can either be during live
231    /// operation (See `ObjectManager::apply_mutation`) or during journal replay, in which case
232    /// transaction will be None (See `super_block::read`).
233    fn apply_mutation(
234        &self,
235        mutation: Mutation,
236        context: &ApplyContext<'_, '_>,
237        assoc_obj: AssocObj<'_>,
238    ) -> Result<(), Error>;
239
240    /// Called when a transaction fails to commit.
241    fn drop_mutation(&self, mutation: Mutation, transaction: &Transaction<'_>);
242
243    /// Called before committing a transaction. Implementations can use this to acquire locks
244    /// or resources (like keys) that must be held until the transaction is committed.
245    async fn prepare_commit<'a>(
246        &self,
247        _filesystem: &'a FxFilesystem,
248        _transaction: &Transaction<'_>,
249    ) -> Result<Option<WriteGuard<'a>>, Error> {
250        Ok(None)
251    }
252
253    /// Flushes in-memory changes to the device (to allow journal space to be freed).
254    ///
255    /// Also returns the earliest version of a struct in the filesystem.
256    async fn flush(&self, reason: FlushReason) -> Result<Version, Error>;
257
258    /// Writes mutations to the journal.  This allows objects to encrypt or otherwise modify what
259    /// gets written to the journal.
260    fn write_mutations(
261        &self,
262        mutations: ObjectMutationIterator<'_, '_>,
263        mut writer: journal::Writer<'_>,
264    ) {
265        for mutation in mutations {
266            writer.write(mutation.clone());
267        }
268    }
269}
270
271#[derive(Default)]
272pub struct SyncOptions<'a> {
273    /// If set, the journal will be flushed, as well as the underlying block device.  This is much
274    /// more expensive, but ensures the contents of the journal are persisted (which also acts as a
275    /// barrier, ensuring all previous journal writes are observable by future operations).
276    /// Note that when this is not set, the journal is *not* synchronously flushed by the sync call,
277    /// and it will return before the journal flush completes.  In other words, some journal
278    /// mutations may still be buffered in memory after this call returns.
279    pub flush_device: bool,
280
281    /// A precondition that is evaluated whilst a lock is held that determines whether or not the
282    /// sync needs to proceed.
283    pub precondition: Option<Box<dyn FnOnce() -> bool + 'a + Send>>,
284}
285
286pub struct OpenFxFilesystem(Arc<FxFilesystem>);
287
288impl OpenFxFilesystem {
289    /// Waits for filesystem to be dropped (so callers should ensure all direct and indirect
290    /// references are dropped) and returns the device.  No attempt is made at a graceful shutdown.
291    pub async fn take_device(self) -> DeviceHolder {
292        let fut = self.device.take_when_dropped();
293        std::mem::drop(self);
294        debug_assert_not_too_long!(fut)
295    }
296}
297
298impl From<Arc<FxFilesystem>> for OpenFxFilesystem {
299    fn from(fs: Arc<FxFilesystem>) -> Self {
300        Self(fs)
301    }
302}
303
304impl Drop for OpenFxFilesystem {
305    fn drop(&mut self) {
306        if self.options.image_builder_mode.is_some()
307            && self.journal().image_builder_mode().is_some()
308        {
309            error!("OpenFxFilesystem in image_builder_mode dropped without calling close().");
310        }
311        if !self.options.read_only && !self.closed.load(Ordering::SeqCst) {
312            error!("OpenFxFilesystem dropped without first being closed. Data loss may occur.");
313        }
314    }
315}
316
317impl std::ops::Deref for OpenFxFilesystem {
318    type Target = Arc<FxFilesystem>;
319
320    fn deref(&self) -> &Self::Target {
321        &self.0
322    }
323}
324
325pub struct FxFilesystemBuilder {
326    format: bool,
327    trace: bool,
328    options: Options,
329    journal_options: JournalOptions,
330    on_new_allocator: Option<Box<dyn Fn(Arc<Allocator>) + Send + Sync>>,
331    on_new_store: Option<Box<dyn Fn(&ObjectStore) + Send + Sync>>,
332    fsck_after_every_transaction: bool,
333    layer_pager: Option<Arc<dyn LayerPager>>,
334}
335
336impl FxFilesystemBuilder {
337    pub fn new() -> Self {
338        Self {
339            format: false,
340            trace: false,
341            options: Options::default(),
342            journal_options: JournalOptions::default(),
343            on_new_allocator: None,
344            on_new_store: None,
345            fsck_after_every_transaction: false,
346            layer_pager: None,
347        }
348    }
349
350    /// Sets whether the block device should be formatted when opened. Defaults to `false`.
351    pub fn format(mut self, format: bool) -> Self {
352        self.format = format;
353        self
354    }
355
356    /// Enables or disables trace level logging. Defaults to `false`.
357    pub fn trace(mut self, trace: bool) -> Self {
358        self.trace = trace;
359        self
360    }
361
362    /// Sets whether the filesystem will be opened in read-only mode. Defaults to `false`.
363    /// Incompatible with `format`.
364    pub fn read_only(mut self, read_only: bool) -> Self {
365        self.options.read_only = read_only;
366        self
367    }
368
369    /// Sets whether Type 3 delivery blobs are allowed. Defaults to `false`.
370    pub fn allow_type3_blobs(mut self, allow: bool) -> Self {
371        self.options.allow_type3_blobs = allow;
372        self
373    }
374
375    /// For image building and in-place migration.
376    ///
377    /// This mode avoids the initial write of super blocks and skips the journal for all
378    /// transactions. The user *must* call `close()` before dropping the filesystem to trigger
379    /// a compaction of in-memory data structures, a minimal journal and a write to one
380    /// superblock (as specified).
381    pub fn image_builder_mode(mut self, mode: Option<SuperBlockInstance>) -> Self {
382        self.options.image_builder_mode = mode;
383        self
384    }
385
386    /// Sets a callback that runs after every transaction has been committed. See
387    /// `Options::post_commit_hook`.
388    pub fn post_commit_hook(
389        mut self,
390        hook: impl Fn() -> futures::future::BoxFuture<'static, ()> + Send + Sync + 'static,
391    ) -> Self {
392        self.options.post_commit_hook = Some(Box::new(hook));
393        self
394    }
395
396    /// Sets whether to do an initial reap of the graveyard at mount time. See
397    /// `Options::skip_initial_reap`. Defaults to `false`.
398    pub fn skip_initial_reap(mut self, skip_initial_reap: bool) -> Self {
399        self.options.skip_initial_reap = skip_initial_reap;
400        self
401    }
402
403    /// Sets the options for the journal.
404    pub fn journal_options(mut self, journal_options: JournalOptions) -> Self {
405        self.journal_options = journal_options;
406        self
407    }
408
409    /// Sets a method to be called immediately after creating the allocator.
410    pub fn on_new_allocator(
411        mut self,
412        on_new_allocator: impl Fn(Arc<Allocator>) + Send + Sync + 'static,
413    ) -> Self {
414        self.on_new_allocator = Some(Box::new(on_new_allocator));
415        self
416    }
417
418    /// Sets a method to be called each time a new store is registered with `ObjectManager`.
419    pub fn on_new_store(
420        mut self,
421        on_new_store: impl Fn(&ObjectStore) + Send + Sync + 'static,
422    ) -> Self {
423        self.on_new_store = Some(Box::new(on_new_store));
424        self
425    }
426
427    /// Enables or disables running fsck after every transaction. Defaults to `false`.
428    pub fn fsck_after_every_transaction(mut self, fsck_after_every_transaction: bool) -> Self {
429        self.fsck_after_every_transaction = fsck_after_every_transaction;
430        self
431    }
432
433    pub fn trim_config(mut self, delay_and_interval: Option<(Duration, Duration)>) -> Self {
434        self.options.trim_config = delay_and_interval;
435        self
436    }
437
438    pub fn power_manager(mut self, power_manager: Arc<dyn PowerManager>) -> Self {
439        self.options.power_manager = Some(power_manager);
440        self
441    }
442
443    pub fn trim_charger_wait(mut self, wait: Duration) -> Self {
444        self.options.trim_charger_wait = wait;
445        self
446    }
447
448    /// Enables or disables inline encryption. Defaults to `false`.
449    pub fn inline_crypto_enabled(mut self, inline_crypto_enabled: bool) -> Self {
450        self.options.inline_crypto_enabled = inline_crypto_enabled;
451        self
452    }
453
454    /// Enables or disables barriers in both the filesystem and journal options.
455    /// Defaults to `false`.
456    pub fn barriers_enabled(mut self, barriers_enabled: bool) -> Self {
457        self.options.barriers_enabled = barriers_enabled;
458        self.journal_options.barriers_enabled = barriers_enabled;
459        self
460    }
461
462    pub fn hooks(mut self, hooks: Arc<crate::hooks::HooksHandle>) -> Self {
463        self.options.hooks = hooks;
464        self
465    }
466
467    pub fn layer_pager(mut self, layer_pager: Option<Arc<dyn LayerPager>>) -> Self {
468        self.layer_pager = layer_pager;
469        self
470    }
471
472    /// Constructs an `FxFilesystem` object with the specified settings.
473    pub async fn open(self, device: DeviceHolder) -> Result<OpenFxFilesystem, Error> {
474        let read_only = self.options.read_only;
475        if self.format && read_only {
476            bail!("Cannot initialize a filesystem as read-only");
477        }
478
479        // Inline encryption requires barriers to be enabled.
480        if self.options.inline_crypto_enabled && !self.options.barriers_enabled {
481            bail!("A filesystem using inline encryption requires barriers");
482        }
483
484        let objects = Arc::new(ObjectManager::new(self.on_new_store));
485        let journal = Arc::new(Journal::new(
486            objects.clone(),
487            self.journal_options,
488            self.options.hooks.clone(),
489        ));
490
491        let image_builder_mode = self.options.image_builder_mode;
492
493        let device_block_size =
494            BlockSize::new(device.block_size()).expect("Device block size is not a power of 2");
495        let block_size = std::cmp::max(device_block_size, MIN_BLOCK_SIZE);
496        assert!(block_size <= MAX_BLOCK_SIZE, "Max supported block size is 64KiB");
497
498        let mut fsck_after_every_transaction = None;
499        let mut filesystem_options = self.options;
500        if self.fsck_after_every_transaction {
501            let instance =
502                FsckAfterEveryTransaction::new(filesystem_options.post_commit_hook.take());
503            fsck_after_every_transaction = Some(instance.clone());
504            filesystem_options.post_commit_hook =
505                Some(Box::new(move || Box::pin(instance.clone().run())));
506        }
507
508        if !read_only && !self.format {
509            // See comment in JournalRecord::DidFlushDevice for why we need to flush the device
510            // before replay.
511            device.flush().await.context("Device flush failed")?;
512        }
513
514        let filesystem = Arc::new_cyclic(|weak: &Weak<FxFilesystem>| {
515            let weak = weak.clone();
516            FxFilesystem {
517                device,
518                block_size,
519                objects: objects.clone(),
520                journal,
521                commit_mutex: futures::lock::Mutex::new(()),
522                lock_manager: LockManager::default(),
523                flush_task: Mutex::new(None),
524                background_tasks: fasync::Scope::new(),
525                closed: AtomicBool::new(true),
526                trace: self.trace,
527                graveyard: Graveyard::new(weak.clone()),
528                completed_transactions: metrics::detail().create_uint("completed_transactions", 0),
529                options: filesystem_options,
530                transaction_limiter: TransactionConcurrencyLimiter::new(MAX_IN_FLIGHT_TRANSACTIONS),
531                layer_pager: self.layer_pager,
532                _stores_node: metrics::register_fs(move || {
533                    let weak = weak.clone();
534                    Box::pin(async move {
535                        if let Some(fs) = weak.upgrade() {
536                            fs.populate_stores_node().await
537                        } else {
538                            Err(anyhow!("Filesystem has been dropped"))
539                        }
540                    })
541                }),
542            }
543        });
544
545        filesystem.journal().set_image_builder_mode(image_builder_mode);
546
547        filesystem.journal.set_trace(self.trace);
548        if self.format {
549            filesystem.journal.init_empty(filesystem.clone()).await?;
550            if image_builder_mode.is_none() {
551                // The filesystem isn't valid until superblocks are written but we want to defer
552                // that until last when migrating filesystems or building system images.
553                filesystem.journal.init_superblocks().await?;
554
555                // Start the graveyard's background reaping task.
556                filesystem.graveyard.clone().reap_async();
557            }
558
559            // Create the root volume directory.
560            let root_store = filesystem.root_store();
561            root_store.set_trace(self.trace);
562            let root_directory =
563                Directory::open(&root_store, root_store.root_directory_object_id())
564                    .await
565                    .context("Unable to open root volume directory")?;
566            let mut transaction = root_store
567                .new_transaction(
568                    lock_keys![LockKey::object(
569                        root_store.store_object_id(),
570                        root_directory.object_id()
571                    )],
572                    transaction::Options::default(),
573                )
574                .await?;
575            let volume_directory =
576                root_directory.create_child_dir(&mut transaction, VOLUMES_DIRECTORY).await?;
577            transaction.commit().await?;
578            objects.set_volume_directory(volume_directory);
579        } else {
580            filesystem
581                .journal
582                .replay(filesystem.clone(), self.on_new_allocator)
583                .await
584                .context("Journal replay failed")?;
585            filesystem.root_store().set_trace(self.trace);
586
587            if !read_only {
588                // Queue all purged entries for tombstoning.  Don't start the reaper yet because
589                // that can trigger a flush which can add more entries to the graveyard which might
590                // get caught in the initial reap and cause objects to be prematurely tombstoned.
591                for store in objects.unlocked_stores() {
592                    filesystem.graveyard.initial_reap(&store).await?;
593                }
594            }
595        }
596
597        // This must be after we've formatted the filesystem; it will fail during format otherwise.
598        if let Some(fsck_after_every_transaction) = fsck_after_every_transaction {
599            fsck_after_every_transaction
600                .fs
601                .set(Arc::downgrade(&filesystem))
602                .unwrap_or_else(|_| unreachable!());
603        }
604
605        filesystem.closed.store(false, Ordering::SeqCst);
606
607        if !read_only && image_builder_mode.is_none() {
608            // Start the background tasks.
609            filesystem.graveyard.clone().reap_async();
610
611            if filesystem.options.trim_config.is_some() {
612                filesystem.start_trim_task();
613            }
614            filesystem.start_clean_transfer_buffer_task();
615        }
616
617        Ok(filesystem.into())
618    }
619}
620
621pub struct FxFilesystem {
622    block_size: BlockSize,
623    objects: Arc<ObjectManager>,
624    journal: Arc<Journal>,
625    commit_mutex: futures::lock::Mutex<()>,
626    lock_manager: LockManager,
627    flush_task: Mutex<Option<fasync::Task<()>>>,
628    background_tasks: fasync::Scope,
629    closed: AtomicBool,
630    // An event that is signalled when the filesystem starts to shut down.
631    trace: bool,
632    graveyard: Arc<Graveyard>,
633    completed_transactions: UintProperty,
634    options: Options,
635
636    transaction_limiter: TransactionConcurrencyLimiter,
637
638    // The "stores" node in the Inspect tree.
639    _stores_node: LazyNode,
640
641    layer_pager: Option<Arc<dyn LayerPager>>,
642
643    // NOTE: This *must* go last so that when users take the device from a closed filesystem, the
644    // filesystem has dropped all other members first (Rust drops members in declaration order).
645    device: DeviceHolder,
646}
647
648#[fxfs_trace::trace]
649impl FxFilesystem {
650    pub async fn new_empty(device: DeviceHolder) -> Result<OpenFxFilesystem, Error> {
651        FxFilesystemBuilder::new().format(true).open(device).await
652    }
653
654    pub async fn open(device: DeviceHolder) -> Result<OpenFxFilesystem, Error> {
655        FxFilesystemBuilder::new().open(device).await
656    }
657
658    pub fn root_parent_store(&self) -> Arc<ObjectStore> {
659        self.objects.root_parent_store()
660    }
661
662    pub fn layer_pager(&self) -> Option<&Arc<dyn LayerPager>> {
663        self.layer_pager.as_ref()
664    }
665
666    pub async fn close(&self) -> Result<(), Error> {
667        if self.journal().image_builder_mode().is_some() {
668            self.journal().finalize_image_builder().await?;
669        }
670        assert_eq!(self.closed.swap(true, Ordering::SeqCst), false);
671        debug_assert_not_too_long!(self.graveyard.wait_for_reap());
672        debug_assert_not_too_long!(self.background_tasks.clone().cancel());
673        self.journal.stop_compactions().await;
674        let sync_status =
675            if self.journal().image_builder_mode().is_some() || self.options().read_only {
676                Ok(None)
677            } else {
678                self.journal.sync(SyncOptions { flush_device: true, ..Default::default() }).await
679            };
680        match &sync_status {
681            Ok(None) => {}
682            Ok(checkpoint) => info!(
683                "Filesystem closed (checkpoint={}, metadata_reservation={:?}, \
684                 reservation_required={}, borrowed={})",
685                checkpoint.as_ref().unwrap().file_offset,
686                self.object_manager().metadata_reservation(),
687                self.object_manager().required_reservation(),
688                self.object_manager().borrowed_metadata_space(),
689            ),
690            Err(e) => error!(error:? = e; "Failed to sync filesystem; data may be lost"),
691        }
692        self.journal.terminate();
693        let flush_task = self.flush_task.lock().take();
694        if let Some(task) = flush_task {
695            debug_assert_not_too_long!(task);
696        }
697        // Regardless of whether sync succeeds, we should close the device, since otherwise we will
698        // crash instead of exiting gracefully.
699        self.device().close().await.context("Failed to close device")?;
700        sync_status.map(|_| ())
701    }
702
703    pub fn device(&self) -> Arc<dyn Device> {
704        Arc::clone(&self.device)
705    }
706
707    pub fn root_store(&self) -> Arc<ObjectStore> {
708        self.objects.root_store()
709    }
710
711    pub fn allocator(&self) -> Arc<Allocator> {
712        self.objects.allocator()
713    }
714
715    /// Enables allocations for the allocator.
716    /// This is only used in image_builder_mode where it *must*
717    /// be called before any allocations can take place.
718    pub fn enable_allocations(&self) {
719        self.allocator().enable_allocations();
720    }
721
722    pub fn object_manager(&self) -> &Arc<ObjectManager> {
723        &self.objects
724    }
725
726    pub fn journal(&self) -> &Arc<Journal> {
727        &self.journal
728    }
729
730    pub async fn sync(&self, options: SyncOptions<'_>) -> Result<(), Error> {
731        self.journal.sync(options).await.map(|_| ())
732    }
733
734    pub fn block_size(&self) -> BlockSize {
735        self.block_size
736    }
737
738    pub fn get_info(&self) -> Info {
739        Info {
740            total_bytes: self.device.size(),
741            used_bytes: self.object_manager().allocator().get_used_bytes().0,
742        }
743    }
744
745    pub fn super_block_header(&self) -> SuperBlockHeader {
746        self.journal.super_block_header()
747    }
748
749    pub fn graveyard(&self) -> &Arc<Graveyard> {
750        &self.graveyard
751    }
752
753    pub fn trace(&self) -> bool {
754        self.trace
755    }
756
757    pub fn options(&self) -> &Options {
758        &self.options
759    }
760
761    pub fn scope(&self) -> &fasync::Scope {
762        &self.background_tasks
763    }
764
765    /// Returns a guard that must be taken before any transaction can commence.  This guard takes a
766    /// shared lock on the filesystem.  `fsck` will take an exclusive lock so that it can get a
767    /// consistent picture of the filesystem that it can verify.  It is important that this lock is
768    /// acquired before *all* other locks.  It is also important that this lock is not taken twice
769    /// by the same task since that can lead to deadlocks if another task tries to take a write
770    /// lock.
771    pub async fn lock_commits(&self) -> futures::lock::MutexGuard<'_, ()> {
772        self.commit_mutex.lock().await
773    }
774
775    #[trace]
776    pub async fn commit_transaction<R: Send>(
777        &self,
778        transaction: &mut Transaction<'_>,
779        callback: impl FnOnce(u64) -> R + Send,
780    ) -> Result<R, Error> {
781        self.hooks().on_pre_commit(transaction)?;
782        debug_assert_not_too_long!(self.lock_manager.commit_prepare(&transaction));
783
784        // Call prepare_commit on all unique objects involved in the transaction.
785        // We must hold the returned guards until the transaction is committed.
786        // Since transaction.mutations() is sorted by object_id, we can deduplicate
787        // on-the-fly.
788        let mut guards = Vec::new();
789        let mut last_object_id = 0;
790        for mutation in transaction.mutations() {
791            let object_id = mutation.object_id;
792
793            // We don't need to prepare commits (which reserves keys) for flush mutations.
794            if matches!(mutation.mutation, Mutation::BeginFlush | Mutation::EndFlush(_)) {
795                continue;
796            }
797
798            if object_id == last_object_id {
799                continue;
800            }
801            assert!(object_id > last_object_id);
802            last_object_id = object_id;
803
804            if let Some(obj) = self.object_manager().journaling_object(object_id) {
805                if let Some(guard) = obj.prepare_commit(self, transaction).await? {
806                    guards.push(guard);
807                }
808            }
809        }
810
811        self.maybe_start_flush_task();
812
813        self.hooks().on_before_commit();
814
815        let _guard = debug_assert_not_too_long!(self.commit_mutex.lock());
816        let journal_offset = if self.journal().image_builder_mode().is_some() {
817            let journal_checkpoint =
818                JournalCheckpoint { file_offset: 0, checksum: 0, version: LATEST_VERSION };
819            self.object_manager()
820                .apply_transaction(transaction, &journal_checkpoint)
821                .expect("Transactions must not fail in image_builder_mode");
822            self.object_manager().did_commit_transaction(transaction, 0, &[]);
823            0
824        } else {
825            self.journal.commit(transaction).await?
826        };
827
828        std::mem::drop(guards);
829        self.completed_transactions.add(1);
830
831        // For now, call the callback whilst holding the lock.  Technically, we don't need to do
832        // that except if there's a post-commit-hook (which there usually won't be).  We can
833        // consider changing this if we need to for performance, but we'd need to double check that
834        // callers don't depend on this.
835        let result = callback(journal_offset);
836
837        if let Some(hook) = self.options.post_commit_hook.as_ref() {
838            hook().await;
839        }
840
841        Ok(result)
842    }
843
844    pub fn lock_manager(&self) -> &LockManager {
845        &self.lock_manager
846    }
847
848    pub fn hooks(&self) -> &Arc<HooksHandle> {
849        &self.options.hooks
850    }
851
852    pub(crate) fn drop_transaction(&self, transaction: &mut Transaction<'_>) {
853        self.objects.drop_transaction(transaction);
854        self.lock_manager.drop_transaction(transaction);
855    }
856}
857
858#[async_trait]
859pub trait Filesystem: TransactionSink + Send + Sync + 'static {
860    /// Returns true if the filesystem is remote.  Some functionality is not supported in this case
861    /// (e.g. the allocator, graveyard, or journal).
862    fn is_remote(&self) -> bool {
863        true
864    }
865
866    /// Returns a reference to the local `FxFilesystem` instance.
867    ///
868    /// Panics if called on a remote filesystem.
869    fn ensure_local(&self) -> &FxFilesystem {
870        panic!("not a local filesystem");
871    }
872
873    /// Returns the layer pager if one is configured for this filesystem.
874    fn layer_pager(&self) -> Option<&Arc<dyn LayerPager>> {
875        None
876    }
877
878    /// Queues a graveyard object for tombstoning.
879    fn queue_tombstone_object(&self, store_object_id: u64, object_id: u64);
880
881    /// Tombstones a graveyard object synchronously.
882    async fn tombstone_object(
883        &self,
884        store_object_id: u64,
885        object_id: u64,
886        truncate_guard: Option<&TruncateGuard<'_>>,
887    ) -> Result<(), Error>;
888
889    /// Returns the options used to configure the filesystem.
890    fn options(&self) -> &Options;
891
892    /// Returns the block size of the underlying device.
893    fn block_size(&self) -> BlockSize;
894
895    /// Returns info about the filesystem.
896    fn get_info(&self) -> Info;
897
898    /// Syncs the filesystem.
899    async fn sync(&self, options: SyncOptions<'_>) -> Result<(), Error>;
900
901    /// Takes a truncate guard for the given store and object.
902    async fn truncate_guard(&self, store_id: u64, object_id: u64) -> TruncateGuard<'_> {
903        let keys = lock_keys![LockKey::truncate(store_id, object_id)];
904        TruncateGuard(self.lock_manager().write_lock(keys).await)
905    }
906
907    /// Returns the hooks handle for this filesystem.
908    fn hooks(&self) -> &Arc<HooksHandle> {
909        &self.options().hooks
910    }
911}
912
913impl dyn Filesystem {
914    pub async fn truncate_guard_owned(
915        self: &Arc<Self>,
916        store_id: u64,
917        object_id: u64,
918    ) -> TruncateGuard<'static> {
919        self.truncate_guard(store_id, object_id).await.into_owned(self.clone())
920    }
921}
922
923#[async_trait]
924impl TransactionSink for FxFilesystem {
925    async fn new_transaction<'a>(
926        self: Arc<Self>,
927        txn_locks: LockKeys,
928        options: transaction::Options<'a>,
929    ) -> Result<Transaction<'a>, Error> {
930        Transaction::new(self, options, txn_locks).await
931    }
932
933    async fn check_space(&self) -> Result<(), Error> {
934        if self.options.image_builder_mode.is_none() {
935            self.maybe_start_flush_task();
936            self.journal.check_journal_space().await?;
937        }
938        Ok(())
939    }
940
941    fn reserve(&self, owner_object_id: Option<u64>, amount: u64) -> Option<Reservation> {
942        self.allocator().reserve(owner_object_id, amount)
943    }
944
945    fn image_builder_mode(&self) -> bool {
946        self.options.image_builder_mode.is_some()
947    }
948
949    async fn commit(&self, transaction: &mut Transaction<'_>) -> Result<u64, Error> {
950        self.commit_transaction(transaction, |x| x).await
951    }
952
953    async fn commit_with_callback<'a>(
954        &self,
955        transaction: &mut Transaction<'_>,
956        callback: Box<dyn FnOnce(u64) + Send + 'a>,
957    ) -> Result<u64, Error> {
958        let mut journal_offset = 0;
959        self.commit_transaction(transaction, |offset| {
960            journal_offset = offset;
961            callback(offset);
962        })
963        .await?;
964        Ok(journal_offset)
965    }
966
967    fn downgrade_locks(&self, lock_keys: &LockKeys) {
968        self.lock_manager().downgrade_locks(lock_keys);
969    }
970
971    fn drop_transaction(&self, transaction: &mut Transaction<'_>) {
972        FxFilesystem::drop_transaction(self, transaction);
973    }
974
975    fn lock_manager(&self) -> &LockManager {
976        self.lock_manager()
977    }
978
979    fn transaction_limiter(&self) -> &TransactionConcurrencyLimiter {
980        &self.transaction_limiter
981    }
982
983    fn metadata_reservation(&self) -> Option<&Reservation> {
984        Some(self.object_manager().metadata_reservation())
985    }
986
987    fn is_system_store(&self, store_id: u64) -> bool {
988        self.object_manager().is_system_store(store_id)
989    }
990}
991
992#[async_trait]
993impl Filesystem for FxFilesystem {
994    fn is_remote(&self) -> bool {
995        false
996    }
997
998    fn ensure_local(&self) -> &FxFilesystem {
999        self
1000    }
1001
1002    fn layer_pager(&self) -> Option<&Arc<dyn LayerPager>> {
1003        self.layer_pager()
1004    }
1005
1006    fn queue_tombstone_object(&self, store_object_id: u64, object_id: u64) {
1007        self.graveyard().queue_tombstone_object(store_object_id, object_id);
1008    }
1009
1010    async fn tombstone_object(
1011        &self,
1012        store_object_id: u64,
1013        object_id: u64,
1014        truncate_guard: Option<&TruncateGuard<'_>>,
1015    ) -> Result<(), Error> {
1016        FxFilesystem::tombstone_object(self, store_object_id, object_id, truncate_guard).await
1017    }
1018
1019    fn block_size(&self) -> BlockSize {
1020        self.block_size()
1021    }
1022
1023    fn get_info(&self) -> Info {
1024        FxFilesystem::get_info(self)
1025    }
1026
1027    async fn sync(&self, options: SyncOptions<'_>) -> Result<(), Error> {
1028        FxFilesystem::sync(self, options).await
1029    }
1030
1031    fn options(&self) -> &Options {
1032        &self.options
1033    }
1034}
1035
1036impl FxFilesystem {
1037    fn maybe_start_flush_task(&self) {
1038        if self.journal.image_builder_mode().is_some() {
1039            return;
1040        }
1041        let mut flush_task = self.flush_task.lock();
1042        if flush_task.is_none() {
1043            let journal = self.journal.clone();
1044            *flush_task = Some(fasync::Task::spawn(
1045                journal.flush_task().trace(trace_future_args!("Journal::flush_task")),
1046            ));
1047        }
1048    }
1049
1050    fn start_trim_task(self: &Arc<Self>) {
1051        if !self.device.supports_trim() {
1052            info!("Device does not support trim; not scheduling trimming");
1053            return;
1054        }
1055        let this = self.clone();
1056        self.background_tasks
1057            .spawn(this.trim_task().trace(trace_future_args!("Filesystem::trim_task")));
1058    }
1059
1060    async fn trim_task(self: Arc<Self>) {
1061        // This task will be cancelled when the filesystem is closed.
1062        let Some((mut next_timer, _)) = self.options.trim_config else { return };
1063        loop {
1064            fasync::Timer::new(next_timer.clone()).await;
1065
1066            // The timer has fired indicating a trim is now due.  If we have a power manager, we
1067            // now check to see if there's an external power source.
1068            let start = Instant::now();
1069            let result = if let Some(pm) = &self.options.power_manager {
1070                let mut watcher = pm.clone().watch_battery();
1071
1072                // The pauser starts paused.
1073                let pauser = Pauser::new(self.options.trim_charger_wait);
1074
1075                let mut pause_future = pin!(
1076                    async {
1077                        let mut wake_lease: Option<WakeLease> = None;
1078                        loop {
1079                            let Some((using_battery, new_lease)) = watcher.next_latest().await
1080                            else {
1081                                // If we lose the connection to the watcher, unpause and do not
1082                                // worry about monitoring the power source.
1083                                pauser.set_pause(false);
1084                                drop(wake_lease); // Silence the compiler warnings.
1085                                return;
1086                            };
1087
1088                            // Pause if the device is using battery.
1089                            pauser.set_pause(using_battery);
1090
1091                            // Hold onto a wake lease if we are using an external power source (and
1092                            // we are therefore unpaused).
1093                            if using_battery {
1094                                wake_lease = None;
1095                            } else if new_lease.is_some() {
1096                                wake_lease = new_lease;
1097                            }
1098                        }
1099                    }
1100                    .fuse()
1101                );
1102
1103                let mut do_trim = pin!(self.do_trim(Some(&pauser)).fuse());
1104
1105                loop {
1106                    futures::select! {
1107                        _ = pause_future => {}
1108                        result = do_trim => break result,
1109                    }
1110                }
1111
1112                // Now that trim has completed, we don't need to watch the power source any more, so
1113                // we just drop the pauser and the future monitoring the power source.
1114            } else {
1115                self.do_trim(None).await
1116            };
1117
1118            let duration = start.elapsed();
1119            match result {
1120                Ok(bytes_trimmed) => info!(
1121                    "Trimmed {bytes_trimmed} bytes in {duration:?}.  Next trim in \
1122                     {next_timer:?}",
1123                ),
1124                Err(error) => error!(error:?; "Failed to trim"),
1125            }
1126
1127            let Some((_, interval)) = self.options.trim_config else { return };
1128            next_timer = interval;
1129            if next_timer.is_zero() {
1130                fasync::yield_now().await;
1131            }
1132        }
1133    }
1134
1135    // Returns the number of bytes trimmed.
1136    async fn do_trim(&self, pauser: Option<&Pauser>) -> Result<usize, Error> {
1137        const MAX_EXTENTS_PER_BATCH: usize = 8;
1138        const MAX_EXTENT_SIZE: usize = 256 * 1024;
1139        let mut offset = 0;
1140        let mut bytes_trimmed = 0;
1141        loop {
1142            let allocator = self.allocator();
1143            if let Some(pauser) = pauser {
1144                pauser.maybe_pause().await;
1145            }
1146            let trimmable_extents =
1147                allocator.take_for_trimming(offset, MAX_EXTENT_SIZE, MAX_EXTENTS_PER_BATCH).await?;
1148            for device_range in trimmable_extents.extents() {
1149                self.device.trim(device_range.clone()).await?;
1150                bytes_trimmed += device_range.length()? as usize;
1151            }
1152            if let Some(device_range) = trimmable_extents.extents().last() {
1153                offset = device_range.end;
1154            } else {
1155                break;
1156            }
1157        }
1158        Ok(bytes_trimmed)
1159    }
1160
1161    fn start_clean_transfer_buffer_task(self: &Arc<Self>) {
1162        let this = self.clone();
1163        self.background_tasks.spawn(
1164            async move {
1165                loop {
1166                    fasync::Timer::new(CLEAN_TRANSFER_BUFFER_INTERVAL).await;
1167                    this.device().clean_transfer_buffer();
1168                }
1169            }
1170            .trace(trace_future_args!("Filesystem::clean_transfer_buffer_task")),
1171        );
1172    }
1173
1174    pub async fn truncate_guard(&self, store_id: u64, object_id: u64) -> TruncateGuard<'_> {
1175        let keys = lock_keys![LockKey::truncate(store_id, object_id,)];
1176        TruncateGuard(self.lock_manager().write_lock(keys).await)
1177    }
1178
1179    pub async fn truncate_guard_owned(
1180        self: &Arc<Self>,
1181        store_id: u64,
1182        object_id: u64,
1183    ) -> TruncateGuard<'static> {
1184        self.truncate_guard(store_id, object_id).await.into_owned(self.clone())
1185    }
1186
1187    /// Immediately tombstones (purges) an object in the graveyard, deallocating its extents,
1188    /// removing its graveyard entry, and inserting an LSM-tree tombstone record
1189    /// (`ObjectValue::None`) that deletes all remaining records for the object during compaction.
1190    ///
1191    /// Use this when space should be reclaimed immediately (e.g. when deleting a volume or
1192    /// unlinking a file with no open references) or when the background graveyard reaper is not
1193    /// running. Use [`Graveyard::queue_tombstone_object`] instead when tombstoning from a
1194    /// non-async context (such as `Drop`) or when work can be deferred to the background (such as
1195    /// initial mount-time reaping).
1196    pub async fn tombstone_object(
1197        &self,
1198        store_id: u64,
1199        object_id: u64,
1200        truncate_guard: Option<&TruncateGuard<'_>>,
1201    ) -> Result<(), Error> {
1202        let store = self
1203            .objects
1204            .store(store_id)
1205            .with_context(|| format!("Failed to get store {}", store_id))?;
1206        store
1207            .tombstone_object(
1208                object_id,
1209                transaction::Options {
1210                    reservation: transaction::ReservationOptions::Borrowed,
1211                    ..Default::default()
1212                },
1213                truncate_guard,
1214            )
1215            .await
1216    }
1217
1218    /// Immediately tombstones (purges) an attribute in the graveyard, deallocating its extents,
1219    /// removing its graveyard entry, and inserting an LSM-tree tombstone record
1220    /// (`ObjectValue::None`) that deletes the attribute record during compaction.
1221    ///
1222    /// See [`FxFilesystem::tombstone_object`] for when to use this vs.
1223    /// [`Graveyard::queue_tombstone_attribute`].
1224    pub async fn tombstone_attribute(
1225        &self,
1226        store_id: u64,
1227        object_id: u64,
1228        attribute_id: AttributeId,
1229    ) -> Result<(), Error> {
1230        let store = self
1231            .objects
1232            .store(store_id)
1233            .with_context(|| format!("Failed to get store {}", store_id))?;
1234        store
1235            .tombstone_attribute(
1236                object_id,
1237                attribute_id,
1238                transaction::Options {
1239                    reservation: transaction::ReservationOptions::Borrowed,
1240                    ..Default::default()
1241                },
1242            )
1243            .await
1244    }
1245
1246    async fn populate_stores_node(&self) -> Result<Inspector, Error> {
1247        let inspector = fuchsia_inspect::Inspector::default();
1248        let root = inspector.root();
1249        root.record_child("__root", |n| self.root_store().record_data(n));
1250        root.record_child("__root_parent", |n| self.root_parent_store().record_data(n));
1251        let object_manager = self.object_manager();
1252        let volume_directory = object_manager.volume_directory();
1253        let layer_set = volume_directory.store().tree().layer_set();
1254        let mut merger = layer_set.merger();
1255        let mut iter = volume_directory.iter(&mut merger).await?;
1256        while let Some((name, id, _)) = iter.get() {
1257            if let Some(store) = object_manager.store(id) {
1258                root.record_child(name.to_string(), |n| store.record_data(n));
1259            }
1260            iter.advance().await?;
1261        }
1262        Ok(inspector)
1263    }
1264}
1265
1266/// A wrapper around a guard that needs to be taken when truncating an object.
1267#[allow(dead_code)]
1268pub struct TruncateGuard<'a>(WriteGuard<'a>);
1269
1270impl<'a> TruncateGuard<'a> {
1271    pub fn into_owned(self, fs: Arc<dyn Filesystem>) -> TruncateGuard<'static> {
1272        TruncateGuard(self.0.into_owned(fs))
1273    }
1274}
1275
1276/// Helper method for making a new filesystem.
1277pub async fn mkfs(device: DeviceHolder) -> Result<DeviceHolder, Error> {
1278    let fs = FxFilesystem::new_empty(device).await?;
1279    fs.close().await?;
1280    Ok(fs.take_device().await)
1281}
1282
1283/// Helper method for making a new filesystem with a single named volume.
1284/// This shouldn't be used in production; instead volumes should be created with the Volumes
1285/// protocol.
1286pub async fn mkfs_with_volume(
1287    device: DeviceHolder,
1288    volume_name: &str,
1289    crypt: Option<Arc<dyn Crypt>>,
1290) -> Result<DeviceHolder, Error> {
1291    let fs = FxFilesystem::new_empty(device).await?;
1292    {
1293        // expect instead of propagating errors here, since otherwise we could drop |fs| before
1294        // close is called, which leads to confusing and unrelated error messages.
1295        let root_volume = root_volume(fs.clone()).await.expect("Open root_volume failed");
1296        root_volume
1297            .new_volume(
1298                volume_name,
1299                NewChildStoreOptions {
1300                    options: StoreOptions { crypt, ..StoreOptions::default() },
1301                    ..Default::default()
1302                },
1303            )
1304            .await
1305            .expect("Create volume failed");
1306    }
1307    fs.close().await?;
1308    Ok(fs.take_device().await)
1309}
1310
1311struct FsckAfterEveryTransaction {
1312    fs: OnceLock<Weak<FxFilesystem>>,
1313    old_hook: PostCommitHook,
1314}
1315
1316impl FsckAfterEveryTransaction {
1317    fn new(old_hook: PostCommitHook) -> Arc<Self> {
1318        Arc::new(Self { fs: OnceLock::new(), old_hook })
1319    }
1320
1321    async fn run(self: Arc<Self>) {
1322        if let Some(fs) = self.fs.get().and_then(Weak::upgrade) {
1323            let options = FsckOptions {
1324                fail_on_warning: true,
1325                no_lock: true,
1326                quiet: true,
1327                ..Default::default()
1328            };
1329            fsck_with_options(fs.clone(), &options).await.expect("fsck failed");
1330            let object_manager = fs.object_manager();
1331            for store in object_manager.unlocked_stores() {
1332                let store_id = store.store_object_id();
1333                if !object_manager.is_system_store(store_id) {
1334                    fsck_volume_with_options(fs.as_ref(), &options, store_id, None)
1335                        .await
1336                        .expect("fsck_volume_with_options failed");
1337                }
1338            }
1339        }
1340        if let Some(old_hook) = self.old_hook.as_ref() {
1341            old_hook().await;
1342        }
1343    }
1344}
1345
1346struct Pauser {
1347    pause: Condition<bool>,
1348    bounce_delay: Duration,
1349}
1350
1351impl Pauser {
1352    /// Returns a new Pauser which starts paused.
1353    fn new(bounce_delay: Duration) -> Self {
1354        Self { pause: Condition::new(true), bounce_delay }
1355    }
1356
1357    async fn maybe_pause(&self) {
1358        loop {
1359            if !*self.pause.lock() {
1360                return;
1361            }
1362            self.pause.when(|p| if **p { Poll::Pending } else { Poll::Ready(()) }).await;
1363            fasync::Timer::new(self.bounce_delay).await;
1364        }
1365    }
1366
1367    fn set_pause(&self, v: bool) {
1368        let mut guard = self.pause.lock();
1369        if *guard == v {
1370            return;
1371        }
1372        *guard = v;
1373        for waker in guard.drain_wakers() {
1374            waker.wake();
1375        }
1376    }
1377}
1378
1379trait NextLatest: Stream + Unpin {
1380    /// Gets the next item from the stream, but if multiple items are ready, returns the latest.
1381    async fn next_latest(&mut self) -> Option<Self::Item> {
1382        let Some(mut next) = self.next().await else { return None };
1383
1384        // Coalesce with any subsequent items that are ready.
1385        loop {
1386            match self.next().now_or_never() {
1387                None => return Some(next),
1388                Some(None) => return None,
1389                Some(Some(n)) => next = n,
1390            }
1391        }
1392    }
1393}
1394
1395impl<T: ?Sized + Unpin> NextLatest for T where T: Stream {}
1396
1397#[cfg(test)]
1398mod tests {
1399    use super::{FlushReason, ForceMajor, FxFilesystem, FxFilesystemBuilder, SyncOptions};
1400    use crate::errors::FxfsError;
1401    use crate::fsck::{fsck, fsck_volume};
1402    use crate::log::*;
1403    use crate::lsm_tree::Operation;
1404    use crate::lsm_tree::types::Item;
1405    use crate::object_handle::{INVALID_OBJECT_ID, ObjectHandle, WriteObjectHandle};
1406    use crate::object_store::directory::{Directory, replace_child};
1407    use crate::object_store::journal::JournalOptions;
1408    use crate::object_store::journal::super_block::SuperBlockInstance;
1409    use crate::object_store::transaction::{LockKey, Mutation, Options, lock_keys};
1410    use crate::object_store::volume::root_volume;
1411    use crate::object_store::{
1412        HandleOptions, NewChildStoreOptions, ObjectDescriptor, ObjectKey, ObjectStore, ObjectValue,
1413        StoreOptions,
1414    };
1415    use crate::range::RangeExt;
1416    use fuchsia_async as fasync;
1417    use fuchsia_sync::Mutex;
1418    use futures::future::join_all;
1419    use futures::stream::{FuturesUnordered, TryStreamExt};
1420    use fxfs_insecure_crypto::new_insecure_crypt;
1421    use rustc_hash::FxHashMap as HashMap;
1422    use std::ops::Range;
1423    use std::sync::Arc;
1424    use std::sync::atomic::{AtomicU32, Ordering};
1425    use std::time::Duration;
1426    use storage_device::DeviceHolder;
1427    use storage_device::fake_device::{self, FakeDevice};
1428    use test_case::test_case;
1429
1430    const TEST_DEVICE_BLOCK_SIZE: u32 = 512;
1431
1432    #[fuchsia::test(threads = 10)]
1433    async fn test_compaction() {
1434        let device = DeviceHolder::new(FakeDevice::new(8192, TEST_DEVICE_BLOCK_SIZE));
1435
1436        // If compaction is not working correctly, this test will run out of space.
1437        let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
1438        let root_store = fs.root_store();
1439        let root_directory = Directory::open(&root_store, root_store.root_directory_object_id())
1440            .await
1441            .expect("open failed");
1442
1443        let mut tasks = Vec::new();
1444        for i in 0..2 {
1445            let mut transaction = fs
1446                .root_store()
1447                .new_transaction(
1448                    lock_keys![LockKey::object(
1449                        root_store.store_object_id(),
1450                        root_directory.object_id()
1451                    )],
1452                    Options::default(),
1453                )
1454                .await
1455                .expect("new_transaction failed");
1456            let handle = root_directory
1457                .create_child_file(&mut transaction, &format!("{}", i))
1458                .await
1459                .expect("create_child_file failed");
1460            transaction.commit().await.expect("commit failed");
1461            tasks.push(fasync::Task::spawn(async move {
1462                const TEST_DATA: &[u8] = b"hello";
1463                let mut buf = handle.allocate_buffer(TEST_DATA.len()).await;
1464                buf.copy_from_slice(TEST_DATA);
1465                for _ in 0..1500 {
1466                    handle.write_or_append(Some(0), buf.as_ref()).await.expect("write failed");
1467                }
1468            }));
1469        }
1470        join_all(tasks).await;
1471        fs.sync(SyncOptions::default()).await.expect("sync failed");
1472
1473        fsck(fs.clone()).await.expect("fsck failed");
1474        fs.close().await.expect("Close failed");
1475    }
1476
1477    #[fuchsia::test]
1478    async fn test_enable_allocations() {
1479        // 1. enable_allocations() has no impact if image_builder_mode is not used.
1480        {
1481            let device = DeviceHolder::new(FakeDevice::new(8192, TEST_DEVICE_BLOCK_SIZE));
1482            let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
1483            fs.enable_allocations();
1484            let root_store = fs.root_store();
1485            let root_directory =
1486                Directory::open(&root_store, root_store.root_directory_object_id())
1487                    .await
1488                    .expect("open failed");
1489            let mut transaction = fs
1490                .root_store()
1491                .new_transaction(
1492                    lock_keys![LockKey::object(
1493                        root_store.store_object_id(),
1494                        root_directory.object_id()
1495                    )],
1496                    Options::default(),
1497                )
1498                .await
1499                .expect("new_transaction failed");
1500            root_directory
1501                .create_child_file(&mut transaction, "test")
1502                .await
1503                .expect("create_child_file failed");
1504            transaction.commit().await.expect("commit failed");
1505            fs.close().await.expect("close failed");
1506        }
1507
1508        // 2. Allocations blow up if done before this call (in image_builder_mode), but work after
1509        {
1510            let device = DeviceHolder::new(FakeDevice::new(8192, TEST_DEVICE_BLOCK_SIZE));
1511            let fs = FxFilesystemBuilder::new()
1512                .format(true)
1513                .image_builder_mode(Some(SuperBlockInstance::A))
1514                .open(device)
1515                .await
1516                .expect("open failed");
1517            let root_store = fs.root_store();
1518            let root_directory =
1519                Directory::open(&root_store, root_store.root_directory_object_id())
1520                    .await
1521                    .expect("open failed");
1522
1523            let mut transaction = fs
1524                .root_store()
1525                .new_transaction(
1526                    lock_keys![LockKey::object(
1527                        root_store.store_object_id(),
1528                        root_directory.object_id()
1529                    )],
1530                    Options::default(),
1531                )
1532                .await
1533                .expect("new_transaction failed");
1534            let handle = root_directory
1535                .create_child_file(&mut transaction, "test_fail")
1536                .await
1537                .expect("create_child_file failed");
1538            transaction.commit().await.expect("commit failed");
1539
1540            // Allocations should fail before enable_allocations()
1541            assert!(
1542                FxfsError::Unavailable
1543                    .matches(&handle.allocate(0..4096).await.expect_err("allocate should fail"))
1544            );
1545
1546            // Allocations should work after enable_allocations()
1547            fs.enable_allocations();
1548            handle.allocate(0..4096).await.expect("allocate should work after enable_allocations");
1549
1550            // 3. finalize() works regardless of whether enable_allocations() is called.
1551            // (We already called it above, so this verifies it works after it was called).
1552
1553            fs.close().await.expect("close failed");
1554        }
1555        // TODO(https://fxbug.dev/467401079): Add a failure test where we close without
1556        // enabling allocations. (Trivial to do, but causes error logs, which are interpreted as
1557        // test failures and only seem controllable at the BUILD target level).
1558    }
1559
1560    #[fuchsia::test(threads = 10)]
1561    async fn test_replay_is_identical() {
1562        let device = DeviceHolder::new(FakeDevice::new(8192, TEST_DEVICE_BLOCK_SIZE));
1563        let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
1564
1565        // Reopen the store, but set reclaim size to a very large value which will effectively
1566        // stop the journal from flushing and allows us to track all the mutations to the store.
1567        fs.close().await.expect("close failed");
1568        let device = fs.take_device().await;
1569        device.reopen(false);
1570
1571        struct Mutations<K, V>(Mutex<Vec<(Operation, Item<K, V>)>>);
1572
1573        impl<K: Clone, V: Clone> Mutations<K, V> {
1574            fn new() -> Self {
1575                Mutations(Mutex::new(Vec::new()))
1576            }
1577
1578            fn push(&self, operation: Operation, item: &Item<K, V>) {
1579                self.0.lock().push((operation, item.clone()));
1580            }
1581        }
1582
1583        let open_fs = |device,
1584                       object_mutations: Arc<Mutex<HashMap<_, _>>>,
1585                       allocator_mutations: Arc<Mutations<_, _>>| async {
1586            FxFilesystemBuilder::new()
1587                .journal_options(JournalOptions { reclaim_size: u64::MAX, ..Default::default() })
1588                .on_new_allocator(move |allocator| {
1589                    let allocator_mutations = allocator_mutations.clone();
1590                    allocator.tree().set_mutation_callback(Some(Box::new(move |op, item| {
1591                        allocator_mutations.push(op, item)
1592                    })));
1593                })
1594                .on_new_store(move |store| {
1595                    let mutations = Arc::new(Mutations::new());
1596                    object_mutations.lock().insert(store.store_object_id(), mutations.clone());
1597                    store.tree().set_mutation_callback(Some(Box::new(move |op, item| {
1598                        mutations.push(op, item)
1599                    })));
1600                })
1601                .open(device)
1602                .await
1603                .expect("open failed")
1604        };
1605
1606        let allocator_mutations = Arc::new(Mutations::new());
1607        let object_mutations = Arc::new(Mutex::new(HashMap::default()));
1608        let fs = open_fs(device, object_mutations.clone(), allocator_mutations.clone()).await;
1609
1610        let root_store = fs.root_store();
1611        let root_directory = Directory::open(&root_store, root_store.root_directory_object_id())
1612            .await
1613            .expect("open failed");
1614
1615        let mut transaction = fs
1616            .root_store()
1617            .new_transaction(
1618                lock_keys![LockKey::object(
1619                    root_store.store_object_id(),
1620                    root_directory.object_id()
1621                )],
1622                Options::default(),
1623            )
1624            .await
1625            .expect("new_transaction failed");
1626        let object = root_directory
1627            .create_child_file(&mut transaction, "test")
1628            .await
1629            .expect("create_child_file failed");
1630        transaction.commit().await.expect("commit failed");
1631
1632        // Append some data.
1633        let buf = object.allocate_buffer(10000).await;
1634        object.write_or_append(Some(0), buf.as_ref()).await.expect("write failed");
1635
1636        // Overwrite some data.
1637        object.write_or_append(Some(5000), buf.as_ref()).await.expect("write failed");
1638
1639        // Truncate.
1640        object.truncate(3000).await.expect("truncate failed");
1641
1642        // Delete the object.
1643        let mut transaction = fs
1644            .root_store()
1645            .new_transaction(
1646                lock_keys![
1647                    LockKey::object(root_store.store_object_id(), root_directory.object_id()),
1648                    LockKey::object(root_store.store_object_id(), object.object_id()),
1649                ],
1650                Options::default(),
1651            )
1652            .await
1653            .expect("new_transaction failed");
1654
1655        replace_child(&mut transaction, None, (&root_directory, "test"))
1656            .await
1657            .expect("replace_child failed");
1658
1659        transaction.commit().await.expect("commit failed");
1660
1661        // Finally tombstone the object.
1662        root_store
1663            .tombstone_object(object.object_id(), Options::default(), None)
1664            .await
1665            .expect("tombstone failed");
1666
1667        // Now reopen and check that replay produces the same set of mutations.
1668        fs.close().await.expect("close failed");
1669
1670        let metadata_reservation_amount = fs.object_manager().metadata_reservation().amount();
1671
1672        let device = fs.take_device().await;
1673        device.reopen(false);
1674
1675        let replayed_object_mutations = Arc::new(Mutex::new(HashMap::default()));
1676        let replayed_allocator_mutations = Arc::new(Mutations::new());
1677        let fs = open_fs(
1678            device,
1679            replayed_object_mutations.clone(),
1680            replayed_allocator_mutations.clone(),
1681        )
1682        .await;
1683
1684        let m1 = object_mutations.lock();
1685        let m2 = replayed_object_mutations.lock();
1686        assert_eq!(m1.len(), m2.len());
1687        for (store_id, mutations) in &*m1 {
1688            let mutations = mutations.0.lock();
1689            let replayed = m2.get(&store_id).expect("Found unexpected store").0.lock();
1690            assert_eq!(mutations.len(), replayed.len());
1691            for ((op1, i1), (op2, i2)) in mutations.iter().zip(replayed.iter()) {
1692                assert_eq!(op1, op2);
1693                assert_eq!(i1.key, i2.key);
1694                assert_eq!(i1.value, i2.value);
1695            }
1696        }
1697
1698        let a1 = allocator_mutations.0.lock();
1699        let a2 = replayed_allocator_mutations.0.lock();
1700        assert_eq!(a1.len(), a2.len());
1701        for ((op1, i1), (op2, i2)) in a1.iter().zip(a2.iter()) {
1702            assert_eq!(op1, op2);
1703            assert_eq!(i1.key, i2.key);
1704            assert_eq!(i1.value, i2.value);
1705        }
1706
1707        assert_eq!(
1708            fs.object_manager().metadata_reservation().amount(),
1709            metadata_reservation_amount
1710        );
1711    }
1712
1713    #[fuchsia::test]
1714    async fn test_max_in_flight_transactions() {
1715        let device = DeviceHolder::new(FakeDevice::new(8192, TEST_DEVICE_BLOCK_SIZE));
1716        let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
1717
1718        let store = fs.root_store();
1719        let transactions = FuturesUnordered::new();
1720        for _ in 0..super::MAX_IN_FLIGHT_TRANSACTIONS {
1721            transactions.push(store.new_transaction(lock_keys![], Options::default()));
1722        }
1723        let mut transactions: Vec<_> = transactions.try_collect().await.unwrap();
1724
1725        // Trying to create another one should be blocked.
1726        let mut fut = std::pin::pin!(store.new_transaction(lock_keys![], Options::default()));
1727        assert!(futures::poll!(&mut fut).is_pending());
1728
1729        // Dropping one should allow it to proceed.
1730        transactions.pop();
1731
1732        assert!(futures::poll!(&mut fut).is_ready());
1733    }
1734
1735    // If run on a single thread, the trim tasks starve out other work.
1736    #[fuchsia::test(threads = 10)]
1737    async fn test_continuously_trim() {
1738        let device = DeviceHolder::new(FakeDevice::new(8192, TEST_DEVICE_BLOCK_SIZE));
1739        let fs = FxFilesystemBuilder::new()
1740            .trim_config(Some((Duration::ZERO, Duration::ZERO)))
1741            .format(true)
1742            .open(device)
1743            .await
1744            .expect("open failed");
1745        // Do a small sleep so trim has time to get going.
1746        fasync::Timer::new(Duration::from_millis(10)).await;
1747
1748        // Create and delete a bunch of files whilst trim is ongoing.  This just ensures that
1749        // regular usage isn't affected by trim.
1750        let root_store = fs.root_store();
1751        let root_directory = Directory::open(&root_store, root_store.root_directory_object_id())
1752            .await
1753            .expect("open failed");
1754        for _ in 0..100 {
1755            let mut transaction = fs
1756                .root_store()
1757                .new_transaction(
1758                    lock_keys![LockKey::object(
1759                        root_store.store_object_id(),
1760                        root_directory.object_id()
1761                    )],
1762                    Options::default(),
1763                )
1764                .await
1765                .expect("new_transaction failed");
1766            let object = root_directory
1767                .create_child_file(&mut transaction, "test")
1768                .await
1769                .expect("create_child_file failed");
1770            transaction.commit().await.expect("commit failed");
1771
1772            {
1773                let buf = object.allocate_buffer(1024).await;
1774                object.write_or_append(Some(0), buf.as_ref()).await.expect("write failed");
1775            }
1776            std::mem::drop(object);
1777
1778            let mut transaction = root_directory
1779                .acquire_context_for_replace(None, "test", true)
1780                .await
1781                .expect("acquire_context_for_replace failed")
1782                .transaction;
1783            replace_child(&mut transaction, None, (&root_directory, "test"))
1784                .await
1785                .expect("replace_child failed");
1786            transaction.commit().await.expect("commit failed");
1787        }
1788        fs.close().await.expect("close failed");
1789    }
1790
1791    #[test_case(true; "test power fail with barriers")]
1792    #[test_case(false; "test power fail with checksums")]
1793    #[fuchsia::test]
1794    async fn test_power_fail(barriers_enabled: bool) {
1795        // This test randomly discards blocks, so we run it a few times to increase the chances
1796        // of catching an issue in a single run.
1797        for _ in 0..10 {
1798            let (store_id, device, test_file_object_id) = {
1799                let device = DeviceHolder::new(FakeDevice::new(8192, 4096));
1800                let fs = if barriers_enabled {
1801                    FxFilesystemBuilder::new()
1802                        .barriers_enabled(true)
1803                        .format(true)
1804                        .open(device)
1805                        .await
1806                        .expect("new filesystem failed")
1807                } else {
1808                    FxFilesystem::new_empty(device).await.expect("new_empty failed")
1809                };
1810                let root_volume = root_volume(fs.clone()).await.expect("root_volume failed");
1811
1812                fs.sync(SyncOptions { flush_device: true, ..SyncOptions::default() })
1813                    .await
1814                    .expect("sync failed");
1815
1816                let store = root_volume
1817                    .new_volume(
1818                        "test",
1819                        NewChildStoreOptions {
1820                            options: StoreOptions {
1821                                crypt: Some(Arc::new(new_insecure_crypt())),
1822                                ..StoreOptions::default()
1823                            },
1824                            ..Default::default()
1825                        },
1826                    )
1827                    .await
1828                    .expect("new_volume failed");
1829                let root_directory = Directory::open(&store, store.root_directory_object_id())
1830                    .await
1831                    .expect("open failed");
1832
1833                // Create a number of files with the goal of using up more than one journal block.
1834                async fn create_files(store: &Arc<ObjectStore>, prefix: &str) {
1835                    let fs = store.filesystem();
1836                    let root_directory = Directory::open(store, store.root_directory_object_id())
1837                        .await
1838                        .expect("open failed");
1839                    for i in 0..100 {
1840                        let mut transaction = fs
1841                            .ensure_local()
1842                            .root_store()
1843                            .new_transaction(
1844                                lock_keys![LockKey::object(
1845                                    store.store_object_id(),
1846                                    store.root_directory_object_id()
1847                                )],
1848                                Options::default(),
1849                            )
1850                            .await
1851                            .expect("new_transaction failed");
1852                        root_directory
1853                            .create_child_file(&mut transaction, &format!("{prefix} {i}"))
1854                            .await
1855                            .expect("create_child_file failed");
1856                        transaction.commit().await.expect("commit failed");
1857                    }
1858                }
1859
1860                // Create one batch of files.
1861                create_files(&store, "A").await;
1862
1863                // Create a file and write something to it.  This will make sure there's a
1864                // transaction present that includes a checksum.
1865                let mut transaction = fs
1866                    .root_store()
1867                    .new_transaction(
1868                        lock_keys![LockKey::object(
1869                            store.store_object_id(),
1870                            store.root_directory_object_id()
1871                        )],
1872                        Options::default(),
1873                    )
1874                    .await
1875                    .expect("new_transaction failed");
1876                let object = root_directory
1877                    .create_child_file(&mut transaction, "test")
1878                    .await
1879                    .expect("create_child_file failed");
1880                transaction.commit().await.expect("commit failed");
1881
1882                let mut transaction =
1883                    object.new_transaction().await.expect("new_transaction failed");
1884                let mut buffer = object.allocate_buffer(4096).await;
1885                buffer.fill(0xed);
1886                object
1887                    .txn_write(&mut transaction, 0, buffer.as_ref())
1888                    .await
1889                    .expect("txn_write failed");
1890                transaction.commit().await.expect("commit failed");
1891
1892                // Create another batch of files.
1893                create_files(&store, "B").await;
1894
1895                // Sync the device, but don't flush the device. We want to do this so we can
1896                // randomly discard blocks below.
1897                fs.sync(SyncOptions::default()).await.expect("sync failed");
1898
1899                // When we call `sync` above on the filesystem, it will pad the journal so that it
1900                // will get written, but it doesn't wait for the write to occur.  We wait for a
1901                // short time here to give allow time for the journal to be written.  Adding timers
1902                // isn't great, but this test already isn't deterministic since we randomly discard
1903                // blocks.
1904                fasync::Timer::new(Duration::from_millis(10)).await;
1905
1906                (
1907                    store.store_object_id(),
1908                    fs.device().snapshot().expect("snapshot failed"),
1909                    object.object_id(),
1910                )
1911            };
1912
1913            // Randomly discard blocks since the last flush.  This simulates what might happen in
1914            // the case of power-loss.  This will be an uncontrolled unmount.
1915            device
1916                .discard_random_since_last_flush()
1917                .expect("discard_random_since_last_flush failed");
1918
1919            let fs = FxFilesystem::open(device).await.expect("open failed");
1920            fsck(fs.clone()).await.expect("fsck failed");
1921
1922            let mut check_test_file = false;
1923
1924            // If we replayed and the store exists (i.e. the transaction that created the store
1925            // made it out), start by running fsck on it.
1926            let object_id = if fs.object_manager().store(store_id).is_some() {
1927                fsck_volume(&fs, store_id, Some(Arc::new(new_insecure_crypt())))
1928                    .await
1929                    .expect("fsck_volume failed");
1930
1931                // Now we want to create another file, unmount cleanly, and then finally check that
1932                // the new file exists.  This checks that we can continue to use the filesystem
1933                // after an unclean unmount.
1934                let store = root_volume(fs.clone())
1935                    .await
1936                    .expect("root_volume failed")
1937                    .volume(
1938                        "test",
1939                        StoreOptions {
1940                            crypt: Some(Arc::new(new_insecure_crypt())),
1941                            ..StoreOptions::default()
1942                        },
1943                    )
1944                    .await
1945                    .expect("volume failed");
1946
1947                let root_directory = Directory::open(&store, store.root_directory_object_id())
1948                    .await
1949                    .expect("open failed");
1950
1951                let mut transaction = fs
1952                    .root_store()
1953                    .new_transaction(
1954                        lock_keys![LockKey::object(
1955                            store.store_object_id(),
1956                            store.root_directory_object_id()
1957                        )],
1958                        Options::default(),
1959                    )
1960                    .await
1961                    .expect("new_transaction failed");
1962                let object = root_directory
1963                    .create_child_file(&mut transaction, &format!("C"))
1964                    .await
1965                    .expect("create_child_file failed");
1966                transaction.commit().await.expect("commit failed");
1967
1968                // Write again to the test file if it exists.
1969                if let Ok(test_file) = ObjectStore::open_object(
1970                    &store,
1971                    test_file_object_id,
1972                    HandleOptions::default(),
1973                    None,
1974                )
1975                .await
1976                {
1977                    // Check it has the contents we expect.
1978                    let mut buffer = test_file.allocate_buffer(4096).await;
1979                    let bytes =
1980                        test_file.read_aligned(0, buffer.as_mut()).await.expect("read failed");
1981                    if bytes == 4096 {
1982                        let expected = [0xed; 4096];
1983                        assert_eq!(buffer.to_vec(), expected);
1984                    } else {
1985                        // If the write didn't make it, the file should have zero bytes.
1986                        assert_eq!(bytes, 0);
1987                    }
1988
1989                    // Modify the test file.
1990                    let mut transaction =
1991                        test_file.new_transaction().await.expect("new_transaction failed");
1992                    buffer.fill(0x37);
1993                    test_file
1994                        .txn_write(&mut transaction, 0, buffer.as_ref())
1995                        .await
1996                        .expect("txn_write failed");
1997                    transaction.commit().await.expect("commit failed");
1998                    check_test_file = true;
1999                }
2000
2001                object.object_id()
2002            } else {
2003                INVALID_OBJECT_ID
2004            };
2005
2006            // This will do a controlled unmount.
2007            fs.close().await.expect("close failed");
2008            let device = fs.take_device().await;
2009            device.reopen(false);
2010
2011            let fs = FxFilesystem::open(device).await.expect("open failed");
2012            fsck(fs.clone()).await.expect("fsck failed");
2013
2014            // As mentioned above, make sure that the object we created before the clean unmount
2015            // exists.
2016            if object_id != INVALID_OBJECT_ID {
2017                fsck_volume(&fs, store_id, Some(Arc::new(new_insecure_crypt())))
2018                    .await
2019                    .expect("fsck_volume failed");
2020
2021                let store = root_volume(fs.clone())
2022                    .await
2023                    .expect("root_volume failed")
2024                    .volume(
2025                        "test",
2026                        StoreOptions {
2027                            crypt: Some(Arc::new(new_insecure_crypt())),
2028                            ..StoreOptions::default()
2029                        },
2030                    )
2031                    .await
2032                    .expect("volume failed");
2033                // We should be able to open the C object.
2034                ObjectStore::open_object(&store, object_id, HandleOptions::default(), None)
2035                    .await
2036                    .expect("open_object failed");
2037
2038                // If we made the modification to the test file, check it.
2039                if check_test_file {
2040                    info!("Checking test file for modification");
2041                    let test_file = ObjectStore::open_object(
2042                        &store,
2043                        test_file_object_id,
2044                        HandleOptions::default(),
2045                        None,
2046                    )
2047                    .await
2048                    .expect("open_object failed");
2049                    let mut buffer = test_file.allocate_buffer(4096).await;
2050                    assert_eq!(
2051                        test_file.read_aligned(0, buffer.as_mut()).await.expect("read failed"),
2052                        4096
2053                    );
2054                    let expected = [0x37; 4096];
2055                    let data = buffer.to_vec();
2056                    assert_eq!(data, expected);
2057                }
2058            }
2059
2060            fs.close().await.expect("close failed");
2061        }
2062    }
2063
2064    #[fuchsia::test]
2065    async fn test_barrier_not_emitted_when_transaction_has_no_data() {
2066        let barrier_count = Arc::new(AtomicU32::new(0));
2067
2068        struct Observer(Arc<AtomicU32>);
2069
2070        impl fake_device::Observer for Observer {
2071            fn barrier(&self) {
2072                self.0.fetch_add(1, Ordering::Relaxed);
2073            }
2074        }
2075
2076        let mut fake_device = FakeDevice::new(8192, 4096);
2077        fake_device.set_observer(Box::new(Observer(barrier_count.clone())));
2078        let device = DeviceHolder::new(fake_device);
2079        let fs = FxFilesystemBuilder::new()
2080            .barriers_enabled(true)
2081            .format(true)
2082            .open(device)
2083            .await
2084            .expect("new filesystem failed");
2085
2086        {
2087            let root_vol = root_volume(fs.clone()).await.expect("root_volume failed");
2088            root_vol
2089                .new_volume(
2090                    "test",
2091                    NewChildStoreOptions {
2092                        options: StoreOptions {
2093                            crypt: Some(Arc::new(new_insecure_crypt())),
2094                            ..StoreOptions::default()
2095                        },
2096                        ..NewChildStoreOptions::default()
2097                    },
2098                )
2099                .await
2100                .expect("there is no test volume");
2101            fs.close().await.expect("close failed");
2102        }
2103        // Remount the filesystem to ensure that the journal flushes and we can get a reliable
2104        // measure of the number of barriers issued during setup.
2105        let device = fs.take_device().await;
2106        device.reopen(false);
2107        let fs = FxFilesystemBuilder::new()
2108            .barriers_enabled(true)
2109            .open(device)
2110            .await
2111            .expect("new filesystem failed");
2112        let expected_barrier_count = barrier_count.load(Ordering::Relaxed);
2113
2114        let root_vol = root_volume(fs.clone()).await.expect("root_volume failed");
2115        let store = root_vol
2116            .volume(
2117                "test",
2118                StoreOptions {
2119                    crypt: Some(Arc::new(new_insecure_crypt())),
2120                    ..StoreOptions::default()
2121                },
2122            )
2123            .await
2124            .expect("there is no test volume");
2125
2126        // Create a number of files with the goal of using up more than one journal block.
2127        let root_directory =
2128            Directory::open(&store, store.root_directory_object_id()).await.expect("open failed");
2129        for i in 0..100 {
2130            let mut transaction = fs
2131                .root_store()
2132                .new_transaction(
2133                    lock_keys![LockKey::object(
2134                        store.store_object_id(),
2135                        store.root_directory_object_id()
2136                    )],
2137                    Options::default(),
2138                )
2139                .await
2140                .expect("new_transaction failed");
2141            root_directory
2142                .create_child_file(&mut transaction, &format!("A {i}"))
2143                .await
2144                .expect("create_child_file failed");
2145            transaction.commit().await.expect("commit failed");
2146        }
2147
2148        // Unmount the filesystem to ensure that the journal flushes.
2149        fs.close().await.expect("close failed");
2150        // Ensure that no barriers were emitted while creating files, as no data was written.
2151        assert_eq!(expected_barrier_count, barrier_count.load(Ordering::Relaxed));
2152    }
2153
2154    #[fuchsia::test]
2155    async fn test_barrier_emitted_when_transaction_includes_data() {
2156        let barrier_count = Arc::new(AtomicU32::new(0));
2157
2158        struct Observer(Arc<AtomicU32>);
2159
2160        impl fake_device::Observer for Observer {
2161            fn barrier(&self) {
2162                self.0.fetch_add(1, Ordering::Relaxed);
2163            }
2164        }
2165
2166        let mut fake_device = FakeDevice::new(8192, 4096);
2167        fake_device.set_observer(Box::new(Observer(barrier_count.clone())));
2168        let device = DeviceHolder::new(fake_device);
2169        let fs = FxFilesystemBuilder::new()
2170            .barriers_enabled(true)
2171            .format(true)
2172            .open(device)
2173            .await
2174            .expect("new filesystem failed");
2175
2176        {
2177            let root_vol = root_volume(fs.clone()).await.expect("root_volume failed");
2178            root_vol
2179                .new_volume(
2180                    "test",
2181                    NewChildStoreOptions {
2182                        options: StoreOptions {
2183                            crypt: Some(Arc::new(new_insecure_crypt())),
2184                            ..StoreOptions::default()
2185                        },
2186                        ..NewChildStoreOptions::default()
2187                    },
2188                )
2189                .await
2190                .expect("there is no test volume");
2191            fs.close().await.expect("close failed");
2192        }
2193        // Remount the filesystem to ensure that the journal flushes and we can get a reliable
2194        // measure of the number of barriers issued during setup.
2195        let device = fs.take_device().await;
2196        device.reopen(false);
2197        let fs = FxFilesystemBuilder::new()
2198            .barriers_enabled(true)
2199            .open(device)
2200            .await
2201            .expect("new filesystem failed");
2202        let expected_barrier_count = barrier_count.load(Ordering::Relaxed);
2203
2204        let root_vol = root_volume(fs.clone()).await.expect("root_volume failed");
2205        let store = root_vol
2206            .volume(
2207                "test",
2208                StoreOptions {
2209                    crypt: Some(Arc::new(new_insecure_crypt())),
2210                    ..StoreOptions::default()
2211                },
2212            )
2213            .await
2214            .expect("there is no test volume");
2215
2216        // Create a file and write something to it. This should cause a barrier to be emitted.
2217        let root_directory =
2218            Directory::open(&store, store.root_directory_object_id()).await.expect("open failed");
2219
2220        let mut transaction = fs
2221            .root_store()
2222            .new_transaction(
2223                lock_keys![LockKey::object(
2224                    store.store_object_id(),
2225                    store.root_directory_object_id()
2226                )],
2227                Options::default(),
2228            )
2229            .await
2230            .expect("new_transaction failed");
2231        let object = root_directory
2232            .create_child_file(&mut transaction, "test")
2233            .await
2234            .expect("create_child_file failed");
2235        transaction.commit().await.expect("commit failed");
2236
2237        let mut transaction = object.new_transaction().await.expect("new_transaction failed");
2238        let mut buffer = object.allocate_buffer(4096).await;
2239        buffer.fill(0xed);
2240        object.txn_write(&mut transaction, 0, buffer.as_ref()).await.expect("txn_write failed");
2241        transaction.commit().await.expect("commit failed");
2242
2243        // Unmount the filesystem to ensure that the journal flushes.
2244        fs.close().await.expect("close failed");
2245        // Ensure that a barrier was emitted while writing to the file.
2246        assert!(expected_barrier_count < barrier_count.load(Ordering::Relaxed));
2247    }
2248
2249    #[test_case(true; "fail when original filesystem has barriers enabled")]
2250    #[test_case(false; "fail when original filesystem has barriers disabled")]
2251    #[fuchsia::test]
2252    async fn test_switching_barrier_mode_on_existing_filesystem(original_barrier_mode: bool) {
2253        let crypt = Some(Arc::new(new_insecure_crypt()) as Arc<dyn fxfs_crypto::Crypt>);
2254        let fake_device = FakeDevice::new(8192, 4096);
2255        let device = DeviceHolder::new(fake_device);
2256        let fs: super::OpenFxFilesystem = FxFilesystemBuilder::new()
2257            .barriers_enabled(original_barrier_mode)
2258            .format(true)
2259            .open(device)
2260            .await
2261            .expect("new filesystem failed");
2262
2263        // Create a volume named test with a file inside it called file.
2264        {
2265            let root_vol = root_volume(fs.clone()).await.expect("root_volume failed");
2266            let store = root_vol
2267                .new_volume(
2268                    "test",
2269                    NewChildStoreOptions {
2270                        options: StoreOptions { crypt: crypt.clone(), ..Default::default() },
2271                        ..Default::default()
2272                    },
2273                )
2274                .await
2275                .expect("creating test volume");
2276            let root_dir = Directory::open(&store, store.root_directory_object_id())
2277                .await
2278                .expect("open failed");
2279            let mut transaction = fs
2280                .root_store()
2281                .new_transaction(
2282                    lock_keys![LockKey::object(
2283                        store.store_object_id(),
2284                        store.root_directory_object_id()
2285                    )],
2286                    Default::default(),
2287                )
2288                .await
2289                .expect("new_transaction failed");
2290            let object = root_dir
2291                .create_child_file(&mut transaction, "file")
2292                .await
2293                .expect("create_child_file failed");
2294            transaction.commit().await.expect("commit failed");
2295            let mut buffer = object.allocate_buffer(4096).await;
2296            buffer.fill(0xA7);
2297            let new_size = object.write_or_append(None, buffer.as_ref()).await.unwrap();
2298            assert_eq!(new_size, 4096);
2299        }
2300
2301        // Remount the filesystem with the opposite barrier mode and write more data to our file.
2302        fs.close().await.expect("close failed");
2303        let device = fs.take_device().await;
2304        device.reopen(false);
2305        let fs = FxFilesystemBuilder::new()
2306            .barriers_enabled(!original_barrier_mode)
2307            .open(device)
2308            .await
2309            .expect("new filesystem failed");
2310        {
2311            let root_vol = root_volume(fs.clone()).await.expect("root_volume failed");
2312            let store = root_vol
2313                .volume("test", StoreOptions { crypt: crypt.clone(), ..Default::default() })
2314                .await
2315                .expect("opening test volume");
2316            let root_dir = Directory::open(&store, store.root_directory_object_id())
2317                .await
2318                .expect("open failed");
2319            let (object_id, _, _) =
2320                root_dir.lookup("file").await.expect("lookup failed").expect("missing file");
2321            let test_file = ObjectStore::open_object(&store, object_id, Default::default(), None)
2322                .await
2323                .expect("open failed");
2324            // Write some more data.
2325            let mut buffer = test_file.allocate_buffer(4096).await;
2326            buffer.fill(0xA8);
2327            let new_size = test_file.write_or_append(None, buffer.as_ref()).await.unwrap();
2328            assert_eq!(new_size, 8192);
2329        }
2330
2331        // Lastly, remount the filesystems with the original barrier mode and make sure everything
2332        // can be read from the file as expected.
2333        fs.close().await.expect("close failed");
2334        let device = fs.take_device().await;
2335        device.reopen(false);
2336        let fs = FxFilesystemBuilder::new()
2337            .barriers_enabled(original_barrier_mode)
2338            .open(device)
2339            .await
2340            .expect("new filesystem failed");
2341        {
2342            let root_vol = root_volume(fs.clone()).await.expect("root_volume failed");
2343            let store = root_vol
2344                .volume("test", StoreOptions { crypt: crypt.clone(), ..Default::default() })
2345                .await
2346                .expect("opening test volume");
2347            let root_dir = Directory::open(&store, store.root_directory_object_id())
2348                .await
2349                .expect("open failed");
2350            let (object_id, _, _) =
2351                root_dir.lookup("file").await.expect("lookup failed").expect("missing file");
2352            let test_file = ObjectStore::open_object(&store, object_id, Default::default(), None)
2353                .await
2354                .expect("open failed");
2355            let mut buffer = test_file.allocate_buffer(8192).await;
2356            assert_eq!(
2357                test_file.read_aligned(0, buffer.as_mut()).await.expect("read failed"),
2358                8192,
2359                "short read"
2360            );
2361            let data = buffer.to_vec();
2362            assert_eq!(data[0..4096], [0xA7; 4096]);
2363            assert_eq!(data[4096..8192], [0xA8; 4096]);
2364        }
2365        fs.close().await.expect("close failed");
2366    }
2367
2368    #[fuchsia::test]
2369    async fn test_image_builder_mode_no_early_writes() {
2370        const BLOCK_SIZE: u32 = 4096;
2371        let device = DeviceHolder::new(FakeDevice::new(2048, BLOCK_SIZE));
2372        device.reopen(true);
2373        let fs = FxFilesystemBuilder::new()
2374            .format(true)
2375            .image_builder_mode(Some(SuperBlockInstance::A))
2376            .open(device)
2377            .await
2378            .expect("open failed");
2379        fs.enable_allocations();
2380        // fs.close() now performs compaction (writing superblock), so device must be writable.
2381        fs.device().reopen(false);
2382        fs.close().await.expect("closed");
2383    }
2384
2385    #[fuchsia::test]
2386    async fn test_image_builder_mode() {
2387        const BLOCK_SIZE: u32 = 4096;
2388        const EXISTING_FILE_RANGE: Range<u64> = 4096 * 1024..4096 * 1025;
2389        let device = DeviceHolder::new(FakeDevice::new(2048, BLOCK_SIZE));
2390
2391        // Write some fake file data at an offset in the image and confirm it as an fxfs file below.
2392        {
2393            let mut write_buf =
2394                device.allocate_buffer(EXISTING_FILE_RANGE.length().unwrap() as usize).await;
2395            write_buf.fill(0xf0);
2396            device.write(EXISTING_FILE_RANGE.start, write_buf.as_ref()).await.expect("write");
2397        }
2398
2399        device.reopen(true);
2400
2401        let device = {
2402            let fs = FxFilesystemBuilder::new()
2403                .format(true)
2404                .image_builder_mode(Some(SuperBlockInstance::B))
2405                .open(device)
2406                .await
2407                .expect("open failed");
2408            fs.enable_allocations();
2409            {
2410                let root_store = fs.root_store();
2411                let root_directory =
2412                    Directory::open(&root_store, root_store.root_directory_object_id())
2413                        .await
2414                        .expect("open failed");
2415                // Create a file referencing existing data on device.
2416                let handle;
2417                {
2418                    let mut transaction = fs
2419                        .root_store()
2420                        .new_transaction(
2421                            lock_keys![LockKey::object(
2422                                root_directory.store().store_object_id(),
2423                                root_directory.object_id()
2424                            )],
2425                            Options::default(),
2426                        )
2427                        .await
2428                        .expect("new transaction");
2429                    handle = root_directory
2430                        .create_child_file(&mut transaction, "test")
2431                        .await
2432                        .expect("create file");
2433                    handle.extend(&mut transaction, EXISTING_FILE_RANGE).await.expect("extend");
2434                    transaction.commit().await.expect("commit");
2435                }
2436            }
2437            fs.device().reopen(false);
2438            fs.close().await.expect("close");
2439            fs.take_device().await
2440        };
2441        device.reopen(false);
2442        let fs = FxFilesystem::open(device).await.expect("open failed");
2443        fsck(fs.clone()).await.expect("fsck failed");
2444
2445        // Confirm that the test file points at the correct data.
2446        let root_store = fs.root_store();
2447        let root_directory = Directory::open(&root_store, root_store.root_directory_object_id())
2448            .await
2449            .expect("open failed");
2450        let (object_id, descriptor, _) =
2451            root_directory.lookup("test").await.expect("lookup failed").unwrap();
2452        assert_eq!(descriptor, ObjectDescriptor::File);
2453        let test_file =
2454            ObjectStore::open_object(&root_store, object_id, HandleOptions::default(), None)
2455                .await
2456                .expect("open failed");
2457        let mut read_buf =
2458            test_file.allocate_buffer(EXISTING_FILE_RANGE.length().unwrap() as usize).await;
2459        test_file.read_aligned(0, read_buf.as_mut()).await.expect("read failed");
2460        let data = read_buf.to_vec();
2461        assert_eq!(data, [0xf0; 4096]);
2462        fs.close().await.expect("closed");
2463    }
2464
2465    #[fuchsia::test]
2466    async fn test_image_builder_mode_large_store_can_major_compact() {
2467        const BLOCK_SIZE: u32 = 4096;
2468        let device = DeviceHolder::new(FakeDevice::new(8192, BLOCK_SIZE));
2469        let device = {
2470            let fs = FxFilesystemBuilder::new()
2471                .format(true)
2472                .image_builder_mode(Some(SuperBlockInstance::A))
2473                .open(device)
2474                .await
2475                .expect("open failed");
2476            fs.enable_allocations();
2477            {
2478                let root_volume = root_volume(fs.clone()).await.expect("root_volume failed");
2479                let store = root_volume
2480                    .new_volume("vol", NewChildStoreOptions::default())
2481                    .await
2482                    .expect("new_volume failed");
2483                let root_dir = Directory::open(&store, store.root_directory_object_id())
2484                    .await
2485                    .expect("open failed");
2486                let handle = {
2487                    let mut transaction = store
2488                        .new_transaction(
2489                            lock_keys![LockKey::object(
2490                                store.store_object_id(),
2491                                root_dir.object_id()
2492                            )],
2493                            Options::default(),
2494                        )
2495                        .await
2496                        .expect("new_transaction failed");
2497                    let handle = root_dir
2498                        .create_child_file(&mut transaction, "file")
2499                        .await
2500                        .expect("create_child_file failed");
2501                    transaction.commit().await.expect("commit failed");
2502                    handle
2503                };
2504
2505                // Populate enough metadata (> 1 MiB of layer file data) so that `vol`'s store
2506                // reservation exceeds the initial `journal::RESERVED_SPACE`.
2507                for batch in 0..64 {
2508                    let mut transaction = store
2509                        .new_transaction(
2510                            lock_keys![LockKey::object(
2511                                store.store_object_id(),
2512                                handle.object_id()
2513                            )],
2514                            Options::default(),
2515                        )
2516                        .await
2517                        .expect("new_transaction failed");
2518                    for j in 0..20 {
2519                        let idx = batch * 20 + j;
2520                        transaction.add(
2521                            store.store_object_id(),
2522                            Mutation::replace_or_insert_object(
2523                                ObjectKey::extended_attribute(
2524                                    handle.object_id(),
2525                                    format!("attr_{idx:04}").into_bytes(),
2526                                ),
2527                                ObjectValue::inline_extended_attribute(vec![0xaa; 1024]),
2528                            ),
2529                        );
2530                    }
2531                    transaction.commit().await.expect("commit failed");
2532                }
2533            }
2534            fs.close().await.expect("close failed");
2535            fs.take_device().await
2536        };
2537        device.reopen(false);
2538
2539        let fs = FxFilesystem::open(device).await.expect("open failed");
2540        let object_manager = fs.object_manager();
2541        assert!(
2542            object_manager.metadata_reservation().amount()
2543                >= object_manager.max_store_reservation(),
2544            "metadata reservation ({}) is smaller than max store reservation ({}); borrowed={}",
2545            object_manager.metadata_reservation().amount(),
2546            object_manager.max_store_reservation(),
2547            object_manager.borrowed_metadata_space(),
2548        );
2549
2550        // Dirty `vol`, reserve all remaining unreserved disk space, and verify that a forced major
2551        // compaction succeeds using only `metadata_reservation`.
2552        {
2553            let root_volume = root_volume(fs.clone()).await.expect("root_volume failed");
2554            let store =
2555                root_volume.volume("vol", StoreOptions::default()).await.expect("volume failed");
2556            let root_dir = Directory::open(&store, store.root_directory_object_id())
2557                .await
2558                .expect("open failed");
2559            let mut transaction = store
2560                .new_transaction(
2561                    lock_keys![LockKey::object(store.store_object_id(), root_dir.object_id())],
2562                    Options::default(),
2563                )
2564                .await
2565                .expect("new_transaction failed");
2566            root_dir.create_child_file(&mut transaction, "extra").await.expect("create failed");
2567            transaction.commit().await.expect("commit failed");
2568        }
2569        let _hold = fs.allocator().reserve_with(None, |limit| limit);
2570        object_manager
2571            .flush(FlushReason::Journal(ForceMajor::True))
2572            .await
2573            .expect("forced major compaction failed");
2574        std::mem::drop(_hold);
2575        fsck(fs.clone()).await.expect("fsck failed");
2576        fs.close().await.expect("close failed");
2577    }
2578
2579    #[fuchsia::test]
2580    async fn test_read_only_mount_on_full_filesystem() {
2581        let device = DeviceHolder::new(FakeDevice::new(8192, TEST_DEVICE_BLOCK_SIZE));
2582        let fs =
2583            FxFilesystemBuilder::new().format(true).open(device).await.expect("new_empty failed");
2584        let root_store = fs.root_store();
2585        let root_directory = Directory::open(&root_store, root_store.root_directory_object_id())
2586            .await
2587            .expect("open failed");
2588
2589        let mut transaction = fs
2590            .root_store()
2591            .new_transaction(
2592                lock_keys![LockKey::object(
2593                    root_store.store_object_id(),
2594                    root_directory.object_id()
2595                )],
2596                Options::default(),
2597            )
2598            .await
2599            .expect("new_transaction failed");
2600        let handle = root_directory
2601            .create_child_file(&mut transaction, "test")
2602            .await
2603            .expect("create_child_file failed");
2604        transaction.commit().await.expect("commit failed");
2605
2606        let mut buf = handle.allocate_buffer(4096).await;
2607        buf.fill(0xaa);
2608        loop {
2609            if handle.write_or_append(None, buf.as_ref()).await.is_err() {
2610                break;
2611            }
2612        }
2613
2614        let max_offset = fs.allocator().maximum_offset();
2615        fs.close().await.expect("Close failed");
2616
2617        let device = fs.take_device().await;
2618        device.reopen(false);
2619        let mut buffer = device
2620            .allocate_buffer(
2621                crate::round::round_up(max_offset, TEST_DEVICE_BLOCK_SIZE).unwrap() as usize
2622            )
2623            .await;
2624        device.read(0, buffer.as_mut()).await.expect("read failed");
2625
2626        let image_data = buffer.to_vec();
2627        let device = DeviceHolder::new(
2628            FakeDevice::from_image(image_data.as_slice(), TEST_DEVICE_BLOCK_SIZE)
2629                .expect("from_image failed"),
2630        );
2631        let fs =
2632            FxFilesystemBuilder::new().read_only(true).open(device).await.expect("open failed");
2633        fs.close().await.expect("Close failed");
2634    }
2635
2636    #[test_case(SuperBlockInstance::A; "Superblock instance A")]
2637    #[test_case(SuperBlockInstance::B; "Superblock instance B")]
2638    #[fuchsia::test]
2639    async fn test_image_builder_mode_flush_on_close_sb_a(target_sb: SuperBlockInstance) {
2640        const BLOCK_SIZE: u32 = 4096;
2641        let device = DeviceHolder::new(FakeDevice::new(2048, BLOCK_SIZE));
2642
2643        // 1. Initialize in image_builder_mode
2644        device.reopen(true);
2645        let fs = FxFilesystemBuilder::new()
2646            .format(true)
2647            .image_builder_mode(Some(target_sb))
2648            .open(device)
2649            .await
2650            .expect("open failed");
2651
2652        fs.enable_allocations();
2653
2654        // 2. Finalize logic (via close)
2655        fs.device().reopen(false);
2656
2657        // 3. Write data
2658        {
2659            let root_store = fs.root_store();
2660            let root_directory =
2661                Directory::open(&root_store, root_store.root_directory_object_id())
2662                    .await
2663                    .expect("open failed");
2664
2665            let mut transaction = fs
2666                .root_store()
2667                .new_transaction(
2668                    lock_keys![LockKey::object(
2669                        root_directory.store().store_object_id(),
2670                        root_directory.object_id()
2671                    )],
2672                    Options::default(),
2673                )
2674                .await
2675                .expect("new transaction");
2676            let handle = root_directory
2677                .create_child_file(&mut transaction, "post_finalize_file")
2678                .await
2679                .expect("create file");
2680            transaction.commit().await.expect("commit");
2681
2682            let mut buf = handle.allocate_buffer(BLOCK_SIZE as usize).await;
2683            buf.fill(0xaa);
2684            handle.write_or_append(None, buf.as_ref()).await.expect("write failed");
2685        }
2686
2687        // 4. Close. Should flush to `target_sb` only.
2688        fs.close().await.expect("close failed");
2689
2690        let other_sb = target_sb.next();
2691
2692        // 5. Verify `target_sb` is valid and `other_sb` is empty.
2693        let device = fs.take_device().await;
2694        device.reopen(true); // Read-only is fine for verifying.
2695        let mut buf = device.allocate_buffer(BLOCK_SIZE as usize).await;
2696
2697        device.read(target_sb.first_extent().start, buf.as_mut()).await.expect("read target_sb");
2698        let data = buf.to_vec();
2699        assert_eq!(&data[..8], b"FxfsSupr", "target_sb should have magic bytes");
2700
2701        buf.fill(0); // Clear buffer
2702        device.read(other_sb.first_extent().start, buf.as_mut()).await.expect("read other_sb");
2703        // Expecting all zeros for `other_sb`
2704        let data2 = buf.to_vec();
2705        assert_eq!(data2, &[0; 4096], "other_sb should be zeroed");
2706    }
2707
2708    #[cfg(target_os = "fuchsia")]
2709    #[fuchsia::test(allow_stalls = false)]
2710    async fn test_trim_with_power_manager() {
2711        use anyhow::Error;
2712        use async_trait::async_trait;
2713        use fuchsia_async::TestExecutor;
2714        use futures::StreamExt;
2715
2716        TestExecutor::advance_to(fasync::MonotonicInstant::ZERO).await;
2717
2718        #[derive(Default)]
2719        struct MockPowerManager {
2720            on_battery: Mutex<bool>,
2721            event: event_listener::Event,
2722            wake_lease: Mutex<Option<zx::EventPair>>,
2723        }
2724
2725        impl MockPowerManager {
2726            fn set_on_battery(&self, v: bool) {
2727                *self.on_battery.lock() = v;
2728                self.event.notify(usize::MAX);
2729            }
2730
2731            fn is_lease_held(&self) -> bool {
2732                self.wake_lease.lock().as_ref().is_some_and(|handle| {
2733                    handle
2734                        .wait_one(
2735                            zx::Signals::EVENTPAIR_PEER_CLOSED,
2736                            zx::MonotonicInstant::INFINITE_PAST,
2737                        )
2738                        .is_err()
2739                })
2740            }
2741        }
2742
2743        impl super::PowerManager for MockPowerManager {
2744            fn watch_battery(
2745                self: Arc<Self>,
2746            ) -> futures::stream::BoxStream<'static, (bool, Option<super::WakeLease>)> {
2747                futures::stream::unfold(true, move |first| {
2748                    let this = self.clone();
2749                    async move {
2750                        if !first {
2751                            this.event.listen().await;
2752                        }
2753                        let val = *this.on_battery.lock();
2754                        let handle = if val {
2755                            None
2756                        } else {
2757                            let (h1, h2) = zx::EventPair::create();
2758                            *this.wake_lease.lock() = Some(h2);
2759                            Some(super::WakeLease::new(h1))
2760                        };
2761                        Some(((val, handle), false))
2762                    }
2763                })
2764                .boxed()
2765            }
2766        }
2767
2768        let trim_count = Arc::new(AtomicU32::new(0));
2769
2770        struct TrimTrackingDevice {
2771            inner: DeviceHolder,
2772            trim_count: Arc<AtomicU32>,
2773            power_manager: Arc<MockPowerManager>,
2774        }
2775
2776        #[async_trait]
2777        impl storage_device::Device for TrimTrackingDevice {
2778            fn allocate_buffer(&self, size: usize) -> storage_device::buffer::BufferFuture<'_> {
2779                self.inner.allocate_buffer(size)
2780            }
2781            fn block_size(&self) -> u32 {
2782                self.inner.block_size()
2783            }
2784            fn block_count(&self) -> u64 {
2785                self.inner.block_count()
2786            }
2787            async fn read_with_opts(
2788                &self,
2789                offset: u64,
2790                buffer: storage_device::buffer::MutableBufferRef<'_>,
2791                opts: storage_device::ReadOptions,
2792            ) -> Result<(), Error> {
2793                self.inner.read_with_opts(offset, buffer, opts).await
2794            }
2795            async fn write_with_opts(
2796                &self,
2797                offset: u64,
2798                buffer: storage_device::buffer::BufferRef<'_>,
2799                opts: storage_device::WriteOptions,
2800            ) -> Result<(), Error> {
2801                self.inner.write_with_opts(offset, buffer, opts).await
2802            }
2803            async fn trim(&self, range: std::ops::Range<u64>) -> Result<(), Error> {
2804                assert!(self.power_manager.is_lease_held());
2805                self.trim_count.fetch_add(1, Ordering::SeqCst);
2806                self.inner.trim(range).await
2807            }
2808            async fn flush(&self) -> Result<(), Error> {
2809                self.inner.flush().await
2810            }
2811            async fn close(&self) -> Result<(), Error> {
2812                self.inner.close().await
2813            }
2814            fn supports_trim(&self) -> bool {
2815                true
2816            }
2817            fn is_read_only(&self) -> bool {
2818                self.inner.is_read_only()
2819            }
2820            fn snapshot(&self) -> Result<DeviceHolder, Error> {
2821                Ok(DeviceHolder::new(TrimTrackingDevice {
2822                    inner: self.inner.snapshot()?,
2823                    trim_count: self.trim_count.clone(),
2824                    power_manager: self.power_manager.clone(),
2825                }))
2826            }
2827            fn reopen(&self, read_only: bool) {
2828                self.inner.reopen(read_only)
2829            }
2830        }
2831
2832        let pm = Arc::new(MockPowerManager::default());
2833
2834        // Start on battery.
2835        pm.set_on_battery(true);
2836
2837        let fake_device = FakeDevice::new(8192, TEST_DEVICE_BLOCK_SIZE);
2838        let device = DeviceHolder::new(TrimTrackingDevice {
2839            inner: DeviceHolder::new(fake_device),
2840            trim_count: trim_count.clone(),
2841            power_manager: pm.clone(),
2842        });
2843
2844        let fs = FxFilesystemBuilder::new()
2845            .format(true)
2846            .power_manager(pm.clone())
2847            .trim_config(Some((Duration::ZERO, Duration::from_millis(100))))
2848            .trim_charger_wait(Duration::from_millis(10))
2849            .open(device)
2850            .await
2851            .expect("open failed");
2852
2853        // Initially on battery, so no trim should happen.
2854        TestExecutor::advance_to(fasync::MonotonicInstant::after(
2855            Duration::from_millis(500).into(),
2856        ))
2857        .await;
2858        let _ = TestExecutor::poll_until_stalled(std::future::pending::<()>()).await;
2859
2860        assert_eq!(trim_count.load(Ordering::SeqCst), 0);
2861
2862        // Make some things to trim.
2863        {
2864            let root_store = fs.root_store();
2865            let root_directory =
2866                Directory::open(&root_store, root_store.root_directory_object_id())
2867                    .await
2868                    .expect("open failed");
2869            let mut transaction = fs
2870                .root_store()
2871                .new_transaction(
2872                    lock_keys![LockKey::object(
2873                        root_store.store_object_id(),
2874                        root_directory.object_id()
2875                    )],
2876                    Options::default(),
2877                )
2878                .await
2879                .expect("new_transaction failed");
2880            let handle = root_directory
2881                .create_child_file(&mut transaction, "test")
2882                .await
2883                .expect("create_child_file failed");
2884            transaction.commit().await.expect("commit failed");
2885            handle.allocate(0..4096).await.expect("allocate failed");
2886            // Now delete it to make it trimmable.
2887            let mut transaction = fs
2888                .root_store()
2889                .new_transaction(
2890                    lock_keys![
2891                        LockKey::object(root_store.store_object_id(), root_directory.object_id()),
2892                        LockKey::object(root_store.store_object_id(), handle.object_id()),
2893                    ],
2894                    Options::default(),
2895                )
2896                .await
2897                .expect("new_transaction failed");
2898            replace_child(&mut transaction, None, (&root_directory, "test"))
2899                .await
2900                .expect("delete failed");
2901            transaction.commit().await.expect("commit failed");
2902            fs.root_store()
2903                .tombstone_object(handle.object_id(), Options::default(), None)
2904                .await
2905                .expect("tombstone failed");
2906        }
2907
2908        // Put on external power source.
2909        pm.set_on_battery(false);
2910
2911        // Trim should start after 10ms.
2912        TestExecutor::advance_to(fasync::MonotonicInstant::after(Duration::from_millis(10).into()))
2913            .await;
2914
2915        let _ = TestExecutor::poll_until_stalled(std::future::pending::<()>()).await;
2916
2917        assert!(trim_count.load(Ordering::SeqCst) > 0);
2918
2919        // Reset trim count and take off charger.
2920        trim_count.store(0, Ordering::SeqCst);
2921        pm.set_on_battery(true);
2922
2923        // Wait and ensure no more trims.
2924        TestExecutor::advance_to(fasync::MonotonicInstant::after(
2925            Duration::from_millis(500).into(),
2926        ))
2927        .await;
2928
2929        let _ = TestExecutor::poll_until_stalled(std::future::pending::<()>()).await;
2930
2931        assert_eq!(trim_count.load(Ordering::SeqCst), 0);
2932
2933        fs.close().await.expect("close failed");
2934    }
2935
2936    #[fuchsia::test]
2937    async fn test_concurrent_do_trim_returns_error() {
2938        let device = DeviceHolder::new(FakeDevice::new(8192, TEST_DEVICE_BLOCK_SIZE));
2939        let fs = FxFilesystemBuilder::new()
2940            .trim_config(None)
2941            .format(true)
2942            .open(device)
2943            .await
2944            .expect("open failed");
2945
2946        let max_extent_size = fs.device().size() as usize;
2947        const EXTENTS_PER_BATCH: usize = usize::MAX;
2948
2949        // Hold onto trimmable extents to simulate an in-flight trim operation.
2950        let allocator = fs.allocator();
2951        let _trimmable_extents = allocator
2952            .take_for_trimming(0, max_extent_size, EXTENTS_PER_BATCH)
2953            .await
2954            .expect("take_for_trimming failed");
2955
2956        // Attempting to run do_trim concurrently while a trim is in-flight
2957        // should return FxfsError::AlreadyBound rather than panicking.
2958        let res = fs.do_trim(None).await;
2959        assert!(matches!(res, Err(e) if FxfsError::AlreadyBound.matches(&e)));
2960
2961        fs.close().await.expect("close failed");
2962    }
2963}