1use 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_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::sync::Arc;
20use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
21use zx::{self as zx, AsHandleRef as _, Status};
22use {fidl_fuchsia_component_decl as fdecl, fidl_fuchsia_io as fio};
23
24pub trait BlockConnector: Send + Sync {
36    fn connect_channel_to_volume(&self, server_end: ServerEnd<VolumeMarker>) -> Result<(), Error>;
37    fn connect_volume(&self) -> Result<ClientEnd<VolumeMarker>, Error> {
38        let (client, server) = fidl::endpoints::create_endpoints();
39        self.connect_channel_to_volume(server)?;
40        Ok(client)
41    }
42    fn connect_partition(
43        &self,
44    ) -> Result<ClientEnd<fidl_fuchsia_hardware_block_partition::PartitionMarker>, Error> {
45        self.connect_volume().map(|v| ClientEnd::new(v.into_channel()))
46    }
47    fn connect_block(&self) -> Result<ClientEnd<fidl_fuchsia_hardware_block::BlockMarker>, Error> {
48        self.connect_volume().map(|v| ClientEnd::new(v.into_channel()))
49    }
50}
51
52#[derive(Clone, Debug)]
54pub struct DirBasedBlockConnector(fio::DirectoryProxy, String);
55
56impl DirBasedBlockConnector {
57    pub fn new(dir: fio::DirectoryProxy, path: String) -> Self {
58        Self(dir, path)
59    }
60
61    pub fn path(&self) -> &str {
62        &self.1
63    }
64}
65
66impl BlockConnector for DirBasedBlockConnector {
67    fn connect_channel_to_volume(&self, server_end: ServerEnd<VolumeMarker>) -> Result<(), Error> {
68        self.0.open(
69            self.path(),
70            fio::Flags::PROTOCOL_SERVICE,
71            &fio::Options::default(),
72            server_end.into_channel(),
73        )?;
74        Ok(())
75    }
76}
77
78impl BlockConnector for fidl_fuchsia_device::ControllerProxy {
79    fn connect_channel_to_volume(&self, server_end: ServerEnd<VolumeMarker>) -> Result<(), Error> {
80        let () = self.connect_to_device_fidl(server_end.into_channel())?;
81        Ok(())
82    }
83}
84
85impl BlockConnector for fidl_fuchsia_storage_partitions::PartitionServiceProxy {
86    fn connect_channel_to_volume(&self, server_end: ServerEnd<VolumeMarker>) -> Result<(), Error> {
87        self.connect_channel_to_volume(server_end)?;
88        Ok(())
89    }
90}
91
92impl<T: BlockConnector> BlockConnector for Arc<T> {
96    fn connect_channel_to_volume(&self, server_end: ServerEnd<VolumeMarker>) -> Result<(), Error> {
97        self.as_ref().connect_channel_to_volume(server_end)
98    }
99}
100
101impl<F> BlockConnector for F
102where
103    F: Fn(ServerEnd<VolumeMarker>) -> Result<(), Error> + Send + Sync,
104{
105    fn connect_channel_to_volume(&self, server_end: ServerEnd<VolumeMarker>) -> Result<(), Error> {
106        self(server_end)
107    }
108}
109
110pub struct Filesystem {
112    config: Box<dyn FSConfig>,
119    block_connector: Box<dyn BlockConnector>,
120    component: Option<Arc<DynamicComponentInstance>>,
121}
122
123static COLLECTION_COUNTER: AtomicU64 = AtomicU64::new(0);
125
126impl Filesystem {
127    pub fn config(&self) -> &dyn FSConfig {
128        self.config.as_ref()
129    }
130
131    pub fn into_config(self) -> Box<dyn FSConfig> {
132        self.config
133    }
134
135    pub fn new<B: BlockConnector + 'static, FSC: FSConfig>(
137        block_connector: B,
138        config: FSC,
139    ) -> Self {
140        Self::from_boxed_config(Box::new(block_connector), Box::new(config))
141    }
142
143    pub fn from_boxed_config(
145        block_connector: Box<dyn BlockConnector>,
146        config: Box<dyn FSConfig>,
147    ) -> Self {
148        Self { config, block_connector, component: None }
149    }
150
151    pub async fn get_component_moniker(&mut self) -> Result<String, Error> {
154        let _ = self.get_component_exposed_dir().await?;
155        Ok(match self.config.options().component_type {
156            ComponentType::StaticChild => self.config.options().component_name.to_string(),
157            ComponentType::DynamicChild { .. } => {
158                let component = self.component.as_ref().unwrap();
159                format!("{}:{}", component.collection, component.name)
160            }
161        })
162    }
163
164    async fn get_component_exposed_dir(&mut self) -> Result<fio::DirectoryProxy, Error> {
165        let options = self.config.options();
166        let component_name = options.component_name;
167        match options.component_type {
168            ComponentType::StaticChild => open_childs_exposed_directory(component_name, None).await,
169            ComponentType::DynamicChild { collection_name } => {
170                if let Some(component) = &self.component {
171                    return open_childs_exposed_directory(
172                        component.name.clone(),
173                        Some(component.collection.clone()),
174                    )
175                    .await;
176                }
177
178                let name = format!(
182                    "{}-{}-{}",
183                    component_name,
184                    fuchsia_runtime::process_self().get_koid().unwrap().raw_koid(),
185                    COLLECTION_COUNTER.fetch_add(1, Ordering::Relaxed)
186                );
187
188                let collection_ref = fdecl::CollectionRef { name: collection_name };
189                let child_decls = vec![
190                    fdecl::Child {
191                        name: Some(format!("{}-relative", name)),
192                        url: Some(format!("#meta/{}.cm", component_name)),
193                        startup: Some(fdecl::StartupMode::Lazy),
194                        ..Default::default()
195                    },
196                    fdecl::Child {
197                        name: Some(name),
198                        url: Some(format!(
199                            "fuchsia-boot:///{}#meta/{}.cm",
200                            component_name, component_name
201                        )),
202                        startup: Some(fdecl::StartupMode::Lazy),
203                        ..Default::default()
204                    },
205                ];
206                let realm_proxy = connect_to_protocol::<RealmMarker>()?;
207                for child_decl in child_decls {
208                    realm_proxy
210                        .create_child(
211                            &collection_ref,
212                            &child_decl,
213                            fcomponent::CreateChildArgs::default(),
214                        )
215                        .await?
216                        .map_err(|e| anyhow!("create_child failed: {:?}", e))?;
217
218                    let component = Arc::new(DynamicComponentInstance {
219                        name: child_decl.name.unwrap(),
220                        collection: collection_ref.name.clone(),
221                        should_not_drop: AtomicBool::new(false),
222                    });
223
224                    if let Ok(proxy) = open_childs_exposed_directory(
225                        component.name.clone(),
226                        Some(component.collection.clone()),
227                    )
228                    .await
229                    {
230                        self.component = Some(component);
231                        return Ok(proxy);
232                    }
233                }
234                Err(anyhow!("Failed to open exposed directory"))
235            }
236        }
237    }
238
239    pub async fn format(&mut self) -> Result<(), Error> {
252        let channel = self.block_connector.connect_block()?;
253
254        let exposed_dir = self.get_component_exposed_dir().await?;
255        let proxy = connect_to_protocol_at_dir_root::<StartupMarker>(&exposed_dir)?;
256        proxy
257            .format(channel, &self.config().options().format_options)
258            .await?
259            .map_err(Status::from_raw)?;
260
261        Ok(())
262    }
263
264    pub async fn fsck(&mut self) -> Result<(), Error> {
277        let channel = self.block_connector.connect_block()?;
278        let exposed_dir = self.get_component_exposed_dir().await?;
279        let proxy = connect_to_protocol_at_dir_root::<StartupMarker>(&exposed_dir)?;
280        proxy.check(channel, CheckOptions::default()).await?.map_err(Status::from_raw)?;
281        Ok(())
282    }
283
284    pub async fn serve(mut self) -> Result<ServingSingleVolumeFilesystem, Error> {
291        if self.config.is_multi_volume() {
292            bail!("Can't serve a multivolume filesystem; use serve_multi_volume");
293        }
294        let Options { start_options, reuse_component_after_serving, .. } = self.config.options();
295
296        let exposed_dir = self.get_component_exposed_dir().await?;
297        let proxy = connect_to_protocol_at_dir_root::<StartupMarker>(&exposed_dir)?;
298        proxy
299            .start(self.block_connector.connect_block()?, &start_options)
300            .await?
301            .map_err(Status::from_raw)?;
302
303        let (root_dir, server_end) = create_endpoints::<fio::NodeMarker>();
304        exposed_dir.open(
305            "root",
306            fio::PERM_READABLE | fio::Flags::PERM_INHERIT_WRITE | fio::Flags::PERM_INHERIT_EXECUTE,
307            &Default::default(),
308            server_end.into_channel(),
309        )?;
310        let component = self.component.clone();
311        if !reuse_component_after_serving {
312            self.component = None;
313        }
314        Ok(ServingSingleVolumeFilesystem {
315            component,
316            exposed_dir: Some(exposed_dir),
317            root_dir: ClientEnd::<fio::DirectoryMarker>::new(root_dir.into_channel()).into_proxy(),
318            binding: None,
319        })
320    }
321
322    pub async fn serve_multi_volume(mut self) -> Result<ServingMultiVolumeFilesystem, Error> {
330        if !self.config.is_multi_volume() {
331            bail!("Can't serve_multi_volume a single-volume filesystem; use serve");
332        }
333
334        let exposed_dir = self.get_component_exposed_dir().await?;
335        let proxy = connect_to_protocol_at_dir_root::<StartupMarker>(&exposed_dir)?;
336        proxy
337            .start(self.block_connector.connect_block()?, &self.config.options().start_options)
338            .await?
339            .map_err(Status::from_raw)?;
340
341        Ok(ServingMultiVolumeFilesystem {
342            component: self.component,
343            exposed_dir: Some(exposed_dir),
344        })
345    }
346}
347
348struct DynamicComponentInstance {
350    name: String,
351    collection: String,
352    should_not_drop: AtomicBool,
353}
354
355impl DynamicComponentInstance {
356    fn forget(&self) {
357        self.should_not_drop.store(true, Ordering::Relaxed);
358    }
359}
360
361impl Drop for DynamicComponentInstance {
362    fn drop(&mut self) {
363        if self.should_not_drop.load(Ordering::Relaxed) {
364            return;
365        }
366        if let Ok(realm_proxy) = connect_to_protocol::<RealmMarker>() {
367            let _ = realm_proxy.destroy_child(&fdecl::ChildRef {
368                name: self.name.clone(),
369                collection: Some(self.collection.clone()),
370            });
371        }
372    }
373}
374
375#[derive(Default)]
378pub struct NamespaceBinding(String);
379
380impl NamespaceBinding {
381    pub fn create(root_dir: &fio::DirectoryProxy, path: String) -> Result<NamespaceBinding, Error> {
382        let (client_end, server_end) = create_endpoints();
383        root_dir.clone(ServerEnd::new(server_end.into_channel()))?;
384        let namespace = fdio::Namespace::installed()?;
385        namespace.bind(&path, client_end)?;
386        Ok(Self(path))
387    }
388}
389
390impl std::ops::Deref for NamespaceBinding {
391    type Target = str;
392    fn deref(&self) -> &Self::Target {
393        &self.0
394    }
395}
396
397impl Drop for NamespaceBinding {
398    fn drop(&mut self) {
399        if let Ok(namespace) = fdio::Namespace::installed() {
400            let _ = namespace.unbind(&self.0);
401        }
402    }
403}
404
405pub type ServingFilesystem = ServingSingleVolumeFilesystem;
407
408pub struct ServingSingleVolumeFilesystem {
410    component: Option<Arc<DynamicComponentInstance>>,
411    exposed_dir: Option<fio::DirectoryProxy>,
413    root_dir: fio::DirectoryProxy,
414
415    binding: Option<NamespaceBinding>,
417}
418
419impl ServingSingleVolumeFilesystem {
420    pub fn exposed_dir(&self) -> &fio::DirectoryProxy {
422        self.exposed_dir.as_ref().unwrap()
423    }
424
425    pub fn root(&self) -> &fio::DirectoryProxy {
427        &self.root_dir
428    }
429
430    pub fn bind_to_path(&mut self, path: &str) -> Result<(), Error> {
438        ensure!(self.binding.is_none(), "Already bound");
439        self.binding = Some(NamespaceBinding::create(&self.root_dir, path.to_string())?);
440        Ok(())
441    }
442
443    pub fn bound_path(&self) -> Option<&str> {
444        self.binding.as_deref()
445    }
446
447    pub async fn query(&self) -> Result<Box<fio::FilesystemInfo>, QueryError> {
453        let (status, info) = self.root_dir.query_filesystem().await?;
454        Status::ok(status).map_err(QueryError::DirectoryQuery)?;
455        info.ok_or(QueryError::DirectoryEmptyResult)
456    }
457
458    pub fn take_exposed_dir(mut self) -> fio::DirectoryProxy {
462        self.component.take().expect("BUG: component missing").forget();
463        self.exposed_dir.take().expect("BUG: exposed dir missing")
464    }
465
466    pub async fn shutdown(mut self) -> Result<(), ShutdownError> {
474        connect_to_protocol_at_dir_root::<fidl_fuchsia_fs::AdminMarker>(
475            &self.exposed_dir.take().expect("BUG: exposed dir missing"),
476        )?
477        .shutdown()
478        .await?;
479        Ok(())
480    }
481
482    pub async fn kill(self) -> Result<(), Error> {
489        self.shutdown().await?;
493        Ok(())
494    }
495}
496
497impl Drop for ServingSingleVolumeFilesystem {
498    fn drop(&mut self) {
499        if let Some(exposed_dir) = self.exposed_dir.take() {
501            if let Ok(proxy) =
502                connect_to_protocol_at_dir_root::<fidl_fuchsia_fs::AdminMarker>(&exposed_dir)
503            {
504                let _ = proxy.shutdown();
505            }
506        }
507    }
508}
509
510pub struct ServingMultiVolumeFilesystem {
513    component: Option<Arc<DynamicComponentInstance>>,
514    exposed_dir: Option<fio::DirectoryProxy>,
516}
517
518pub struct ServingVolume {
520    root_dir: fio::DirectoryProxy,
521    binding: Option<NamespaceBinding>,
522    exposed_dir: fio::DirectoryProxy,
523}
524
525impl ServingVolume {
526    fn new(exposed_dir: fio::DirectoryProxy) -> Result<Self, Error> {
527        let (root_dir, server_end) = create_endpoints::<fio::NodeMarker>();
528        exposed_dir.open(
529            "root",
530            fio::PERM_READABLE | fio::Flags::PERM_INHERIT_WRITE | fio::Flags::PERM_INHERIT_EXECUTE,
531            &Default::default(),
532            server_end.into_channel(),
533        )?;
534        Ok(ServingVolume {
535            root_dir: ClientEnd::<fio::DirectoryMarker>::new(root_dir.into_channel()).into_proxy(),
536            binding: None,
537            exposed_dir,
538        })
539    }
540
541    pub fn root(&self) -> &fio::DirectoryProxy {
543        &self.root_dir
544    }
545
546    pub fn exposed_dir(&self) -> &fio::DirectoryProxy {
548        &self.exposed_dir
549    }
550
551    pub fn bind_to_path(&mut self, path: &str) -> Result<(), Error> {
559        ensure!(self.binding.is_none(), "Already bound");
560        self.binding = Some(NamespaceBinding::create(&self.root_dir, path.to_string())?);
561        Ok(())
562    }
563
564    pub fn unbind_path(&mut self) {
568        let _ = self.binding.take();
569    }
570
571    pub fn bound_path(&self) -> Option<&str> {
572        self.binding.as_deref()
573    }
574
575    pub async fn query(&self) -> Result<Box<fio::FilesystemInfo>, QueryError> {
581        let (status, info) = self.root_dir.query_filesystem().await?;
582        Status::ok(status).map_err(QueryError::DirectoryQuery)?;
583        info.ok_or(QueryError::DirectoryEmptyResult)
584    }
585
586    pub async fn shutdown(self) -> Result<(), Error> {
589        let admin_proxy = connect_to_protocol_at_dir_svc::<AdminMarker>(self.exposed_dir())?;
590        admin_proxy.shutdown().await.context("failed to shutdown volume")?;
591        Ok(())
592    }
593}
594
595impl ServingMultiVolumeFilesystem {
596    pub async fn has_volume(&self, volume: &str) -> Result<bool, Error> {
598        let path = format!("volumes/{}", volume);
599        fuchsia_fs::directory::open_node(
600            self.exposed_dir.as_ref().unwrap(),
601            &path,
602            fio::Flags::PROTOCOL_NODE,
603        )
604        .await
605        .map(|_| true)
606        .or_else(|e| {
607            if let fuchsia_fs::node::OpenError::OpenError(status) = &e {
608                if *status == zx::Status::NOT_FOUND {
609                    return Ok(false);
610                }
611            }
612            Err(e.into())
613        })
614    }
615
616    pub async fn create_volume(
620        &self,
621        volume: &str,
622        create_options: CreateOptions,
623        options: MountOptions,
624    ) -> Result<ServingVolume, Error> {
625        let (exposed_dir, server) = create_proxy::<fio::DirectoryMarker>();
626        connect_to_protocol_at_dir_root::<fidl_fuchsia_fs_startup::VolumesMarker>(
627            self.exposed_dir.as_ref().unwrap(),
628        )?
629        .create(volume, server, create_options, options)
630        .await?
631        .map_err(|e| anyhow!(zx::Status::from_raw(e)))?;
632        ServingVolume::new(exposed_dir)
633    }
634
635    pub async fn remove_volume(&self, volume: &str) -> Result<(), Error> {
637        connect_to_protocol_at_dir_root::<fidl_fuchsia_fs_startup::VolumesMarker>(
638            self.exposed_dir.as_ref().unwrap(),
639        )?
640        .remove(volume)
641        .await?
642        .map_err(|e| anyhow!(zx::Status::from_raw(e)))
643    }
644
645    pub async fn open_volume(
648        &self,
649        volume: &str,
650        options: MountOptions,
651    ) -> Result<ServingVolume, Error> {
652        let (exposed_dir, server) = create_proxy::<fio::DirectoryMarker>();
653        let path = format!("volumes/{}", volume);
654        connect_to_named_protocol_at_dir_root::<fidl_fuchsia_fs_startup::VolumeMarker>(
655            self.exposed_dir.as_ref().unwrap(),
656            &path,
657        )?
658        .mount(server, options)
659        .await?
660        .map_err(|e| anyhow!(zx::Status::from_raw(e)))?;
661
662        ServingVolume::new(exposed_dir)
663    }
664
665    pub async fn set_byte_limit(&self, volume: &str, byte_limit: u64) -> Result<(), Error> {
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(&self, volume: &str, options: CheckOptions) -> Result<(), Error> {
681        let path = format!("volumes/{}", volume);
682        connect_to_named_protocol_at_dir_root::<fidl_fuchsia_fs_startup::VolumeMarker>(
683            self.exposed_dir.as_ref().unwrap(),
684            &path,
685        )?
686        .check(options)
687        .await?
688        .map_err(|e| anyhow!(zx::Status::from_raw(e)))?;
689        Ok(())
690    }
691
692    pub fn exposed_dir(&self) -> &fio::DirectoryProxy {
695        self.exposed_dir.as_ref().expect("BUG: exposed dir missing")
696    }
697
698    pub async fn shutdown(mut self) -> Result<(), ShutdownError> {
705        connect_to_protocol_at_dir_root::<fidl_fuchsia_fs::AdminMarker>(
706            &self.exposed_dir.take().expect("BUG: exposed dir missing"),
708        )?
709        .shutdown()
710        .await?;
711        Ok(())
712    }
713
714    pub fn take_exposed_dir(mut self) -> fio::DirectoryProxy {
718        self.component.take().expect("BUG: missing component").forget();
719        self.exposed_dir.take().expect("BUG: exposed dir missing")
720    }
721
722    pub async fn list_volumes(&self) -> Result<Vec<String>, Error> {
724        let volumes_dir = fuchsia_fs::directory::open_async::<fio::DirectoryMarker>(
725            self.exposed_dir(),
726            "volumes",
727            fio::PERM_READABLE,
728        )
729        .unwrap();
730        fuchsia_fs::directory::readdir(&volumes_dir)
731            .await
732            .map(|entries| entries.into_iter().map(|e| e.name).collect())
733            .map_err(|e| anyhow!("failed to read volumes dir: {}", e))
734    }
735}
736
737impl Drop for ServingMultiVolumeFilesystem {
738    fn drop(&mut self) {
739        if let Some(exposed_dir) = self.exposed_dir.take() {
740            if let Ok(proxy) =
742                connect_to_protocol_at_dir_root::<fidl_fuchsia_fs::AdminMarker>(&exposed_dir)
743            {
744                let _ = proxy.shutdown();
745            }
746        }
747    }
748}
749
750#[cfg(test)]
751mod tests {
752    use super::*;
753    use crate::{BlobCompression, BlobEvictionPolicy, Blobfs, F2fs, Fxfs, Minfs};
754    use ramdevice_client::RamdiskClient;
755    use std::io::{Read as _, Write as _};
756
757    async fn ramdisk(block_size: u64) -> RamdiskClient {
758        RamdiskClient::create(block_size, 1 << 16).await.unwrap()
759    }
760
761    async fn new_fs<FSC: FSConfig>(ramdisk: &RamdiskClient, config: FSC) -> Filesystem {
762        Filesystem::new(ramdisk.open_controller().unwrap(), config)
763    }
764
765    #[fuchsia::test]
766    async fn blobfs_custom_config() {
767        let block_size = 512;
768        let ramdisk = ramdisk(block_size).await;
769        let config = Blobfs {
770            verbose: true,
771            readonly: true,
772            write_compression_algorithm: BlobCompression::Uncompressed,
773            cache_eviction_policy_override: BlobEvictionPolicy::EvictImmediately,
774            ..Default::default()
775        };
776        let mut blobfs = new_fs(&ramdisk, config).await;
777
778        blobfs.format().await.expect("failed to format blobfs");
779        blobfs.fsck().await.expect("failed to fsck blobfs");
780        let _ = blobfs.serve().await.expect("failed to serve blobfs");
781
782        ramdisk.destroy().await.expect("failed to destroy ramdisk");
783    }
784
785    #[fuchsia::test]
786    async fn blobfs_format_fsck_success() {
787        let block_size = 512;
788        let ramdisk = ramdisk(block_size).await;
789        let mut blobfs = new_fs(&ramdisk, Blobfs::default()).await;
790
791        blobfs.format().await.expect("failed to format blobfs");
792        blobfs.fsck().await.expect("failed to fsck blobfs");
793
794        ramdisk.destroy().await.expect("failed to destroy ramdisk");
795    }
796
797    #[fuchsia::test]
798    async fn blobfs_format_serve_write_query_restart_read_shutdown() {
799        let block_size = 512;
800        let ramdisk = ramdisk(block_size).await;
801        let mut blobfs = new_fs(&ramdisk, Blobfs::default()).await;
802
803        blobfs.format().await.expect("failed to format blobfs");
804
805        let serving = blobfs.serve().await.expect("failed to serve blobfs the first time");
806
807        let fs_info1 =
809            serving.query().await.expect("failed to query filesystem info after first serving");
810
811        let merkle = "be901a14ec42ee0a8ee220eb119294cdd40d26d573139ee3d51e4430e7d08c28";
813        let content = String::from("test content").into_bytes();
814
815        {
816            let test_file = fuchsia_fs::directory::open_file(
817                serving.root(),
818                merkle,
819                fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_WRITABLE,
820            )
821            .await
822            .expect("failed to create test file");
823            let () = test_file
824                .resize(content.len() as u64)
825                .await
826                .expect("failed to send resize FIDL")
827                .map_err(Status::from_raw)
828                .expect("failed to resize file");
829            let _: u64 = test_file
830                .write(&content)
831                .await
832                .expect("failed to write to test file")
833                .map_err(Status::from_raw)
834                .expect("write error");
835        }
836
837        let fs_info2 = serving.query().await.expect("failed to query filesystem info after write");
839        assert_eq!(
840            fs_info2.used_bytes - fs_info1.used_bytes,
841            fs_info2.block_size as u64 );
843
844        serving.shutdown().await.expect("failed to shutdown blobfs the first time");
845        let blobfs = new_fs(&ramdisk, Blobfs::default()).await;
846        let serving = blobfs.serve().await.expect("failed to serve blobfs the second time");
847        {
848            let test_file =
849                fuchsia_fs::directory::open_file(serving.root(), merkle, fio::PERM_READABLE)
850                    .await
851                    .expect("failed to open test file");
852            let read_content =
853                fuchsia_fs::file::read(&test_file).await.expect("failed to read from test file");
854            assert_eq!(content, read_content);
855        }
856
857        let fs_info3 = serving.query().await.expect("failed to query filesystem info after read");
859        assert_eq!(
860            fs_info3.used_bytes - fs_info1.used_bytes,
861            fs_info3.block_size as u64 );
863
864        serving.shutdown().await.expect("failed to shutdown blobfs the second time");
865
866        ramdisk.destroy().await.expect("failed to destroy ramdisk");
867    }
868
869    #[fuchsia::test]
870    async fn blobfs_bind_to_path() {
871        let block_size = 512;
872        let merkle = "be901a14ec42ee0a8ee220eb119294cdd40d26d573139ee3d51e4430e7d08c28";
873        let test_content = b"test content";
874        let ramdisk = ramdisk(block_size).await;
875        let mut blobfs = new_fs(&ramdisk, Blobfs::default()).await;
876
877        blobfs.format().await.expect("failed to format blobfs");
878        let mut serving = blobfs.serve().await.expect("failed to serve blobfs");
879        serving.bind_to_path("/test-blobfs-path").expect("bind_to_path failed");
880        let test_path = format!("/test-blobfs-path/{}", merkle);
881
882        {
883            let mut file = std::fs::File::create(&test_path).expect("failed to create test file");
884            file.set_len(test_content.len() as u64).expect("failed to set size");
885            file.write_all(test_content).expect("write bytes");
886        }
887
888        {
889            let mut file = std::fs::File::open(&test_path).expect("failed to open test file");
890            let mut buf = Vec::new();
891            file.read_to_end(&mut buf).expect("failed to read test file");
892            assert_eq!(buf, test_content);
893        }
894
895        serving.shutdown().await.expect("failed to shutdown blobfs");
896
897        std::fs::File::open(&test_path).expect_err("test file was not unbound");
898    }
899
900    #[fuchsia::test]
901    async fn minfs_custom_config() {
902        let block_size = 512;
903        let ramdisk = ramdisk(block_size).await;
904        let config = Minfs {
905            verbose: true,
906            readonly: true,
907            fsck_after_every_transaction: true,
908            ..Default::default()
909        };
910        let mut minfs = new_fs(&ramdisk, config).await;
911
912        minfs.format().await.expect("failed to format minfs");
913        minfs.fsck().await.expect("failed to fsck minfs");
914        let _ = minfs.serve().await.expect("failed to serve minfs");
915
916        ramdisk.destroy().await.expect("failed to destroy ramdisk");
917    }
918
919    #[fuchsia::test]
920    async fn minfs_format_fsck_success() {
921        let block_size = 8192;
922        let ramdisk = ramdisk(block_size).await;
923        let mut minfs = new_fs(&ramdisk, Minfs::default()).await;
924
925        minfs.format().await.expect("failed to format minfs");
926        minfs.fsck().await.expect("failed to fsck minfs");
927
928        ramdisk.destroy().await.expect("failed to destroy ramdisk");
929    }
930
931    #[fuchsia::test]
932    async fn minfs_format_serve_write_query_restart_read_shutdown() {
933        let block_size = 8192;
934        let ramdisk = ramdisk(block_size).await;
935        let mut minfs = new_fs(&ramdisk, Minfs::default()).await;
936
937        minfs.format().await.expect("failed to format minfs");
938        let serving = minfs.serve().await.expect("failed to serve minfs the first time");
939
940        let fs_info1 =
942            serving.query().await.expect("failed to query filesystem info after first serving");
943
944        let filename = "test_file";
945        let content = String::from("test content").into_bytes();
946
947        {
948            let test_file = fuchsia_fs::directory::open_file(
949                serving.root(),
950                filename,
951                fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_WRITABLE,
952            )
953            .await
954            .expect("failed to create test file");
955            let _: u64 = test_file
956                .write(&content)
957                .await
958                .expect("failed to write to test file")
959                .map_err(Status::from_raw)
960                .expect("write error");
961        }
962
963        let fs_info2 = serving.query().await.expect("failed to query filesystem info after write");
965        assert_eq!(
966            fs_info2.used_bytes - fs_info1.used_bytes,
967            fs_info2.block_size as u64 );
969
970        serving.shutdown().await.expect("failed to shutdown minfs the first time");
971        let minfs = new_fs(&ramdisk, Minfs::default()).await;
972        let serving = minfs.serve().await.expect("failed to serve minfs the second time");
973
974        {
975            let test_file =
976                fuchsia_fs::directory::open_file(serving.root(), filename, fio::PERM_READABLE)
977                    .await
978                    .expect("failed to open test file");
979            let read_content =
980                fuchsia_fs::file::read(&test_file).await.expect("failed to read from test file");
981            assert_eq!(content, read_content);
982        }
983
984        let fs_info3 = serving.query().await.expect("failed to query filesystem info after read");
986        assert_eq!(
987            fs_info3.used_bytes - fs_info1.used_bytes,
988            fs_info3.block_size as u64 );
990
991        let _ = serving.shutdown().await.expect("failed to shutdown minfs the second time");
992
993        ramdisk.destroy().await.expect("failed to destroy ramdisk");
994    }
995
996    #[fuchsia::test]
997    async fn minfs_bind_to_path() {
998        let block_size = 8192;
999        let test_content = b"test content";
1000        let ramdisk = ramdisk(block_size).await;
1001        let mut minfs = new_fs(&ramdisk, Minfs::default()).await;
1002
1003        minfs.format().await.expect("failed to format minfs");
1004        let mut serving = minfs.serve().await.expect("failed to serve minfs");
1005        serving.bind_to_path("/test-minfs-path").expect("bind_to_path failed");
1006        let test_path = "/test-minfs-path/test_file";
1007
1008        {
1009            let mut file = std::fs::File::create(test_path).expect("failed to create test file");
1010            file.write_all(test_content).expect("write bytes");
1011        }
1012
1013        {
1014            let mut file = std::fs::File::open(test_path).expect("failed to open test file");
1015            let mut buf = Vec::new();
1016            file.read_to_end(&mut buf).expect("failed to read test file");
1017            assert_eq!(buf, test_content);
1018        }
1019
1020        serving.shutdown().await.expect("failed to shutdown minfs");
1021
1022        std::fs::File::open(test_path).expect_err("test file was not unbound");
1023    }
1024
1025    #[fuchsia::test]
1026    async fn minfs_take_exposed_dir_does_not_drop() {
1027        let block_size = 512;
1028        let test_content = b"test content";
1029        let test_file_name = "test-file";
1030        let ramdisk = ramdisk(block_size).await;
1031        let mut minfs = new_fs(&ramdisk, Minfs::default()).await;
1032
1033        minfs.format().await.expect("failed to format fxfs");
1034
1035        let fs = minfs.serve().await.expect("failed to serve fxfs");
1036        let file = {
1037            let file = fuchsia_fs::directory::open_file(
1038                fs.root(),
1039                test_file_name,
1040                fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_READABLE | fio::PERM_WRITABLE,
1041            )
1042            .await
1043            .unwrap();
1044            fuchsia_fs::file::write(&file, test_content).await.unwrap();
1045            file.close().await.expect("close fidl error").expect("close error");
1046            fuchsia_fs::directory::open_file(fs.root(), test_file_name, fio::PERM_READABLE)
1047                .await
1048                .unwrap()
1049        };
1050
1051        let exposed_dir = fs.take_exposed_dir();
1052
1053        assert_eq!(fuchsia_fs::file::read(&file).await.unwrap(), test_content);
1054
1055        connect_to_protocol_at_dir_root::<fidl_fuchsia_fs::AdminMarker>(&exposed_dir)
1056            .expect("connecting to admin marker")
1057            .shutdown()
1058            .await
1059            .expect("shutdown failed");
1060    }
1061
1062    #[fuchsia::test]
1063    async fn f2fs_format_fsck_success() {
1064        let block_size = 4096;
1065        let ramdisk = ramdisk(block_size).await;
1066        let mut f2fs = new_fs(&ramdisk, F2fs::default()).await;
1067
1068        f2fs.format().await.expect("failed to format f2fs");
1069        f2fs.fsck().await.expect("failed to fsck f2fs");
1070
1071        ramdisk.destroy().await.expect("failed to destroy ramdisk");
1072    }
1073
1074    #[fuchsia::test]
1075    async fn f2fs_format_serve_write_query_restart_read_shutdown() {
1076        let block_size = 4096;
1077        let ramdisk = ramdisk(block_size).await;
1078        let mut f2fs = new_fs(&ramdisk, F2fs::default()).await;
1079
1080        f2fs.format().await.expect("failed to format f2fs");
1081        let serving = f2fs.serve().await.expect("failed to serve f2fs the first time");
1082
1083        let fs_info1 =
1085            serving.query().await.expect("failed to query filesystem info after first serving");
1086
1087        let filename = "test_file";
1088        let content = String::from("test content").into_bytes();
1089
1090        {
1091            let test_file = fuchsia_fs::directory::open_file(
1092                serving.root(),
1093                filename,
1094                fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_WRITABLE,
1095            )
1096            .await
1097            .expect("failed to create test file");
1098            let _: u64 = test_file
1099                .write(&content)
1100                .await
1101                .expect("failed to write to test file")
1102                .map_err(Status::from_raw)
1103                .expect("write error");
1104        }
1105
1106        let fs_info2 = serving.query().await.expect("failed to query filesystem info after write");
1108        let expected_size2 = fs_info2.block_size * 2;
1113        assert_eq!(fs_info2.used_bytes - fs_info1.used_bytes, expected_size2 as u64);
1114
1115        serving.shutdown().await.expect("failed to shutdown f2fs the first time");
1116        let f2fs = new_fs(&ramdisk, F2fs::default()).await;
1117        let serving = f2fs.serve().await.expect("failed to serve f2fs the second time");
1118
1119        {
1120            let test_file =
1121                fuchsia_fs::directory::open_file(serving.root(), filename, fio::PERM_READABLE)
1122                    .await
1123                    .expect("failed to open test file");
1124            let read_content =
1125                fuchsia_fs::file::read(&test_file).await.expect("failed to read from test file");
1126            assert_eq!(content, read_content);
1127        }
1128
1129        let fs_info3 = serving.query().await.expect("failed to query filesystem info after read");
1131        let expected_size3 = fs_info3.block_size * 2;
1133        assert_eq!(fs_info3.used_bytes - fs_info1.used_bytes, expected_size3 as u64);
1134
1135        serving.shutdown().await.expect("failed to shutdown f2fs the second time");
1136        let mut f2fs = new_fs(&ramdisk, F2fs::default()).await;
1137        f2fs.fsck().await.expect("failed to fsck f2fs after shutting down the second time");
1138
1139        ramdisk.destroy().await.expect("failed to destroy ramdisk");
1140    }
1141
1142    #[fuchsia::test]
1143    async fn f2fs_bind_to_path() {
1144        let block_size = 4096;
1145        let test_content = b"test content";
1146        let ramdisk = ramdisk(block_size).await;
1147        let mut f2fs = new_fs(&ramdisk, F2fs::default()).await;
1148
1149        f2fs.format().await.expect("failed to format f2fs");
1150        let mut serving = f2fs.serve().await.expect("failed to serve f2fs");
1151        serving.bind_to_path("/test-f2fs-path").expect("bind_to_path failed");
1152        let test_path = "/test-f2fs-path/test_file";
1153
1154        {
1155            let mut file = std::fs::File::create(test_path).expect("failed to create test file");
1156            file.write_all(test_content).expect("write bytes");
1157        }
1158
1159        {
1160            let mut file = std::fs::File::open(test_path).expect("failed to open test file");
1161            let mut buf = Vec::new();
1162            file.read_to_end(&mut buf).expect("failed to read test file");
1163            assert_eq!(buf, test_content);
1164        }
1165
1166        serving.shutdown().await.expect("failed to shutdown f2fs");
1167
1168        std::fs::File::open(test_path).expect_err("test file was not unbound");
1169    }
1170
1171    #[fuchsia::test]
1172    async fn fxfs_open_volume() {
1173        let block_size = 512;
1174        let ramdisk = ramdisk(block_size).await;
1175        let mut fxfs = new_fs(&ramdisk, Fxfs::default()).await;
1176
1177        fxfs.format().await.expect("failed to format fxfs");
1178
1179        let fs = fxfs.serve_multi_volume().await.expect("failed to serve fxfs");
1180
1181        assert_eq!(fs.has_volume("foo").await.expect("has_volume"), false);
1182        assert!(
1183            fs.open_volume("foo", MountOptions::default()).await.is_err(),
1184            "Opening nonexistent volume should fail"
1185        );
1186
1187        let vol = fs
1188            .create_volume("foo", CreateOptions::default(), MountOptions::default())
1189            .await
1190            .expect("Create volume failed");
1191        vol.query().await.expect("Query volume failed");
1192        assert_eq!(fs.has_volume("foo").await.expect("has_volume"), true);
1198    }
1199
1200    #[fuchsia::test]
1201    async fn fxfs_take_exposed_dir_does_not_drop() {
1202        let block_size = 512;
1203        let test_content = b"test content";
1204        let test_file_name = "test-file";
1205        let ramdisk = ramdisk(block_size).await;
1206        let mut fxfs = new_fs(&ramdisk, Fxfs::default()).await;
1207
1208        fxfs.format().await.expect("failed to format fxfs");
1209
1210        let fs = fxfs.serve_multi_volume().await.expect("failed to serve fxfs");
1211        let file = {
1212            let vol = fs
1213                .create_volume("foo", CreateOptions::default(), MountOptions::default())
1214                .await
1215                .expect("Create volume failed");
1216            let file = fuchsia_fs::directory::open_file(
1217                vol.root(),
1218                test_file_name,
1219                fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_READABLE | fio::PERM_WRITABLE,
1220            )
1221            .await
1222            .unwrap();
1223            fuchsia_fs::file::write(&file, test_content).await.unwrap();
1224            file.close().await.expect("close fidl error").expect("close error");
1225            fuchsia_fs::directory::open_file(vol.root(), test_file_name, fio::PERM_READABLE)
1226                .await
1227                .unwrap()
1228        };
1229
1230        let exposed_dir = fs.take_exposed_dir();
1231
1232        assert_eq!(fuchsia_fs::file::read(&file).await.unwrap(), test_content);
1233
1234        connect_to_protocol_at_dir_root::<fidl_fuchsia_fs::AdminMarker>(&exposed_dir)
1235            .expect("connecting to admin marker")
1236            .shutdown()
1237            .await
1238            .expect("shutdown failed");
1239    }
1240}