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