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