Skip to main content

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