fs_management/
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
5//! Contains the asynchronous version of [`Filesystem`][`crate::Filesystem`].
6
7use crate::error::{QueryError, ShutdownError};
8use crate::{ComponentType, FSConfig, Options};
9use anyhow::{anyhow, bail, ensure, Context, Error};
10use fidl::endpoints::{create_endpoints, create_proxy, ClientEnd, ServerEnd};
11use fidl_fuchsia_component::{self as fcomponent, RealmMarker};
12use fidl_fuchsia_fs::AdminMarker;
13use fidl_fuchsia_fs_startup::{CheckOptions, CreateOptions, MountOptions, StartupMarker};
14use fidl_fuchsia_hardware_block_volume::VolumeMarker;
15use fuchsia_component::client::{
16    connect_to_named_protocol_at_dir_root, connect_to_protocol, connect_to_protocol_at_dir_root,
17    connect_to_protocol_at_dir_svc, open_childs_exposed_directory,
18};
19use std::collections::HashMap;
20use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
21use std::sync::Arc;
22use zx::{self as zx, AsHandleRef as _, Status};
23use {fidl_fuchsia_component_decl as fdecl, fidl_fuchsia_io as fio};
24
25/// An abstract connector for things that speak fuchsia.hardware.block.Block and similar protocols.
26pub trait BlockConnector: Send + Sync {
27    fn connect_channel_to_volume(&self, server_end: ServerEnd<VolumeMarker>) -> Result<(), Error>;
28    fn connect_volume(&self) -> Result<ClientEnd<VolumeMarker>, Error> {
29        let (client, server) = fidl::endpoints::create_endpoints();
30        self.connect_channel_to_volume(server)?;
31        Ok(client)
32    }
33    fn connect_partition(
34        &self,
35    ) -> Result<ClientEnd<fidl_fuchsia_hardware_block_partition::PartitionMarker>, Error> {
36        self.connect_volume().map(|v| ClientEnd::new(v.into_channel()))
37    }
38    fn connect_block(&self) -> Result<ClientEnd<fidl_fuchsia_hardware_block::BlockMarker>, Error> {
39        self.connect_volume().map(|v| ClientEnd::new(v.into_channel()))
40    }
41}
42
43/// Implements `BlockConnector` via a service dir.  Wraps `connect_to_named_protocol_at_dir_root`.
44#[derive(Clone, Debug)]
45pub struct DirBasedBlockConnector(fio::DirectoryProxy, String);
46
47impl DirBasedBlockConnector {
48    pub fn new(dir: fio::DirectoryProxy, path: String) -> Self {
49        Self(dir, path)
50    }
51
52    pub fn path(&self) -> &str {
53        &self.1
54    }
55}
56
57impl BlockConnector for DirBasedBlockConnector {
58    fn connect_channel_to_volume(&self, server_end: ServerEnd<VolumeMarker>) -> Result<(), Error> {
59        self.0.open(
60            self.path(),
61            fio::Flags::PROTOCOL_SERVICE,
62            &fio::Options::default(),
63            server_end.into_channel(),
64        )?;
65        Ok(())
66    }
67}
68
69impl BlockConnector for fidl_fuchsia_device::ControllerProxy {
70    fn connect_channel_to_volume(&self, server_end: ServerEnd<VolumeMarker>) -> Result<(), Error> {
71        let () = self.connect_to_device_fidl(server_end.into_channel())?;
72        Ok(())
73    }
74}
75
76impl BlockConnector for fidl_fuchsia_storage_partitions::PartitionServiceProxy {
77    fn connect_channel_to_volume(&self, server_end: ServerEnd<VolumeMarker>) -> Result<(), Error> {
78        self.connect_channel_to_volume(server_end)?;
79        Ok(())
80    }
81}
82
83// NB: We have to be specific here; we cannot do a blanket impl for AsRef<T: BlockConnector> because
84// that would conflict with a downstream crate that implements AsRef for a concrete BlockConnector
85// defined here already.
86impl<T: BlockConnector> BlockConnector for Arc<T> {
87    fn connect_channel_to_volume(&self, server_end: ServerEnd<VolumeMarker>) -> Result<(), Error> {
88        self.as_ref().connect_channel_to_volume(server_end)
89    }
90}
91
92impl<F> BlockConnector for F
93where
94    F: Fn(ServerEnd<VolumeMarker>) -> Result<(), Error> + Send + Sync,
95{
96    fn connect_channel_to_volume(&self, server_end: ServerEnd<VolumeMarker>) -> Result<(), Error> {
97        self(server_end)
98    }
99}
100
101/// Asynchronously manages a block device for filesystem operations.
102pub struct Filesystem {
103    /// The filesystem struct keeps the FSConfig in a Box<dyn> instead of holding it directly for
104    /// code size reasons. Using a type parameter instead would make monomorphized versions of the
105    /// Filesystem impl block for each filesystem type, which duplicates several multi-kilobyte
106    /// functions (get_component_exposed_dir and serve in particular) that are otherwise quite
107    /// generic over config. Clients that want to be generic over filesystem type also pay the
108    /// monomorphization cost, with some, like fshost, paying a lot.
109    config: Box<dyn FSConfig>,
110    block_connector: Box<dyn BlockConnector>,
111    component: Option<Arc<DynamicComponentInstance>>,
112}
113
114// Used to disambiguate children in our component collection.
115static COLLECTION_COUNTER: AtomicU64 = AtomicU64::new(0);
116
117impl Filesystem {
118    pub fn config(&self) -> &dyn FSConfig {
119        self.config.as_ref()
120    }
121
122    pub fn into_config(self) -> Box<dyn FSConfig> {
123        self.config
124    }
125
126    /// Creates a new `Filesystem`.
127    pub fn new<B: BlockConnector + 'static, FSC: FSConfig>(
128        block_connector: B,
129        config: FSC,
130    ) -> Self {
131        Self::from_boxed_config(Box::new(block_connector), Box::new(config))
132    }
133
134    /// Creates a new `Filesystem`.
135    pub fn from_boxed_config(
136        block_connector: Box<dyn BlockConnector>,
137        config: Box<dyn FSConfig>,
138    ) -> Self {
139        Self { config, block_connector, component: None }
140    }
141
142    /// If the filesystem is a currently running component, returns its (relative) moniker.
143    pub fn get_component_moniker(&self) -> Option<String> {
144        Some(match self.config.options().component_type {
145            ComponentType::StaticChild => self.config.options().component_name.to_string(),
146            ComponentType::DynamicChild { .. } => {
147                let component = self.component.as_ref()?;
148                format!("{}:{}", component.collection, component.name)
149            }
150        })
151    }
152
153    async fn get_component_exposed_dir(&mut self) -> Result<fio::DirectoryProxy, Error> {
154        let options = self.config.options();
155        let component_name = options.component_name;
156        match options.component_type {
157            ComponentType::StaticChild => open_childs_exposed_directory(component_name, None).await,
158            ComponentType::DynamicChild { collection_name } => {
159                if let Some(component) = &self.component {
160                    return open_childs_exposed_directory(
161                        component.name.clone(),
162                        Some(component.collection.clone()),
163                    )
164                    .await;
165                }
166
167                // We need a unique name, so we pull in the process Koid here since it's possible
168                // for the same binary in a component to be launched multiple times and we don't
169                // want to collide with children created by other processes.
170                let name = format!(
171                    "{}-{}-{}",
172                    component_name,
173                    fuchsia_runtime::process_self().get_koid().unwrap().raw_koid(),
174                    COLLECTION_COUNTER.fetch_add(1, Ordering::Relaxed)
175                );
176
177                let collection_ref = fdecl::CollectionRef { name: collection_name };
178                let child_decls = vec![
179                    fdecl::Child {
180                        name: Some(format!("{}-relative", name)),
181                        url: Some(format!("#meta/{}.cm", component_name)),
182                        startup: Some(fdecl::StartupMode::Lazy),
183                        ..Default::default()
184                    },
185                    fdecl::Child {
186                        name: Some(name),
187                        url: Some(format!(
188                            "fuchsia-boot:///{}#meta/{}.cm",
189                            component_name, component_name
190                        )),
191                        startup: Some(fdecl::StartupMode::Lazy),
192                        ..Default::default()
193                    },
194                ];
195                let realm_proxy = connect_to_protocol::<RealmMarker>()?;
196                for child_decl in child_decls {
197                    // Launch a new component in our collection.
198                    realm_proxy
199                        .create_child(
200                            &collection_ref,
201                            &child_decl,
202                            fcomponent::CreateChildArgs::default(),
203                        )
204                        .await?
205                        .map_err(|e| anyhow!("create_child failed: {:?}", e))?;
206
207                    let component = Arc::new(DynamicComponentInstance {
208                        name: child_decl.name.unwrap(),
209                        collection: collection_ref.name.clone(),
210                        should_not_drop: AtomicBool::new(false),
211                    });
212
213                    if let Ok(proxy) = open_childs_exposed_directory(
214                        component.name.clone(),
215                        Some(component.collection.clone()),
216                    )
217                    .await
218                    {
219                        self.component = Some(component);
220                        return Ok(proxy);
221                    }
222                }
223                Err(anyhow!("Failed to open exposed directory"))
224            }
225        }
226    }
227
228    /// Calls fuchsia.fs.startup/Startup.Format on the configured filesystem component.
229    ///
230    /// Which component is used and the options passed to it are controlled by the config this
231    /// `Filesystem` was created with.
232    ///
233    /// See [`FSConfig`].
234    ///
235    /// # Errors
236    ///
237    /// Returns any errors from the Format method. Also returns an error if the startup protocol is
238    /// not found, if it couldn't launch or find the filesystem component, or if it couldn't get
239    /// the block device channel.
240    pub async fn format(&mut self) -> Result<(), Error> {
241        let channel = self.block_connector.connect_block()?;
242
243        let exposed_dir = self.get_component_exposed_dir().await?;
244        let proxy = connect_to_protocol_at_dir_root::<StartupMarker>(&exposed_dir)?;
245        proxy
246            .format(channel, &self.config().options().format_options)
247            .await?
248            .map_err(Status::from_raw)?;
249
250        Ok(())
251    }
252
253    /// Calls fuchsia.fs.startup/Startup.Check on the configured filesystem component.
254    ///
255    /// Which component is used and the options passed to it are controlled by the config this
256    /// `Filesystem` was created with.
257    ///
258    /// See [`FSConfig`].
259    ///
260    /// # Errors
261    ///
262    /// Returns any errors from the Check method. Also returns an error if the startup protocol is
263    /// not found, if it couldn't launch or find the filesystem component, or if it couldn't get
264    /// the block device channel.
265    pub async fn fsck(&mut self) -> Result<(), Error> {
266        let channel = self.block_connector.connect_block()?;
267        let exposed_dir = self.get_component_exposed_dir().await?;
268        let proxy = connect_to_protocol_at_dir_root::<StartupMarker>(&exposed_dir)?;
269        proxy.check(channel, CheckOptions::default()).await?.map_err(Status::from_raw)?;
270        Ok(())
271    }
272
273    /// Serves the filesystem on the block device and returns a [`ServingSingleVolumeFilesystem`]
274    /// representing the running filesystem component.
275    ///
276    /// # Errors
277    ///
278    /// Returns [`Err`] if serving the filesystem failed.
279    pub async fn serve(&mut self) -> Result<ServingSingleVolumeFilesystem, Error> {
280        if self.config.is_multi_volume() {
281            bail!("Can't serve a multivolume filesystem; use serve_multi_volume");
282        }
283        let Options { start_options, reuse_component_after_serving, .. } = self.config.options();
284
285        let exposed_dir = self.get_component_exposed_dir().await?;
286        let proxy = connect_to_protocol_at_dir_root::<StartupMarker>(&exposed_dir)?;
287        proxy
288            .start(self.block_connector.connect_block()?, start_options)
289            .await?
290            .map_err(Status::from_raw)?;
291
292        let (root_dir, server_end) = create_endpoints::<fio::NodeMarker>();
293        exposed_dir.open(
294            "root",
295            fio::PERM_READABLE | fio::Flags::PERM_INHERIT_WRITE | fio::Flags::PERM_INHERIT_EXECUTE,
296            &Default::default(),
297            server_end.into_channel(),
298        )?;
299        let component = self.component.clone();
300        if !reuse_component_after_serving {
301            self.component = None;
302        }
303        Ok(ServingSingleVolumeFilesystem {
304            component,
305            exposed_dir: Some(exposed_dir),
306            root_dir: ClientEnd::<fio::DirectoryMarker>::new(root_dir.into_channel()).into_proxy(),
307            binding: None,
308        })
309    }
310
311    /// Serves the filesystem on the block device and returns a [`ServingMultiVolumeFilesystem`]
312    /// representing the running filesystem component.  No volumes are opened; clients have to do
313    /// that explicitly.
314    ///
315    /// # Errors
316    ///
317    /// Returns [`Err`] if serving the filesystem failed.
318    pub async fn serve_multi_volume(&mut self) -> Result<ServingMultiVolumeFilesystem, Error> {
319        if !self.config.is_multi_volume() {
320            bail!("Can't serve_multi_volume a single-volume filesystem; use serve");
321        }
322
323        let exposed_dir = self.get_component_exposed_dir().await?;
324        let proxy = connect_to_protocol_at_dir_root::<StartupMarker>(&exposed_dir)?;
325        proxy
326            .start(self.block_connector.connect_block()?, self.config.options().start_options)
327            .await?
328            .map_err(Status::from_raw)?;
329
330        Ok(ServingMultiVolumeFilesystem {
331            component: self.component.clone(),
332            exposed_dir: Some(exposed_dir),
333            volumes: HashMap::default(),
334        })
335    }
336}
337
338// Destroys the child when dropped.
339struct DynamicComponentInstance {
340    name: String,
341    collection: String,
342    should_not_drop: AtomicBool,
343}
344
345impl DynamicComponentInstance {
346    fn forget(&self) {
347        self.should_not_drop.store(true, Ordering::Relaxed);
348    }
349}
350
351impl Drop for DynamicComponentInstance {
352    fn drop(&mut self) {
353        if self.should_not_drop.load(Ordering::Relaxed) {
354            return;
355        }
356        if let Ok(realm_proxy) = connect_to_protocol::<RealmMarker>() {
357            let _ = realm_proxy.destroy_child(&fdecl::ChildRef {
358                name: self.name.clone(),
359                collection: Some(self.collection.clone()),
360            });
361        }
362    }
363}
364
365/// Manages the binding of a `fuchsia_io::DirectoryProxy` into the local namespace.  When the object
366/// is dropped, the binding is removed.
367#[derive(Default)]
368pub struct NamespaceBinding(String);
369
370impl NamespaceBinding {
371    pub fn create(root_dir: &fio::DirectoryProxy, path: String) -> Result<NamespaceBinding, Error> {
372        let (client_end, server_end) = create_endpoints();
373        root_dir.clone(ServerEnd::new(server_end.into_channel()))?;
374        let namespace = fdio::Namespace::installed()?;
375        namespace.bind(&path, client_end)?;
376        Ok(Self(path))
377    }
378}
379
380impl std::ops::Deref for NamespaceBinding {
381    type Target = str;
382    fn deref(&self) -> &Self::Target {
383        &self.0
384    }
385}
386
387impl Drop for NamespaceBinding {
388    fn drop(&mut self) {
389        if let Ok(namespace) = fdio::Namespace::installed() {
390            let _ = namespace.unbind(&self.0);
391        }
392    }
393}
394
395// TODO(https://fxbug.dev/42174810): Soft migration; remove this after completion
396pub type ServingFilesystem = ServingSingleVolumeFilesystem;
397
398/// Asynchronously manages a serving filesystem. Created from [`Filesystem::serve()`].
399pub struct ServingSingleVolumeFilesystem {
400    component: Option<Arc<DynamicComponentInstance>>,
401    // exposed_dir will always be Some, except when the filesystem is shutting down.
402    exposed_dir: Option<fio::DirectoryProxy>,
403    root_dir: fio::DirectoryProxy,
404
405    // The path in the local namespace that this filesystem is bound to (optional).
406    binding: Option<NamespaceBinding>,
407}
408
409impl ServingSingleVolumeFilesystem {
410    /// Returns a proxy to the exposed directory of the serving filesystem.
411    pub fn exposed_dir(&self) -> &fio::DirectoryProxy {
412        self.exposed_dir.as_ref().unwrap()
413    }
414
415    /// Returns a proxy to the root directory of the serving filesystem.
416    pub fn root(&self) -> &fio::DirectoryProxy {
417        &self.root_dir
418    }
419
420    /// Binds the root directory being served by this filesystem to a path in the local namespace.
421    /// The path must be absolute, containing no "." nor ".." entries.  The binding will be dropped
422    /// when self is dropped.  Only one binding is supported.
423    ///
424    /// # Errors
425    ///
426    /// Returns [`Err`] if binding failed.
427    pub fn bind_to_path(&mut self, path: &str) -> Result<(), Error> {
428        ensure!(self.binding.is_none(), "Already bound");
429        self.binding = Some(NamespaceBinding::create(&self.root_dir, path.to_string())?);
430        Ok(())
431    }
432
433    pub fn bound_path(&self) -> Option<&str> {
434        self.binding.as_deref()
435    }
436
437    /// Returns a [`FilesystemInfo`] object containing information about the serving filesystem.
438    ///
439    /// # Errors
440    ///
441    /// Returns [`Err`] if querying the filesystem failed.
442    pub async fn query(&self) -> Result<Box<fio::FilesystemInfo>, QueryError> {
443        let (status, info) = self.root_dir.query_filesystem().await?;
444        Status::ok(status).map_err(QueryError::DirectoryQuery)?;
445        info.ok_or(QueryError::DirectoryEmptyResult)
446    }
447
448    /// Take the exposed dir from this filesystem instance, dropping the management struct without
449    /// shutting the filesystem down. This leaves the caller with the responsibility of shutting
450    /// down the filesystem, and the filesystem component if necessary.
451    pub fn take_exposed_dir(mut self) -> fio::DirectoryProxy {
452        self.component.take().expect("BUG: component missing").forget();
453        self.exposed_dir.take().expect("BUG: exposed dir missing")
454    }
455
456    /// Attempts to shutdown the filesystem using the
457    /// [`fidl_fuchsia_fs::AdminProxy::shutdown()`] FIDL method and waiting for the filesystem
458    /// process to terminate.
459    ///
460    /// # Errors
461    ///
462    /// Returns [`Err`] if the shutdown failed or the filesystem process did not terminate.
463    pub async fn shutdown(mut self) -> Result<(), ShutdownError> {
464        connect_to_protocol_at_dir_root::<fidl_fuchsia_fs::AdminMarker>(
465            &self.exposed_dir.take().expect("BUG: exposed dir missing"),
466        )?
467        .shutdown()
468        .await?;
469        Ok(())
470    }
471
472    /// Attempts to kill the filesystem process and waits for the process to terminate.
473    ///
474    /// # Errors
475    ///
476    /// Returns [`Err`] if the filesystem process could not be terminated. There is no way to
477    /// recover the [`Filesystem`] from this error.
478    pub async fn kill(self) -> Result<(), Error> {
479        // For components, just shut down the filesystem.
480        // TODO(https://fxbug.dev/293949323): Figure out a way to make this more abrupt - the use-cases are
481        // either testing or when the filesystem isn't responding.
482        self.shutdown().await?;
483        Ok(())
484    }
485}
486
487impl Drop for ServingSingleVolumeFilesystem {
488    fn drop(&mut self) {
489        // Make a best effort attempt to shut down to the filesystem, if we need to.
490        if let Some(exposed_dir) = self.exposed_dir.take() {
491            if let Ok(proxy) =
492                connect_to_protocol_at_dir_root::<fidl_fuchsia_fs::AdminMarker>(&exposed_dir)
493            {
494                let _ = proxy.shutdown();
495            }
496        }
497    }
498}
499
500/// Asynchronously manages a serving multivolume filesystem. Created from
501/// [`Filesystem::serve_multi_volume()`].
502pub struct ServingMultiVolumeFilesystem {
503    component: Option<Arc<DynamicComponentInstance>>,
504    // exposed_dir will always be Some, except in Self::shutdown.
505    exposed_dir: Option<fio::DirectoryProxy>,
506    volumes: HashMap<String, ServingVolume>,
507}
508
509/// Represents an opened volume in a [`ServingMultiVolumeFilesystem'] instance.
510pub struct ServingVolume {
511    root_dir: fio::DirectoryProxy,
512    binding: Option<NamespaceBinding>,
513    exposed_dir: fio::DirectoryProxy,
514}
515
516impl ServingVolume {
517    /// Returns a proxy to the root directory of the serving volume.
518    pub fn root(&self) -> &fio::DirectoryProxy {
519        &self.root_dir
520    }
521
522    /// Returns a proxy to the exposed directory of the serving volume.
523    pub fn exposed_dir(&self) -> &fio::DirectoryProxy {
524        &self.exposed_dir
525    }
526
527    /// Binds the root directory being served by this filesystem to a path in the local namespace.
528    /// The path must be absolute, containing no "." nor ".." entries.  The binding will be dropped
529    /// when self is dropped, or when unbind_path is called.  Only one binding is supported.
530    ///
531    /// # Errors
532    ///
533    /// Returns [`Err`] if binding failed, or if a binding already exists.
534    pub fn bind_to_path(&mut self, path: &str) -> Result<(), Error> {
535        ensure!(self.binding.is_none(), "Already bound");
536        self.binding = Some(NamespaceBinding::create(&self.root_dir, path.to_string())?);
537        Ok(())
538    }
539
540    /// Remove the namespace binding to the root directory being served by this volume, if there is
541    /// one. If there is no binding, this function does nothing. After this, it is safe to call
542    /// bind_to_path again.
543    pub fn unbind_path(&mut self) {
544        let _ = self.binding.take();
545    }
546
547    pub fn bound_path(&self) -> Option<&str> {
548        self.binding.as_deref()
549    }
550
551    /// Returns a [`FilesystemInfo`] object containing information about the serving volume.
552    ///
553    /// # Errors
554    ///
555    /// Returns [`Err`] if querying the filesystem failed.
556    pub async fn query(&self) -> Result<Box<fio::FilesystemInfo>, QueryError> {
557        let (status, info) = self.root_dir.query_filesystem().await?;
558        Status::ok(status).map_err(QueryError::DirectoryQuery)?;
559        info.ok_or(QueryError::DirectoryEmptyResult)
560    }
561}
562
563impl ServingMultiVolumeFilesystem {
564    /// Gets a reference to the given volume, if it's already open.
565    pub fn volume(&self, volume: &str) -> Option<&ServingVolume> {
566        self.volumes.get(volume)
567    }
568
569    /// Gets a mutable reference to the given volume, if it's already open.
570    pub fn volume_mut(&mut self, volume: &str) -> Option<&mut ServingVolume> {
571        self.volumes.get_mut(volume)
572    }
573
574    pub fn close_volume(&mut self, volume: &str) {
575        self.volumes.remove(volume);
576    }
577
578    /// Attempts to shutdown the filesystem using the [`fidl_fuchsia_fs::AdminProxy::shutdown()`]
579    /// FIDL method. Fails if the volume is not already open.
580    pub async fn shutdown_volume(&mut self, volume: &str) -> Result<(), Error> {
581        ensure!(self.volumes.contains_key(volume), "Volume not mounted");
582        let serving_vol = self.volume(volume).unwrap();
583        let admin_proxy = connect_to_protocol_at_dir_svc::<AdminMarker>(serving_vol.exposed_dir())?;
584        admin_proxy.shutdown().await.context("failed to shutdown volume")?;
585        self.close_volume(volume);
586        Ok(())
587    }
588
589    /// Returns whether the given volume exists.
590    pub async fn has_volume(&mut self, volume: &str) -> Result<bool, Error> {
591        if self.volumes.contains_key(volume) {
592            return Ok(true);
593        }
594        let path = format!("volumes/{}", volume);
595        fuchsia_fs::directory::open_node(
596            self.exposed_dir.as_ref().unwrap(),
597            &path,
598            fio::Flags::PROTOCOL_NODE,
599        )
600        .await
601        .map(|_| true)
602        .or_else(|e| {
603            if let fuchsia_fs::node::OpenError::OpenError(status) = &e {
604                if *status == zx::Status::NOT_FOUND {
605                    return Ok(false);
606                }
607            }
608            Err(e.into())
609        })
610    }
611
612    /// Creates and mounts the volume.  Fails if the volume already exists.
613    /// If `options.crypt` is set, the volume will be encrypted using the provided Crypt instance.
614    /// If `options.as_blob` is set, creates a blob volume that is mounted as a blob filesystem.
615    pub async fn create_volume(
616        &mut self,
617        volume: &str,
618        create_options: CreateOptions,
619        options: MountOptions,
620    ) -> Result<&mut ServingVolume, Error> {
621        ensure!(!self.volumes.contains_key(volume), "Already bound");
622        let (exposed_dir, server) = create_proxy::<fio::DirectoryMarker>();
623        connect_to_protocol_at_dir_root::<fidl_fuchsia_fs_startup::VolumesMarker>(
624            self.exposed_dir.as_ref().unwrap(),
625        )?
626        .create(volume, server, create_options, options)
627        .await?
628        .map_err(|e| anyhow!(zx::Status::from_raw(e)))?;
629        self.insert_volume(volume.to_string(), exposed_dir).await
630    }
631
632    /// Deletes the volume. Fails if the volume is already mounted.
633    pub async fn remove_volume(&mut self, volume: &str) -> Result<(), Error> {
634        ensure!(!self.volumes.contains_key(volume), "Already bound");
635        connect_to_protocol_at_dir_root::<fidl_fuchsia_fs_startup::VolumesMarker>(
636            self.exposed_dir.as_ref().unwrap(),
637        )?
638        .remove(volume)
639        .await?
640        .map_err(|e| anyhow!(zx::Status::from_raw(e)))
641    }
642
643    /// Mounts an existing volume.  Fails if the volume is already mounted or doesn't exist.
644    /// If `crypt` is set, the volume will be decrypted using the provided Crypt instance.
645    pub async fn open_volume(
646        &mut self,
647        volume: &str,
648        options: MountOptions,
649    ) -> Result<&mut ServingVolume, Error> {
650        ensure!(!self.volumes.contains_key(volume), "Already bound");
651        let (exposed_dir, server) = create_proxy::<fio::DirectoryMarker>();
652        let path = format!("volumes/{}", volume);
653        connect_to_named_protocol_at_dir_root::<fidl_fuchsia_fs_startup::VolumeMarker>(
654            self.exposed_dir.as_ref().unwrap(),
655            &path,
656        )?
657        .mount(server, options)
658        .await?
659        .map_err(|e| anyhow!(zx::Status::from_raw(e)))?;
660
661        self.insert_volume(volume.to_string(), exposed_dir).await
662    }
663
664    /// Sets the max byte limit for a volume. Fails if the volume is not mounted.
665    pub async fn set_byte_limit(&self, volume: &str, byte_limit: u64) -> Result<(), Error> {
666        ensure!(self.volumes.contains_key(volume), "Volume not mounted");
667        if byte_limit == 0 {
668            return Ok(());
669        }
670        let path = format!("volumes/{}", volume);
671        connect_to_named_protocol_at_dir_root::<fidl_fuchsia_fs_startup::VolumeMarker>(
672            self.exposed_dir.as_ref().unwrap(),
673            &path,
674        )?
675        .set_limit(byte_limit)
676        .await?
677        .map_err(|e| anyhow!(zx::Status::from_raw(e)))
678    }
679
680    pub async fn check_volume(
681        &mut self,
682        volume: &str,
683        crypt: Option<ClientEnd<fidl_fuchsia_fxfs::CryptMarker>>,
684    ) -> Result<(), Error> {
685        ensure!(!self.volumes.contains_key(volume), "Already bound");
686        let path = format!("volumes/{}", volume);
687        connect_to_named_protocol_at_dir_root::<fidl_fuchsia_fs_startup::VolumeMarker>(
688            self.exposed_dir.as_ref().unwrap(),
689            &path,
690        )?
691        .check(fidl_fuchsia_fs_startup::CheckOptions { crypt, ..Default::default() })
692        .await?
693        .map_err(|e| anyhow!(zx::Status::from_raw(e)))?;
694        Ok(())
695    }
696
697    async fn insert_volume(
698        &mut self,
699        volume: String,
700        exposed_dir: fio::DirectoryProxy,
701    ) -> Result<&mut ServingVolume, Error> {
702        let (root_dir, server_end) = create_endpoints::<fio::NodeMarker>();
703        exposed_dir.open(
704            "root",
705            fio::PERM_READABLE | fio::Flags::PERM_INHERIT_WRITE | fio::Flags::PERM_INHERIT_EXECUTE,
706            &Default::default(),
707            server_end.into_channel(),
708        )?;
709        Ok(self.volumes.entry(volume).or_insert(ServingVolume {
710            root_dir: ClientEnd::<fio::DirectoryMarker>::new(root_dir.into_channel()).into_proxy(),
711            binding: None,
712            exposed_dir,
713        }))
714    }
715
716    /// Provides access to the internal |exposed_dir| for use in testing
717    /// callsites which need directory access.
718    pub fn exposed_dir(&self) -> &fio::DirectoryProxy {
719        self.exposed_dir.as_ref().expect("BUG: exposed dir missing")
720    }
721
722    /// Attempts to shutdown the filesystem using the [`fidl_fuchsia_fs::AdminProxy::shutdown()`]
723    /// FIDL method.
724    ///
725    /// # Errors
726    ///
727    /// Returns [`Err`] if the shutdown failed.
728    pub async fn shutdown(mut self) -> Result<(), ShutdownError> {
729        connect_to_protocol_at_dir_root::<fidl_fuchsia_fs::AdminMarker>(
730            // Take exposed_dir so we don't attempt to shut down again in Drop.
731            &self.exposed_dir.take().expect("BUG: exposed dir missing"),
732        )?
733        .shutdown()
734        .await?;
735        Ok(())
736    }
737
738    /// Take the exposed dir from this filesystem instance, dropping the management struct without
739    /// shutting the filesystem down. This leaves the caller with the responsibility of shutting
740    /// down the filesystem, and the filesystem component if necessary.
741    pub fn take_exposed_dir(mut self) -> fio::DirectoryProxy {
742        self.component.take().expect("BUG: missing component").forget();
743        self.exposed_dir.take().expect("BUG: exposed dir missing")
744    }
745}
746
747impl Drop for ServingMultiVolumeFilesystem {
748    fn drop(&mut self) {
749        if let Some(exposed_dir) = self.exposed_dir.take() {
750            // Make a best effort attempt to shut down to the filesystem.
751            if let Ok(proxy) =
752                connect_to_protocol_at_dir_root::<fidl_fuchsia_fs::AdminMarker>(&exposed_dir)
753            {
754                let _ = proxy.shutdown();
755            }
756        }
757    }
758}
759
760#[cfg(test)]
761mod tests {
762    use super::*;
763    use crate::{BlobCompression, BlobEvictionPolicy, Blobfs, F2fs, Fxfs, Minfs};
764    use fuchsia_async as fasync;
765    use ramdevice_client::RamdiskClient;
766    use std::io::{Read as _, Write as _};
767    use std::time::Duration;
768
769    async fn ramdisk(block_size: u64) -> RamdiskClient {
770        RamdiskClient::create(block_size, 1 << 16).await.unwrap()
771    }
772
773    async fn new_fs<FSC: FSConfig>(ramdisk: &mut RamdiskClient, config: FSC) -> Filesystem {
774        Filesystem::new(ramdisk.take_controller().unwrap(), config)
775    }
776
777    #[fuchsia::test]
778    async fn blobfs_custom_config() {
779        let block_size = 512;
780        let mut ramdisk = ramdisk(block_size).await;
781        let config = Blobfs {
782            verbose: true,
783            readonly: true,
784            write_compression_algorithm: BlobCompression::Uncompressed,
785            cache_eviction_policy_override: BlobEvictionPolicy::EvictImmediately,
786            ..Default::default()
787        };
788        let mut blobfs = new_fs(&mut ramdisk, config).await;
789
790        blobfs.format().await.expect("failed to format blobfs");
791        blobfs.fsck().await.expect("failed to fsck blobfs");
792        let _ = blobfs.serve().await.expect("failed to serve blobfs");
793
794        ramdisk.destroy().await.expect("failed to destroy ramdisk");
795    }
796
797    #[fuchsia::test]
798    async fn blobfs_format_fsck_success() {
799        let block_size = 512;
800        let mut ramdisk = ramdisk(block_size).await;
801        let mut blobfs = new_fs(&mut ramdisk, Blobfs::default()).await;
802
803        blobfs.format().await.expect("failed to format blobfs");
804        blobfs.fsck().await.expect("failed to fsck blobfs");
805
806        ramdisk.destroy().await.expect("failed to destroy ramdisk");
807    }
808
809    #[fuchsia::test]
810    async fn blobfs_format_serve_write_query_restart_read_shutdown() {
811        let block_size = 512;
812        let mut ramdisk = ramdisk(block_size).await;
813        let mut blobfs = new_fs(&mut ramdisk, Blobfs::default()).await;
814
815        blobfs.format().await.expect("failed to format blobfs");
816
817        let serving = blobfs.serve().await.expect("failed to serve blobfs the first time");
818
819        // snapshot of FilesystemInfo
820        let fs_info1 =
821            serving.query().await.expect("failed to query filesystem info after first serving");
822
823        // pre-generated merkle test fixture data
824        let merkle = "be901a14ec42ee0a8ee220eb119294cdd40d26d573139ee3d51e4430e7d08c28";
825        let content = String::from("test content").into_bytes();
826
827        {
828            let test_file = fuchsia_fs::directory::open_file(
829                serving.root(),
830                merkle,
831                fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_WRITABLE,
832            )
833            .await
834            .expect("failed to create test file");
835            let () = test_file
836                .resize(content.len() as u64)
837                .await
838                .expect("failed to send resize FIDL")
839                .map_err(Status::from_raw)
840                .expect("failed to resize file");
841            let _: u64 = test_file
842                .write(&content)
843                .await
844                .expect("failed to write to test file")
845                .map_err(Status::from_raw)
846                .expect("write error");
847        }
848
849        // check against the snapshot FilesystemInfo
850        let fs_info2 = serving.query().await.expect("failed to query filesystem info after write");
851        assert_eq!(
852            fs_info2.used_bytes - fs_info1.used_bytes,
853            fs_info2.block_size as u64 // assuming content < 8K
854        );
855
856        serving.shutdown().await.expect("failed to shutdown blobfs the first time");
857        let serving = blobfs.serve().await.expect("failed to serve blobfs the second time");
858        {
859            let test_file =
860                fuchsia_fs::directory::open_file(serving.root(), merkle, fio::PERM_READABLE)
861                    .await
862                    .expect("failed to open test file");
863            let read_content =
864                fuchsia_fs::file::read(&test_file).await.expect("failed to read from test file");
865            assert_eq!(content, read_content);
866        }
867
868        // once more check against the snapshot FilesystemInfo
869        let fs_info3 = serving.query().await.expect("failed to query filesystem info after read");
870        assert_eq!(
871            fs_info3.used_bytes - fs_info1.used_bytes,
872            fs_info3.block_size as u64 // assuming content < 8K
873        );
874
875        serving.shutdown().await.expect("failed to shutdown blobfs the second time");
876
877        ramdisk.destroy().await.expect("failed to destroy ramdisk");
878    }
879
880    #[fuchsia::test]
881    async fn blobfs_bind_to_path() {
882        let block_size = 512;
883        let merkle = "be901a14ec42ee0a8ee220eb119294cdd40d26d573139ee3d51e4430e7d08c28";
884        let test_content = b"test content";
885        let mut ramdisk = ramdisk(block_size).await;
886        let mut blobfs = new_fs(&mut ramdisk, Blobfs::default()).await;
887
888        blobfs.format().await.expect("failed to format blobfs");
889        let mut serving = blobfs.serve().await.expect("failed to serve blobfs");
890        serving.bind_to_path("/test-blobfs-path").expect("bind_to_path failed");
891        let test_path = format!("/test-blobfs-path/{}", merkle);
892
893        {
894            let mut file = std::fs::File::create(&test_path).expect("failed to create test file");
895            file.set_len(test_content.len() as u64).expect("failed to set size");
896            file.write_all(test_content).expect("write bytes");
897        }
898
899        {
900            let mut file = std::fs::File::open(&test_path).expect("failed to open test file");
901            let mut buf = Vec::new();
902            file.read_to_end(&mut buf).expect("failed to read test file");
903            assert_eq!(buf, test_content);
904        }
905
906        serving.shutdown().await.expect("failed to shutdown blobfs");
907
908        std::fs::File::open(&test_path).expect_err("test file was not unbound");
909    }
910
911    #[fuchsia::test]
912    async fn minfs_custom_config() {
913        let block_size = 512;
914        let mut ramdisk = ramdisk(block_size).await;
915        let config = Minfs {
916            verbose: true,
917            readonly: true,
918            fsck_after_every_transaction: true,
919            ..Default::default()
920        };
921        let mut minfs = new_fs(&mut ramdisk, config).await;
922
923        minfs.format().await.expect("failed to format minfs");
924        minfs.fsck().await.expect("failed to fsck minfs");
925        let _ = minfs.serve().await.expect("failed to serve minfs");
926
927        ramdisk.destroy().await.expect("failed to destroy ramdisk");
928    }
929
930    #[fuchsia::test]
931    async fn minfs_format_fsck_success() {
932        let block_size = 8192;
933        let mut ramdisk = ramdisk(block_size).await;
934        let mut minfs = new_fs(&mut ramdisk, Minfs::default()).await;
935
936        minfs.format().await.expect("failed to format minfs");
937        minfs.fsck().await.expect("failed to fsck minfs");
938
939        ramdisk.destroy().await.expect("failed to destroy ramdisk");
940    }
941
942    #[fuchsia::test]
943    async fn minfs_format_serve_write_query_restart_read_shutdown() {
944        let block_size = 8192;
945        let mut ramdisk = ramdisk(block_size).await;
946        let mut minfs = new_fs(&mut ramdisk, Minfs::default()).await;
947
948        minfs.format().await.expect("failed to format minfs");
949        let serving = minfs.serve().await.expect("failed to serve minfs the first time");
950
951        // snapshot of FilesystemInfo
952        let fs_info1 =
953            serving.query().await.expect("failed to query filesystem info after first serving");
954
955        let filename = "test_file";
956        let content = String::from("test content").into_bytes();
957
958        {
959            let test_file = fuchsia_fs::directory::open_file(
960                serving.root(),
961                filename,
962                fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_WRITABLE,
963            )
964            .await
965            .expect("failed to create test file");
966            let _: u64 = test_file
967                .write(&content)
968                .await
969                .expect("failed to write to test file")
970                .map_err(Status::from_raw)
971                .expect("write error");
972        }
973
974        // check against the snapshot FilesystemInfo
975        let fs_info2 = serving.query().await.expect("failed to query filesystem info after write");
976        assert_eq!(
977            fs_info2.used_bytes - fs_info1.used_bytes,
978            fs_info2.block_size as u64 // assuming content < 8K
979        );
980
981        serving.shutdown().await.expect("failed to shutdown minfs the first time");
982        let serving = minfs.serve().await.expect("failed to serve minfs the second time");
983
984        {
985            let test_file =
986                fuchsia_fs::directory::open_file(serving.root(), filename, fio::PERM_READABLE)
987                    .await
988                    .expect("failed to open test file");
989            let read_content =
990                fuchsia_fs::file::read(&test_file).await.expect("failed to read from test file");
991            assert_eq!(content, read_content);
992        }
993
994        // once more check against the snapshot FilesystemInfo
995        let fs_info3 = serving.query().await.expect("failed to query filesystem info after read");
996        assert_eq!(
997            fs_info3.used_bytes - fs_info1.used_bytes,
998            fs_info3.block_size as u64 // assuming content < 8K
999        );
1000
1001        let _ = serving.shutdown().await.expect("failed to shutdown minfs the second time");
1002
1003        ramdisk.destroy().await.expect("failed to destroy ramdisk");
1004    }
1005
1006    #[fuchsia::test]
1007    async fn minfs_bind_to_path() {
1008        let block_size = 8192;
1009        let test_content = b"test content";
1010        let mut ramdisk = ramdisk(block_size).await;
1011        let mut minfs = new_fs(&mut ramdisk, Minfs::default()).await;
1012
1013        minfs.format().await.expect("failed to format minfs");
1014        let mut serving = minfs.serve().await.expect("failed to serve minfs");
1015        serving.bind_to_path("/test-minfs-path").expect("bind_to_path failed");
1016        let test_path = "/test-minfs-path/test_file";
1017
1018        {
1019            let mut file = std::fs::File::create(test_path).expect("failed to create test file");
1020            file.write_all(test_content).expect("write bytes");
1021        }
1022
1023        {
1024            let mut file = std::fs::File::open(test_path).expect("failed to open test file");
1025            let mut buf = Vec::new();
1026            file.read_to_end(&mut buf).expect("failed to read test file");
1027            assert_eq!(buf, test_content);
1028        }
1029
1030        serving.shutdown().await.expect("failed to shutdown minfs");
1031
1032        std::fs::File::open(test_path).expect_err("test file was not unbound");
1033    }
1034
1035    #[fuchsia::test]
1036    async fn minfs_take_exposed_dir_does_not_drop() {
1037        let block_size = 512;
1038        let test_content = b"test content";
1039        let test_file_name = "test-file";
1040        let mut ramdisk = ramdisk(block_size).await;
1041        let mut minfs = new_fs(&mut ramdisk, Minfs::default()).await;
1042
1043        minfs.format().await.expect("failed to format fxfs");
1044
1045        let fs = minfs.serve().await.expect("failed to serve fxfs");
1046        let file = {
1047            let file = fuchsia_fs::directory::open_file(
1048                fs.root(),
1049                test_file_name,
1050                fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_READABLE | fio::PERM_WRITABLE,
1051            )
1052            .await
1053            .unwrap();
1054            fuchsia_fs::file::write(&file, test_content).await.unwrap();
1055            file.close().await.expect("close fidl error").expect("close error");
1056            fuchsia_fs::directory::open_file(fs.root(), test_file_name, fio::PERM_READABLE)
1057                .await
1058                .unwrap()
1059        };
1060
1061        let exposed_dir = fs.take_exposed_dir();
1062
1063        assert_eq!(fuchsia_fs::file::read(&file).await.unwrap(), test_content);
1064
1065        connect_to_protocol_at_dir_root::<fidl_fuchsia_fs::AdminMarker>(&exposed_dir)
1066            .expect("connecting to admin marker")
1067            .shutdown()
1068            .await
1069            .expect("shutdown failed");
1070    }
1071
1072    #[fuchsia::test]
1073    async fn f2fs_format_fsck_success() {
1074        let block_size = 4096;
1075        let mut ramdisk = ramdisk(block_size).await;
1076        let mut f2fs = new_fs(&mut ramdisk, F2fs::default()).await;
1077
1078        f2fs.format().await.expect("failed to format f2fs");
1079        f2fs.fsck().await.expect("failed to fsck f2fs");
1080
1081        ramdisk.destroy().await.expect("failed to destroy ramdisk");
1082    }
1083
1084    #[fuchsia::test]
1085    async fn f2fs_format_serve_write_query_restart_read_shutdown() {
1086        let block_size = 4096;
1087        let mut ramdisk = ramdisk(block_size).await;
1088        let mut f2fs = new_fs(&mut ramdisk, F2fs::default()).await;
1089
1090        f2fs.format().await.expect("failed to format f2fs");
1091        let serving = f2fs.serve().await.expect("failed to serve f2fs the first time");
1092
1093        // snapshot of FilesystemInfo
1094        let fs_info1 =
1095            serving.query().await.expect("failed to query filesystem info after first serving");
1096
1097        let filename = "test_file";
1098        let content = String::from("test content").into_bytes();
1099
1100        {
1101            let test_file = fuchsia_fs::directory::open_file(
1102                serving.root(),
1103                filename,
1104                fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_WRITABLE,
1105            )
1106            .await
1107            .expect("failed to create test file");
1108            let _: u64 = test_file
1109                .write(&content)
1110                .await
1111                .expect("failed to write to test file")
1112                .map_err(Status::from_raw)
1113                .expect("write error");
1114        }
1115
1116        // check against the snapshot FilesystemInfo
1117        let fs_info2 = serving.query().await.expect("failed to query filesystem info after write");
1118        // With zx::stream, f2fs doesn't support the inline data feature allowing file
1119        // inode blocks to include small data. This way requires keeping two copies of VMOs
1120        // for the same inline data
1121        // assuming content < 4K and its inode block.
1122        let expected_size2 = fs_info2.block_size * 2;
1123        assert_eq!(fs_info2.used_bytes - fs_info1.used_bytes, expected_size2 as u64);
1124
1125        serving.shutdown().await.expect("failed to shutdown f2fs the first time");
1126        let serving = f2fs.serve().await.expect("failed to serve f2fs the second time");
1127
1128        {
1129            let test_file =
1130                fuchsia_fs::directory::open_file(serving.root(), filename, fio::PERM_READABLE)
1131                    .await
1132                    .expect("failed to open test file");
1133            let read_content =
1134                fuchsia_fs::file::read(&test_file).await.expect("failed to read from test file");
1135            assert_eq!(content, read_content);
1136        }
1137
1138        // once more check against the snapshot FilesystemInfo
1139        let fs_info3 = serving.query().await.expect("failed to query filesystem info after read");
1140        // assuming content < 4K and its inode block.
1141        let expected_size3 = fs_info3.block_size * 2;
1142        assert_eq!(fs_info3.used_bytes - fs_info1.used_bytes, expected_size3 as u64);
1143
1144        serving.shutdown().await.expect("failed to shutdown f2fs the second time");
1145        f2fs.fsck().await.expect("failed to fsck f2fs after shutting down the second time");
1146
1147        ramdisk.destroy().await.expect("failed to destroy ramdisk");
1148    }
1149
1150    #[fuchsia::test]
1151    async fn f2fs_bind_to_path() {
1152        let block_size = 4096;
1153        let test_content = b"test content";
1154        let mut ramdisk = ramdisk(block_size).await;
1155        let mut f2fs = new_fs(&mut ramdisk, F2fs::default()).await;
1156
1157        f2fs.format().await.expect("failed to format f2fs");
1158        let mut serving = f2fs.serve().await.expect("failed to serve f2fs");
1159        serving.bind_to_path("/test-f2fs-path").expect("bind_to_path failed");
1160        let test_path = "/test-f2fs-path/test_file";
1161
1162        {
1163            let mut file = std::fs::File::create(test_path).expect("failed to create test file");
1164            file.write_all(test_content).expect("write bytes");
1165        }
1166
1167        {
1168            let mut file = std::fs::File::open(test_path).expect("failed to open test file");
1169            let mut buf = Vec::new();
1170            file.read_to_end(&mut buf).expect("failed to read test file");
1171            assert_eq!(buf, test_content);
1172        }
1173
1174        serving.shutdown().await.expect("failed to shutdown f2fs");
1175
1176        std::fs::File::open(test_path).expect_err("test file was not unbound");
1177    }
1178
1179    // TODO(https://fxbug.dev/42174810): Re-enable this test; it depends on Fxfs failing repeated calls to
1180    // Start.
1181    #[ignore]
1182    #[fuchsia::test]
1183    async fn fxfs_shutdown_component_when_dropped() {
1184        let block_size = 512;
1185        let mut ramdisk = ramdisk(block_size).await;
1186        let mut fxfs = new_fs(&mut ramdisk, Fxfs::default()).await;
1187
1188        fxfs.format().await.expect("failed to format fxfs");
1189        {
1190            let _fs = fxfs.serve_multi_volume().await.expect("failed to serve fxfs");
1191
1192            // Serve should fail for the second time.
1193            assert!(
1194                fxfs.serve_multi_volume().await.is_err(),
1195                "serving succeeded when already mounted"
1196            );
1197        }
1198
1199        // Fxfs should get shut down when dropped, but it's asynchronous, so we need to loop here.
1200        let mut attempts = 0;
1201        loop {
1202            if let Ok(_) = fxfs.serve_multi_volume().await {
1203                break;
1204            }
1205            attempts += 1;
1206            assert!(attempts < 10);
1207            fasync::Timer::new(Duration::from_secs(1)).await;
1208        }
1209    }
1210
1211    #[fuchsia::test]
1212    async fn fxfs_open_volume() {
1213        let block_size = 512;
1214        let mut ramdisk = ramdisk(block_size).await;
1215        let mut fxfs = new_fs(&mut ramdisk, Fxfs::default()).await;
1216
1217        fxfs.format().await.expect("failed to format fxfs");
1218
1219        let mut fs = fxfs.serve_multi_volume().await.expect("failed to serve fxfs");
1220
1221        assert_eq!(fs.has_volume("foo").await.expect("has_volume"), false);
1222        assert!(
1223            fs.open_volume("foo", MountOptions::default()).await.is_err(),
1224            "Opening nonexistent volume should fail"
1225        );
1226
1227        let vol = fs
1228            .create_volume("foo", CreateOptions::default(), MountOptions::default())
1229            .await
1230            .expect("Create volume failed");
1231        vol.query().await.expect("Query volume failed");
1232        fs.close_volume("foo");
1233        // TODO(https://fxbug.dev/42057878) Closing the volume is not synchronous. Immediately reopening the
1234        // volume will race with the asynchronous close and sometimes fail because the volume is
1235        // still mounted.
1236        // fs.open_volume("foo", MountOptions{crypt: None, as_blob: false}).await
1237        //    .expect("Open volume failed");
1238        assert_eq!(fs.has_volume("foo").await.expect("has_volume"), true);
1239    }
1240
1241    #[fuchsia::test]
1242    async fn fxfs_take_exposed_dir_does_not_drop() {
1243        let block_size = 512;
1244        let test_content = b"test content";
1245        let test_file_name = "test-file";
1246        let mut ramdisk = ramdisk(block_size).await;
1247        let mut fxfs = new_fs(&mut ramdisk, Fxfs::default()).await;
1248
1249        fxfs.format().await.expect("failed to format fxfs");
1250
1251        let mut fs = fxfs.serve_multi_volume().await.expect("failed to serve fxfs");
1252        let file = {
1253            let vol = fs
1254                .create_volume("foo", CreateOptions::default(), MountOptions::default())
1255                .await
1256                .expect("Create volume failed");
1257            let file = fuchsia_fs::directory::open_file(
1258                vol.root(),
1259                test_file_name,
1260                fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_READABLE | fio::PERM_WRITABLE,
1261            )
1262            .await
1263            .unwrap();
1264            fuchsia_fs::file::write(&file, test_content).await.unwrap();
1265            file.close().await.expect("close fidl error").expect("close error");
1266            fuchsia_fs::directory::open_file(vol.root(), test_file_name, fio::PERM_READABLE)
1267                .await
1268                .unwrap()
1269        };
1270
1271        let exposed_dir = fs.take_exposed_dir();
1272
1273        assert_eq!(fuchsia_fs::file::read(&file).await.unwrap(), test_content);
1274
1275        connect_to_protocol_at_dir_root::<fidl_fuchsia_fs::AdminMarker>(&exposed_dir)
1276            .expect("connecting to admin marker")
1277            .shutdown()
1278            .await
1279            .expect("shutdown failed");
1280    }
1281}