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}