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 get_volume_info(
667 &self,
668 volume: &str,
669 ) -> Result<fidl_fuchsia_fs_startup::VolumeInfo, Error> {
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 .get_info()
676 .await?
677 .map_err(|e| anyhow!(zx::Status::from_raw(e)))
678 }
679
680 pub async fn set_byte_limit(&self, volume: &str, byte_limit: u64) -> Result<(), Error> {
682 if byte_limit == 0 {
683 return Ok(());
684 }
685 let path = format!("volumes/{}", volume);
686 connect_to_named_protocol_at_dir_root::<fidl_fuchsia_fs_startup::VolumeMarker>(
687 self.exposed_dir.as_ref().unwrap(),
688 &path,
689 )?
690 .set_limit(byte_limit)
691 .await?
692 .map_err(|e| anyhow!(zx::Status::from_raw(e)))
693 }
694
695 pub async fn check_volume(&self, volume: &str, options: CheckOptions) -> Result<(), Error> {
696 let path = format!("volumes/{}", volume);
697 connect_to_named_protocol_at_dir_root::<fidl_fuchsia_fs_startup::VolumeMarker>(
698 self.exposed_dir.as_ref().unwrap(),
699 &path,
700 )?
701 .check(options)
702 .await?
703 .map_err(|e| anyhow!(zx::Status::from_raw(e)))?;
704 Ok(())
705 }
706
707 pub fn exposed_dir(&self) -> &fio::DirectoryProxy {
710 self.exposed_dir.as_ref().expect("BUG: exposed dir missing")
711 }
712
713 pub async fn shutdown(mut self) -> Result<(), ShutdownError> {
720 connect_to_protocol_at_dir_root::<fidl_fuchsia_fs::AdminMarker>(
721 &self.exposed_dir.take().expect("BUG: exposed dir missing"),
723 )?
724 .shutdown()
725 .await?;
726 Ok(())
727 }
728
729 pub fn take_exposed_dir(mut self) -> fio::DirectoryProxy {
733 self.component.take().expect("BUG: missing component").forget();
734 self.exposed_dir.take().expect("BUG: exposed dir missing")
735 }
736
737 pub async fn list_volumes(&self) -> Result<Vec<String>, Error> {
739 let volumes_dir = fuchsia_fs::directory::open_async::<fio::DirectoryMarker>(
740 self.exposed_dir(),
741 "volumes",
742 fio::PERM_READABLE,
743 )
744 .unwrap();
745 fuchsia_fs::directory::readdir(&volumes_dir)
746 .await
747 .map(|entries| entries.into_iter().map(|e| e.name).collect())
748 .map_err(|e| anyhow!("failed to read volumes dir: {}", e))
749 }
750}
751
752impl Drop for ServingMultiVolumeFilesystem {
753 fn drop(&mut self) {
754 if let Some(exposed_dir) = self.exposed_dir.take() {
755 if let Ok(proxy) =
757 connect_to_protocol_at_dir_root::<fidl_fuchsia_fs::AdminMarker>(&exposed_dir)
758 {
759 let _ = proxy.shutdown();
760 }
761 }
762 }
763}
764
765#[cfg(test)]
766mod tests {
767 use super::*;
768 use crate::{BlobCompression, BlobEvictionPolicy, Blobfs, F2fs, Fxfs, Minfs};
769 use delivery_blob::{CompressionMode, Type1Blob};
770 use fidl_fuchsia_fxfs::{BlobCreatorMarker, BlobReaderMarker};
771 use ramdevice_client::RamdiskClient;
772 use std::io::{Read as _, Write as _};
773
774 async fn ramdisk(block_size: u64) -> RamdiskClient {
775 RamdiskClient::create(block_size, 1 << 16).await.unwrap()
776 }
777
778 async fn new_fs<FSC: FSConfig>(ramdisk: &RamdiskClient, config: FSC) -> Filesystem {
779 Filesystem::new(ramdisk.open_controller().unwrap(), config)
780 }
781
782 #[fuchsia::test]
783 async fn blobfs_custom_config() {
784 let block_size = 512;
785 let ramdisk = ramdisk(block_size).await;
786 let config = Blobfs {
787 verbose: true,
788 readonly: true,
789 write_compression_algorithm: BlobCompression::Uncompressed,
790 cache_eviction_policy_override: BlobEvictionPolicy::EvictImmediately,
791 ..Default::default()
792 };
793 let mut blobfs = new_fs(&ramdisk, config).await;
794
795 blobfs.format().await.expect("failed to format blobfs");
796 blobfs.fsck().await.expect("failed to fsck blobfs");
797 let _ = blobfs.serve().await.expect("failed to serve blobfs");
798
799 ramdisk.destroy().await.expect("failed to destroy ramdisk");
800 }
801
802 #[fuchsia::test]
803 async fn blobfs_format_fsck_success() {
804 let block_size = 512;
805 let ramdisk = ramdisk(block_size).await;
806 let mut blobfs = new_fs(&ramdisk, Blobfs::default()).await;
807
808 blobfs.format().await.expect("failed to format blobfs");
809 blobfs.fsck().await.expect("failed to fsck blobfs");
810
811 ramdisk.destroy().await.expect("failed to destroy ramdisk");
812 }
813
814 #[fuchsia::test]
815 async fn blobfs_format_serve_write_query_restart_read_shutdown() {
816 let block_size = 512;
817 let ramdisk = ramdisk(block_size).await;
818 let mut blobfs = new_fs(&ramdisk, Blobfs::default()).await;
819
820 blobfs.format().await.expect("failed to format blobfs");
821
822 let serving = blobfs.serve().await.expect("failed to serve blobfs the first time");
823
824 let fs_info1 =
826 serving.query().await.expect("failed to query filesystem info after first serving");
827
828 let content = b"test content";
830 let merkle = fuchsia_merkle::root_from_slice(content);
831 let delivery_blob = Type1Blob::generate(content, CompressionMode::Never);
832
833 {
834 let creator = fuchsia_component_client::connect_to_protocol_at_dir_root::<
835 BlobCreatorMarker,
836 >(serving.exposed_dir())
837 .unwrap();
838 let writer = creator.create(&merkle.into(), false).await.unwrap().unwrap();
839 let mut writer =
840 blob_writer::BlobWriter::create(writer.into_proxy(), delivery_blob.len() as u64)
841 .await
842 .unwrap();
843 writer.write(&delivery_blob).await.unwrap();
844 }
845
846 let fs_info2 = serving.query().await.expect("failed to query filesystem info after write");
848 assert_eq!(
849 fs_info2.used_bytes - fs_info1.used_bytes,
850 fs_info2.block_size as u64 );
852
853 serving.shutdown().await.expect("failed to shutdown blobfs the first time");
854 let blobfs = new_fs(&ramdisk, Blobfs::default()).await;
855 let serving = blobfs.serve().await.expect("failed to serve blobfs the second time");
856 {
857 let reader = fuchsia_component_client::connect_to_protocol_at_dir_root::<
858 BlobReaderMarker,
859 >(serving.exposed_dir())
860 .unwrap();
861 let vmo = reader.get_vmo(&merkle.into()).await.unwrap().unwrap();
862 let read_content = vmo.read_to_vec::<u8>(0, content.len() as u64).unwrap();
863 assert_eq!(read_content, content);
864 }
865
866 let fs_info3 = serving.query().await.expect("failed to query filesystem info after read");
868 assert_eq!(
869 fs_info3.used_bytes - fs_info1.used_bytes,
870 fs_info3.block_size as u64 );
872
873 serving.shutdown().await.expect("failed to shutdown blobfs the second time");
874
875 ramdisk.destroy().await.expect("failed to destroy ramdisk");
876 }
877
878 #[fuchsia::test]
879 async fn blobfs_bind_to_path() {
880 let block_size = 512;
881 let test_content = b"test content";
882 let merkle = fuchsia_merkle::root_from_slice(test_content);
883 let delivery_blob = Type1Blob::generate(test_content, CompressionMode::Never);
884 let ramdisk = ramdisk(block_size).await;
885 let mut blobfs = new_fs(&ramdisk, Blobfs::default()).await;
886
887 blobfs.format().await.expect("failed to format blobfs");
888 let mut serving = blobfs.serve().await.expect("failed to serve blobfs");
889 serving.bind_to_path("/test-blobfs-path").expect("bind_to_path failed");
890
891 {
892 let creator = fuchsia_component_client::connect_to_protocol_at_dir_root::<
893 BlobCreatorMarker,
894 >(serving.exposed_dir())
895 .unwrap();
896 let writer = creator.create(&merkle.into(), false).await.unwrap().unwrap();
897 let mut writer =
898 blob_writer::BlobWriter::create(writer.into_proxy(), delivery_blob.len() as u64)
899 .await
900 .unwrap();
901 writer.write(&delivery_blob).await.unwrap();
902 }
903
904 let entries = std::fs::read_dir("/test-blobfs-path")
905 .unwrap()
906 .map(|entry| entry.unwrap().file_name().into_string().unwrap())
907 .collect::<Vec<_>>();
908 assert_eq!(entries, &[merkle.to_string()]);
909
910 serving.shutdown().await.expect("failed to shutdown blobfs");
911 }
912
913 #[fuchsia::test]
914 async fn minfs_custom_config() {
915 let block_size = 512;
916 let ramdisk = ramdisk(block_size).await;
917 let config = Minfs {
918 verbose: true,
919 readonly: true,
920 fsck_after_every_transaction: true,
921 ..Default::default()
922 };
923 let mut minfs = new_fs(&ramdisk, config).await;
924
925 minfs.format().await.expect("failed to format minfs");
926 minfs.fsck().await.expect("failed to fsck minfs");
927 let _ = minfs.serve().await.expect("failed to serve minfs");
928
929 ramdisk.destroy().await.expect("failed to destroy ramdisk");
930 }
931
932 #[fuchsia::test]
933 async fn minfs_format_fsck_success() {
934 let block_size = 8192;
935 let ramdisk = ramdisk(block_size).await;
936 let mut minfs = new_fs(&ramdisk, Minfs::default()).await;
937
938 minfs.format().await.expect("failed to format minfs");
939 minfs.fsck().await.expect("failed to fsck minfs");
940
941 ramdisk.destroy().await.expect("failed to destroy ramdisk");
942 }
943
944 #[fuchsia::test]
945 async fn minfs_format_serve_write_query_restart_read_shutdown() {
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 let serving = minfs.serve().await.expect("failed to serve minfs the first time");
952
953 let fs_info1 =
955 serving.query().await.expect("failed to query filesystem info after first serving");
956
957 let filename = "test_file";
958 let content = String::from("test content").into_bytes();
959
960 {
961 let test_file = fuchsia_fs::directory::open_file(
962 serving.root(),
963 filename,
964 fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_WRITABLE,
965 )
966 .await
967 .expect("failed to create test file");
968 let _: u64 = test_file
969 .write(&content)
970 .await
971 .expect("failed to write to test file")
972 .map_err(Status::from_raw)
973 .expect("write error");
974 }
975
976 let fs_info2 = serving.query().await.expect("failed to query filesystem info after write");
978 assert_eq!(
979 fs_info2.used_bytes - fs_info1.used_bytes,
980 fs_info2.block_size as u64 );
982
983 serving.shutdown().await.expect("failed to shutdown minfs the first time");
984 let minfs = new_fs(&ramdisk, Minfs::default()).await;
985 let serving = minfs.serve().await.expect("failed to serve minfs the second time");
986
987 {
988 let test_file =
989 fuchsia_fs::directory::open_file(serving.root(), filename, fio::PERM_READABLE)
990 .await
991 .expect("failed to open test file");
992 let read_content =
993 fuchsia_fs::file::read(&test_file).await.expect("failed to read from test file");
994 assert_eq!(content, read_content);
995 }
996
997 let fs_info3 = serving.query().await.expect("failed to query filesystem info after read");
999 assert_eq!(
1000 fs_info3.used_bytes - fs_info1.used_bytes,
1001 fs_info3.block_size as u64 );
1003
1004 let _ = serving.shutdown().await.expect("failed to shutdown minfs the second time");
1005
1006 ramdisk.destroy().await.expect("failed to destroy ramdisk");
1007 }
1008
1009 #[fuchsia::test]
1010 async fn minfs_bind_to_path() {
1011 let block_size = 8192;
1012 let test_content = b"test content";
1013 let ramdisk = ramdisk(block_size).await;
1014 let mut minfs = new_fs(&ramdisk, Minfs::default()).await;
1015
1016 minfs.format().await.expect("failed to format minfs");
1017 let mut serving = minfs.serve().await.expect("failed to serve minfs");
1018 serving.bind_to_path("/test-minfs-path").expect("bind_to_path failed");
1019 let test_path = "/test-minfs-path/test_file";
1020
1021 {
1022 let mut file = std::fs::File::create(test_path).expect("failed to create test file");
1023 file.write_all(test_content).expect("write bytes");
1024 }
1025
1026 {
1027 let mut file = std::fs::File::open(test_path).expect("failed to open test file");
1028 let mut buf = Vec::new();
1029 file.read_to_end(&mut buf).expect("failed to read test file");
1030 assert_eq!(buf, test_content);
1031 }
1032
1033 serving.shutdown().await.expect("failed to shutdown minfs");
1034
1035 std::fs::File::open(test_path).expect_err("test file was not unbound");
1036 }
1037
1038 #[fuchsia::test]
1039 async fn minfs_take_exposed_dir_does_not_drop() {
1040 let block_size = 512;
1041 let test_content = b"test content";
1042 let test_file_name = "test-file";
1043 let ramdisk = ramdisk(block_size).await;
1044 let mut minfs = new_fs(&ramdisk, Minfs::default()).await;
1045
1046 minfs.format().await.expect("failed to format fxfs");
1047
1048 let fs = minfs.serve().await.expect("failed to serve fxfs");
1049 let file = {
1050 let file = fuchsia_fs::directory::open_file(
1051 fs.root(),
1052 test_file_name,
1053 fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_READABLE | fio::PERM_WRITABLE,
1054 )
1055 .await
1056 .unwrap();
1057 fuchsia_fs::file::write(&file, test_content).await.unwrap();
1058 file.close().await.expect("close fidl error").expect("close error");
1059 fuchsia_fs::directory::open_file(fs.root(), test_file_name, fio::PERM_READABLE)
1060 .await
1061 .unwrap()
1062 };
1063
1064 let exposed_dir = fs.take_exposed_dir();
1065
1066 assert_eq!(fuchsia_fs::file::read(&file).await.unwrap(), test_content);
1067
1068 connect_to_protocol_at_dir_root::<fidl_fuchsia_fs::AdminMarker>(&exposed_dir)
1069 .expect("connecting to admin marker")
1070 .shutdown()
1071 .await
1072 .expect("shutdown failed");
1073 }
1074
1075 #[fuchsia::test]
1076 async fn f2fs_format_fsck_success() {
1077 let block_size = 4096;
1078 let ramdisk = ramdisk(block_size).await;
1079 let mut f2fs = new_fs(&ramdisk, F2fs::default()).await;
1080
1081 f2fs.format().await.expect("failed to format f2fs");
1082 f2fs.fsck().await.expect("failed to fsck f2fs");
1083
1084 ramdisk.destroy().await.expect("failed to destroy ramdisk");
1085 }
1086
1087 #[fuchsia::test]
1088 async fn f2fs_format_serve_write_query_restart_read_shutdown() {
1089 let block_size = 4096;
1090 let ramdisk = ramdisk(block_size).await;
1091 let mut f2fs = new_fs(&ramdisk, F2fs::default()).await;
1092
1093 f2fs.format().await.expect("failed to format f2fs");
1094 let serving = f2fs.serve().await.expect("failed to serve f2fs the first time");
1095
1096 let fs_info1 =
1098 serving.query().await.expect("failed to query filesystem info after first serving");
1099
1100 let filename = "test_file";
1101 let content = String::from("test content").into_bytes();
1102
1103 {
1104 let test_file = fuchsia_fs::directory::open_file(
1105 serving.root(),
1106 filename,
1107 fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_WRITABLE,
1108 )
1109 .await
1110 .expect("failed to create test file");
1111 let _: u64 = test_file
1112 .write(&content)
1113 .await
1114 .expect("failed to write to test file")
1115 .map_err(Status::from_raw)
1116 .expect("write error");
1117 }
1118
1119 let fs_info2 = serving.query().await.expect("failed to query filesystem info after write");
1121 let expected_size2 = fs_info2.block_size * 2;
1126 assert_eq!(fs_info2.used_bytes - fs_info1.used_bytes, expected_size2 as u64);
1127
1128 serving.shutdown().await.expect("failed to shutdown f2fs the first time");
1129 let f2fs = new_fs(&ramdisk, F2fs::default()).await;
1130 let serving = f2fs.serve().await.expect("failed to serve f2fs the second time");
1131
1132 {
1133 let test_file =
1134 fuchsia_fs::directory::open_file(serving.root(), filename, fio::PERM_READABLE)
1135 .await
1136 .expect("failed to open test file");
1137 let read_content =
1138 fuchsia_fs::file::read(&test_file).await.expect("failed to read from test file");
1139 assert_eq!(content, read_content);
1140 }
1141
1142 let fs_info3 = serving.query().await.expect("failed to query filesystem info after read");
1144 let expected_size3 = fs_info3.block_size * 2;
1146 assert_eq!(fs_info3.used_bytes - fs_info1.used_bytes, expected_size3 as u64);
1147
1148 serving.shutdown().await.expect("failed to shutdown f2fs the second time");
1149 let mut f2fs = new_fs(&ramdisk, F2fs::default()).await;
1150 f2fs.fsck().await.expect("failed to fsck f2fs after shutting down the second time");
1151
1152 ramdisk.destroy().await.expect("failed to destroy ramdisk");
1153 }
1154
1155 #[fuchsia::test]
1156 async fn f2fs_bind_to_path() {
1157 let block_size = 4096;
1158 let test_content = b"test content";
1159 let ramdisk = ramdisk(block_size).await;
1160 let mut f2fs = new_fs(&ramdisk, F2fs::default()).await;
1161
1162 f2fs.format().await.expect("failed to format f2fs");
1163 let mut serving = f2fs.serve().await.expect("failed to serve f2fs");
1164 serving.bind_to_path("/test-f2fs-path").expect("bind_to_path failed");
1165 let test_path = "/test-f2fs-path/test_file";
1166
1167 {
1168 let mut file = std::fs::File::create(test_path).expect("failed to create test file");
1169 file.write_all(test_content).expect("write bytes");
1170 }
1171
1172 {
1173 let mut file = std::fs::File::open(test_path).expect("failed to open test file");
1174 let mut buf = Vec::new();
1175 file.read_to_end(&mut buf).expect("failed to read test file");
1176 assert_eq!(buf, test_content);
1177 }
1178
1179 serving.shutdown().await.expect("failed to shutdown f2fs");
1180
1181 std::fs::File::open(test_path).expect_err("test file was not unbound");
1182 }
1183
1184 #[fuchsia::test]
1185 async fn fxfs_open_volume() {
1186 let block_size = 512;
1187 let ramdisk = ramdisk(block_size).await;
1188 let mut fxfs = new_fs(&ramdisk, Fxfs::default()).await;
1189
1190 fxfs.format().await.expect("failed to format fxfs");
1191
1192 let fs = fxfs.serve_multi_volume().await.expect("failed to serve fxfs");
1193
1194 assert_eq!(fs.has_volume("foo").await.expect("has_volume"), false);
1195 assert!(
1196 fs.open_volume("foo", MountOptions::default()).await.is_err(),
1197 "Opening nonexistent volume should fail"
1198 );
1199
1200 let vol = fs
1201 .create_volume("foo", CreateOptions::default(), MountOptions::default())
1202 .await
1203 .expect("Create volume failed");
1204 vol.query().await.expect("Query volume failed");
1205 assert_eq!(fs.has_volume("foo").await.expect("has_volume"), true);
1211 }
1212
1213 #[fuchsia::test]
1214 async fn fxfs_take_exposed_dir_does_not_drop() {
1215 let block_size = 512;
1216 let test_content = b"test content";
1217 let test_file_name = "test-file";
1218 let ramdisk = ramdisk(block_size).await;
1219 let mut fxfs = new_fs(&ramdisk, Fxfs::default()).await;
1220
1221 fxfs.format().await.expect("failed to format fxfs");
1222
1223 let fs = fxfs.serve_multi_volume().await.expect("failed to serve fxfs");
1224 let file = {
1225 let vol = fs
1226 .create_volume("foo", CreateOptions::default(), MountOptions::default())
1227 .await
1228 .expect("Create volume failed");
1229 let file = fuchsia_fs::directory::open_file(
1230 vol.root(),
1231 test_file_name,
1232 fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_READABLE | fio::PERM_WRITABLE,
1233 )
1234 .await
1235 .unwrap();
1236 fuchsia_fs::file::write(&file, test_content).await.unwrap();
1237 file.close().await.expect("close fidl error").expect("close error");
1238 fuchsia_fs::directory::open_file(vol.root(), test_file_name, fio::PERM_READABLE)
1239 .await
1240 .unwrap()
1241 };
1242
1243 let exposed_dir = fs.take_exposed_dir();
1244
1245 assert_eq!(fuchsia_fs::file::read(&file).await.unwrap(), test_content);
1246
1247 connect_to_protocol_at_dir_root::<fidl_fuchsia_fs::AdminMarker>(&exposed_dir)
1248 .expect("connecting to admin marker")
1249 .shutdown()
1250 .await
1251 .expect("shutdown failed");
1252 }
1253}