1use crate::error::{QueryError, ShutdownError};
8use crate::{ComponentType, FSConfig, Options};
9use anyhow::{anyhow, bail, ensure, Context, Error};
10use fidl::endpoints::{create_endpoints, create_proxy, ClientEnd, ServerEnd};
11use fidl_fuchsia_component::{self as fcomponent, RealmMarker};
12use fidl_fuchsia_fs::AdminMarker;
13use fidl_fuchsia_fs_startup::{CheckOptions, CreateOptions, MountOptions, StartupMarker};
14use fidl_fuchsia_hardware_block_volume::VolumeMarker;
15use fuchsia_component_client::{
16 connect_to_named_protocol_at_dir_root, connect_to_protocol, connect_to_protocol_at_dir_root,
17 connect_to_protocol_at_dir_svc, open_childs_exposed_directory,
18};
19use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
20use std::sync::Arc;
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 {
26 fn connect_channel_to_volume(&self, server_end: ServerEnd<VolumeMarker>) -> Result<(), Error>;
27 fn connect_volume(&self) -> Result<ClientEnd<VolumeMarker>, Error> {
28 let (client, server) = fidl::endpoints::create_endpoints();
29 self.connect_channel_to_volume(server)?;
30 Ok(client)
31 }
32 fn connect_partition(
33 &self,
34 ) -> Result<ClientEnd<fidl_fuchsia_hardware_block_partition::PartitionMarker>, Error> {
35 self.connect_volume().map(|v| ClientEnd::new(v.into_channel()))
36 }
37 fn connect_block(&self) -> Result<ClientEnd<fidl_fuchsia_hardware_block::BlockMarker>, Error> {
38 self.connect_volume().map(|v| ClientEnd::new(v.into_channel()))
39 }
40}
41
42#[derive(Clone, Debug)]
44pub struct DirBasedBlockConnector(fio::DirectoryProxy, String);
45
46impl DirBasedBlockConnector {
47 pub fn new(dir: fio::DirectoryProxy, path: String) -> Self {
48 Self(dir, path)
49 }
50
51 pub fn path(&self) -> &str {
52 &self.1
53 }
54}
55
56impl BlockConnector for DirBasedBlockConnector {
57 fn connect_channel_to_volume(&self, server_end: ServerEnd<VolumeMarker>) -> Result<(), Error> {
58 self.0.open(
59 self.path(),
60 fio::Flags::PROTOCOL_SERVICE,
61 &fio::Options::default(),
62 server_end.into_channel(),
63 )?;
64 Ok(())
65 }
66}
67
68impl BlockConnector for fidl_fuchsia_device::ControllerProxy {
69 fn connect_channel_to_volume(&self, server_end: ServerEnd<VolumeMarker>) -> Result<(), Error> {
70 let () = self.connect_to_device_fidl(server_end.into_channel())?;
71 Ok(())
72 }
73}
74
75impl BlockConnector for fidl_fuchsia_storage_partitions::PartitionServiceProxy {
76 fn connect_channel_to_volume(&self, server_end: ServerEnd<VolumeMarker>) -> Result<(), Error> {
77 self.connect_channel_to_volume(server_end)?;
78 Ok(())
79 }
80}
81
82impl<T: BlockConnector> BlockConnector for Arc<T> {
86 fn connect_channel_to_volume(&self, server_end: ServerEnd<VolumeMarker>) -> Result<(), Error> {
87 self.as_ref().connect_channel_to_volume(server_end)
88 }
89}
90
91impl<F> BlockConnector for F
92where
93 F: Fn(ServerEnd<VolumeMarker>) -> Result<(), Error> + Send + Sync,
94{
95 fn connect_channel_to_volume(&self, server_end: ServerEnd<VolumeMarker>) -> Result<(), Error> {
96 self(server_end)
97 }
98}
99
100pub struct Filesystem {
102 config: Box<dyn FSConfig>,
109 block_connector: Box<dyn BlockConnector>,
110 component: Option<Arc<DynamicComponentInstance>>,
111}
112
113static COLLECTION_COUNTER: AtomicU64 = AtomicU64::new(0);
115
116impl Filesystem {
117 pub fn config(&self) -> &dyn FSConfig {
118 self.config.as_ref()
119 }
120
121 pub fn into_config(self) -> Box<dyn FSConfig> {
122 self.config
123 }
124
125 pub fn new<B: BlockConnector + 'static, FSC: FSConfig>(
127 block_connector: B,
128 config: FSC,
129 ) -> Self {
130 Self::from_boxed_config(Box::new(block_connector), Box::new(config))
131 }
132
133 pub fn from_boxed_config(
135 block_connector: Box<dyn BlockConnector>,
136 config: Box<dyn FSConfig>,
137 ) -> Self {
138 Self { config, block_connector, component: None }
139 }
140
141 pub fn get_component_moniker(&self) -> Option<String> {
143 Some(match self.config.options().component_type {
144 ComponentType::StaticChild => self.config.options().component_name.to_string(),
145 ComponentType::DynamicChild { .. } => {
146 let component = self.component.as_ref()?;
147 format!("{}:{}", component.collection, component.name)
148 }
149 })
150 }
151
152 async fn get_component_exposed_dir(&mut self) -> Result<fio::DirectoryProxy, Error> {
153 let options = self.config.options();
154 let component_name = options.component_name;
155 match options.component_type {
156 ComponentType::StaticChild => open_childs_exposed_directory(component_name, None).await,
157 ComponentType::DynamicChild { collection_name } => {
158 if let Some(component) = &self.component {
159 return open_childs_exposed_directory(
160 component.name.clone(),
161 Some(component.collection.clone()),
162 )
163 .await;
164 }
165
166 let name = format!(
170 "{}-{}-{}",
171 component_name,
172 fuchsia_runtime::process_self().get_koid().unwrap().raw_koid(),
173 COLLECTION_COUNTER.fetch_add(1, Ordering::Relaxed)
174 );
175
176 let collection_ref = fdecl::CollectionRef { name: collection_name };
177 let child_decls = vec![
178 fdecl::Child {
179 name: Some(format!("{}-relative", name)),
180 url: Some(format!("#meta/{}.cm", component_name)),
181 startup: Some(fdecl::StartupMode::Lazy),
182 ..Default::default()
183 },
184 fdecl::Child {
185 name: Some(name),
186 url: Some(format!(
187 "fuchsia-boot:///{}#meta/{}.cm",
188 component_name, component_name
189 )),
190 startup: Some(fdecl::StartupMode::Lazy),
191 ..Default::default()
192 },
193 ];
194 let realm_proxy = connect_to_protocol::<RealmMarker>()?;
195 for child_decl in child_decls {
196 realm_proxy
198 .create_child(
199 &collection_ref,
200 &child_decl,
201 fcomponent::CreateChildArgs::default(),
202 )
203 .await?
204 .map_err(|e| anyhow!("create_child failed: {:?}", e))?;
205
206 let component = Arc::new(DynamicComponentInstance {
207 name: child_decl.name.unwrap(),
208 collection: collection_ref.name.clone(),
209 should_not_drop: AtomicBool::new(false),
210 });
211
212 if let Ok(proxy) = open_childs_exposed_directory(
213 component.name.clone(),
214 Some(component.collection.clone()),
215 )
216 .await
217 {
218 self.component = Some(component);
219 return Ok(proxy);
220 }
221 }
222 Err(anyhow!("Failed to open exposed directory"))
223 }
224 }
225 }
226
227 pub async fn format(&mut self) -> Result<(), Error> {
240 let channel = self.block_connector.connect_block()?;
241
242 let exposed_dir = self.get_component_exposed_dir().await?;
243 let proxy = connect_to_protocol_at_dir_root::<StartupMarker>(&exposed_dir)?;
244 proxy
245 .format(channel, &self.config().options().format_options)
246 .await?
247 .map_err(Status::from_raw)?;
248
249 Ok(())
250 }
251
252 pub async fn fsck(&mut self) -> Result<(), Error> {
265 let channel = self.block_connector.connect_block()?;
266 let exposed_dir = self.get_component_exposed_dir().await?;
267 let proxy = connect_to_protocol_at_dir_root::<StartupMarker>(&exposed_dir)?;
268 proxy.check(channel, CheckOptions::default()).await?.map_err(Status::from_raw)?;
269 Ok(())
270 }
271
272 pub async fn serve(&mut self) -> Result<ServingSingleVolumeFilesystem, Error> {
279 if self.config.is_multi_volume() {
280 bail!("Can't serve a multivolume filesystem; use serve_multi_volume");
281 }
282 let Options { start_options, reuse_component_after_serving, .. } = self.config.options();
283
284 let exposed_dir = self.get_component_exposed_dir().await?;
285 let proxy = connect_to_protocol_at_dir_root::<StartupMarker>(&exposed_dir)?;
286 proxy
287 .start(self.block_connector.connect_block()?, &start_options)
288 .await?
289 .map_err(Status::from_raw)?;
290
291 let (root_dir, server_end) = create_endpoints::<fio::NodeMarker>();
292 exposed_dir.open(
293 "root",
294 fio::PERM_READABLE | fio::Flags::PERM_INHERIT_WRITE | fio::Flags::PERM_INHERIT_EXECUTE,
295 &Default::default(),
296 server_end.into_channel(),
297 )?;
298 let component = self.component.clone();
299 if !reuse_component_after_serving {
300 self.component = None;
301 }
302 Ok(ServingSingleVolumeFilesystem {
303 component,
304 exposed_dir: Some(exposed_dir),
305 root_dir: ClientEnd::<fio::DirectoryMarker>::new(root_dir.into_channel()).into_proxy(),
306 binding: None,
307 })
308 }
309
310 pub async fn serve_multi_volume(&mut self) -> Result<ServingMultiVolumeFilesystem, Error> {
318 if !self.config.is_multi_volume() {
319 bail!("Can't serve_multi_volume a single-volume filesystem; use serve");
320 }
321
322 let exposed_dir = self.get_component_exposed_dir().await?;
323 let proxy = connect_to_protocol_at_dir_root::<StartupMarker>(&exposed_dir)?;
324 proxy
325 .start(self.block_connector.connect_block()?, &self.config.options().start_options)
326 .await?
327 .map_err(Status::from_raw)?;
328
329 Ok(ServingMultiVolumeFilesystem {
330 component: self.component.clone(),
331 exposed_dir: Some(exposed_dir),
332 })
333 }
334}
335
336struct DynamicComponentInstance {
338 name: String,
339 collection: String,
340 should_not_drop: AtomicBool,
341}
342
343impl DynamicComponentInstance {
344 fn forget(&self) {
345 self.should_not_drop.store(true, Ordering::Relaxed);
346 }
347}
348
349impl Drop for DynamicComponentInstance {
350 fn drop(&mut self) {
351 if self.should_not_drop.load(Ordering::Relaxed) {
352 return;
353 }
354 if let Ok(realm_proxy) = connect_to_protocol::<RealmMarker>() {
355 let _ = realm_proxy.destroy_child(&fdecl::ChildRef {
356 name: self.name.clone(),
357 collection: Some(self.collection.clone()),
358 });
359 }
360 }
361}
362
363#[derive(Default)]
366pub struct NamespaceBinding(String);
367
368impl NamespaceBinding {
369 pub fn create(root_dir: &fio::DirectoryProxy, path: String) -> Result<NamespaceBinding, Error> {
370 let (client_end, server_end) = create_endpoints();
371 root_dir.clone(ServerEnd::new(server_end.into_channel()))?;
372 let namespace = fdio::Namespace::installed()?;
373 namespace.bind(&path, client_end)?;
374 Ok(Self(path))
375 }
376}
377
378impl std::ops::Deref for NamespaceBinding {
379 type Target = str;
380 fn deref(&self) -> &Self::Target {
381 &self.0
382 }
383}
384
385impl Drop for NamespaceBinding {
386 fn drop(&mut self) {
387 if let Ok(namespace) = fdio::Namespace::installed() {
388 let _ = namespace.unbind(&self.0);
389 }
390 }
391}
392
393pub type ServingFilesystem = ServingSingleVolumeFilesystem;
395
396pub struct ServingSingleVolumeFilesystem {
398 component: Option<Arc<DynamicComponentInstance>>,
399 exposed_dir: Option<fio::DirectoryProxy>,
401 root_dir: fio::DirectoryProxy,
402
403 binding: Option<NamespaceBinding>,
405}
406
407impl ServingSingleVolumeFilesystem {
408 pub fn exposed_dir(&self) -> &fio::DirectoryProxy {
410 self.exposed_dir.as_ref().unwrap()
411 }
412
413 pub fn root(&self) -> &fio::DirectoryProxy {
415 &self.root_dir
416 }
417
418 pub fn bind_to_path(&mut self, path: &str) -> Result<(), Error> {
426 ensure!(self.binding.is_none(), "Already bound");
427 self.binding = Some(NamespaceBinding::create(&self.root_dir, path.to_string())?);
428 Ok(())
429 }
430
431 pub fn bound_path(&self) -> Option<&str> {
432 self.binding.as_deref()
433 }
434
435 pub async fn query(&self) -> Result<Box<fio::FilesystemInfo>, QueryError> {
441 let (status, info) = self.root_dir.query_filesystem().await?;
442 Status::ok(status).map_err(QueryError::DirectoryQuery)?;
443 info.ok_or(QueryError::DirectoryEmptyResult)
444 }
445
446 pub fn take_exposed_dir(mut self) -> fio::DirectoryProxy {
450 self.component.take().expect("BUG: component missing").forget();
451 self.exposed_dir.take().expect("BUG: exposed dir missing")
452 }
453
454 pub async fn shutdown(mut self) -> Result<(), ShutdownError> {
462 connect_to_protocol_at_dir_root::<fidl_fuchsia_fs::AdminMarker>(
463 &self.exposed_dir.take().expect("BUG: exposed dir missing"),
464 )?
465 .shutdown()
466 .await?;
467 Ok(())
468 }
469
470 pub async fn kill(self) -> Result<(), Error> {
477 self.shutdown().await?;
481 Ok(())
482 }
483}
484
485impl Drop for ServingSingleVolumeFilesystem {
486 fn drop(&mut self) {
487 if let Some(exposed_dir) = self.exposed_dir.take() {
489 if let Ok(proxy) =
490 connect_to_protocol_at_dir_root::<fidl_fuchsia_fs::AdminMarker>(&exposed_dir)
491 {
492 let _ = proxy.shutdown();
493 }
494 }
495 }
496}
497
498pub struct ServingMultiVolumeFilesystem {
501 component: Option<Arc<DynamicComponentInstance>>,
502 exposed_dir: Option<fio::DirectoryProxy>,
504}
505
506pub struct ServingVolume {
508 root_dir: fio::DirectoryProxy,
509 binding: Option<NamespaceBinding>,
510 exposed_dir: fio::DirectoryProxy,
511}
512
513impl ServingVolume {
514 fn new(exposed_dir: fio::DirectoryProxy) -> Result<Self, Error> {
515 let (root_dir, server_end) = create_endpoints::<fio::NodeMarker>();
516 exposed_dir.open(
517 "root",
518 fio::PERM_READABLE | fio::Flags::PERM_INHERIT_WRITE | fio::Flags::PERM_INHERIT_EXECUTE,
519 &Default::default(),
520 server_end.into_channel(),
521 )?;
522 Ok(ServingVolume {
523 root_dir: ClientEnd::<fio::DirectoryMarker>::new(root_dir.into_channel()).into_proxy(),
524 binding: None,
525 exposed_dir,
526 })
527 }
528
529 pub fn root(&self) -> &fio::DirectoryProxy {
531 &self.root_dir
532 }
533
534 pub fn exposed_dir(&self) -> &fio::DirectoryProxy {
536 &self.exposed_dir
537 }
538
539 pub fn bind_to_path(&mut self, path: &str) -> Result<(), Error> {
547 ensure!(self.binding.is_none(), "Already bound");
548 self.binding = Some(NamespaceBinding::create(&self.root_dir, path.to_string())?);
549 Ok(())
550 }
551
552 pub fn unbind_path(&mut self) {
556 let _ = self.binding.take();
557 }
558
559 pub fn bound_path(&self) -> Option<&str> {
560 self.binding.as_deref()
561 }
562
563 pub async fn query(&self) -> Result<Box<fio::FilesystemInfo>, QueryError> {
569 let (status, info) = self.root_dir.query_filesystem().await?;
570 Status::ok(status).map_err(QueryError::DirectoryQuery)?;
571 info.ok_or(QueryError::DirectoryEmptyResult)
572 }
573
574 pub async fn shutdown(self) -> Result<(), Error> {
577 let admin_proxy = connect_to_protocol_at_dir_svc::<AdminMarker>(self.exposed_dir())?;
578 admin_proxy.shutdown().await.context("failed to shutdown volume")?;
579 Ok(())
580 }
581}
582
583impl ServingMultiVolumeFilesystem {
584 pub async fn has_volume(&mut self, volume: &str) -> Result<bool, Error> {
586 let path = format!("volumes/{}", volume);
587 fuchsia_fs::directory::open_node(
588 self.exposed_dir.as_ref().unwrap(),
589 &path,
590 fio::Flags::PROTOCOL_NODE,
591 )
592 .await
593 .map(|_| true)
594 .or_else(|e| {
595 if let fuchsia_fs::node::OpenError::OpenError(status) = &e {
596 if *status == zx::Status::NOT_FOUND {
597 return Ok(false);
598 }
599 }
600 Err(e.into())
601 })
602 }
603
604 pub async fn create_volume(
608 &self,
609 volume: &str,
610 create_options: CreateOptions,
611 options: MountOptions,
612 ) -> Result<ServingVolume, Error> {
613 let (exposed_dir, server) = create_proxy::<fio::DirectoryMarker>();
614 connect_to_protocol_at_dir_root::<fidl_fuchsia_fs_startup::VolumesMarker>(
615 self.exposed_dir.as_ref().unwrap(),
616 )?
617 .create(volume, server, create_options, options)
618 .await?
619 .map_err(|e| anyhow!(zx::Status::from_raw(e)))?;
620 ServingVolume::new(exposed_dir)
621 }
622
623 pub async fn remove_volume(&self, volume: &str) -> Result<(), Error> {
625 connect_to_protocol_at_dir_root::<fidl_fuchsia_fs_startup::VolumesMarker>(
626 self.exposed_dir.as_ref().unwrap(),
627 )?
628 .remove(volume)
629 .await?
630 .map_err(|e| anyhow!(zx::Status::from_raw(e)))
631 }
632
633 pub async fn open_volume(
636 &self,
637 volume: &str,
638 options: MountOptions,
639 ) -> Result<ServingVolume, Error> {
640 let (exposed_dir, server) = create_proxy::<fio::DirectoryMarker>();
641 let path = format!("volumes/{}", volume);
642 connect_to_named_protocol_at_dir_root::<fidl_fuchsia_fs_startup::VolumeMarker>(
643 self.exposed_dir.as_ref().unwrap(),
644 &path,
645 )?
646 .mount(server, options)
647 .await?
648 .map_err(|e| anyhow!(zx::Status::from_raw(e)))?;
649
650 ServingVolume::new(exposed_dir)
651 }
652
653 pub async fn set_byte_limit(&self, volume: &str, byte_limit: u64) -> Result<(), Error> {
655 if byte_limit == 0 {
656 return Ok(());
657 }
658 let path = format!("volumes/{}", volume);
659 connect_to_named_protocol_at_dir_root::<fidl_fuchsia_fs_startup::VolumeMarker>(
660 self.exposed_dir.as_ref().unwrap(),
661 &path,
662 )?
663 .set_limit(byte_limit)
664 .await?
665 .map_err(|e| anyhow!(zx::Status::from_raw(e)))
666 }
667
668 pub async fn check_volume(&self, volume: &str, options: CheckOptions) -> Result<(), Error> {
669 let path = format!("volumes/{}", volume);
670 connect_to_named_protocol_at_dir_root::<fidl_fuchsia_fs_startup::VolumeMarker>(
671 self.exposed_dir.as_ref().unwrap(),
672 &path,
673 )?
674 .check(options)
675 .await?
676 .map_err(|e| anyhow!(zx::Status::from_raw(e)))?;
677 Ok(())
678 }
679
680 pub fn exposed_dir(&self) -> &fio::DirectoryProxy {
683 self.exposed_dir.as_ref().expect("BUG: exposed dir missing")
684 }
685
686 pub async fn shutdown(mut self) -> Result<(), ShutdownError> {
693 connect_to_protocol_at_dir_root::<fidl_fuchsia_fs::AdminMarker>(
694 &self.exposed_dir.take().expect("BUG: exposed dir missing"),
696 )?
697 .shutdown()
698 .await?;
699 Ok(())
700 }
701
702 pub fn take_exposed_dir(mut self) -> fio::DirectoryProxy {
706 self.component.take().expect("BUG: missing component").forget();
707 self.exposed_dir.take().expect("BUG: exposed dir missing")
708 }
709}
710
711impl Drop for ServingMultiVolumeFilesystem {
712 fn drop(&mut self) {
713 if let Some(exposed_dir) = self.exposed_dir.take() {
714 if let Ok(proxy) =
716 connect_to_protocol_at_dir_root::<fidl_fuchsia_fs::AdminMarker>(&exposed_dir)
717 {
718 let _ = proxy.shutdown();
719 }
720 }
721 }
722}
723
724#[cfg(test)]
725mod tests {
726 use super::*;
727 use crate::{BlobCompression, BlobEvictionPolicy, Blobfs, F2fs, Fxfs, Minfs};
728 use fuchsia_async as fasync;
729 use ramdevice_client::RamdiskClient;
730 use std::io::{Read as _, Write as _};
731 use std::time::Duration;
732
733 async fn ramdisk(block_size: u64) -> RamdiskClient {
734 RamdiskClient::create(block_size, 1 << 16).await.unwrap()
735 }
736
737 async fn new_fs<FSC: FSConfig>(ramdisk: &mut RamdiskClient, config: FSC) -> Filesystem {
738 Filesystem::new(ramdisk.take_controller().unwrap(), config)
739 }
740
741 #[fuchsia::test]
742 async fn blobfs_custom_config() {
743 let block_size = 512;
744 let mut ramdisk = ramdisk(block_size).await;
745 let config = Blobfs {
746 verbose: true,
747 readonly: true,
748 write_compression_algorithm: BlobCompression::Uncompressed,
749 cache_eviction_policy_override: BlobEvictionPolicy::EvictImmediately,
750 ..Default::default()
751 };
752 let mut blobfs = new_fs(&mut ramdisk, config).await;
753
754 blobfs.format().await.expect("failed to format blobfs");
755 blobfs.fsck().await.expect("failed to fsck blobfs");
756 let _ = blobfs.serve().await.expect("failed to serve blobfs");
757
758 ramdisk.destroy().await.expect("failed to destroy ramdisk");
759 }
760
761 #[fuchsia::test]
762 async fn blobfs_format_fsck_success() {
763 let block_size = 512;
764 let mut ramdisk = ramdisk(block_size).await;
765 let mut blobfs = new_fs(&mut ramdisk, Blobfs::default()).await;
766
767 blobfs.format().await.expect("failed to format blobfs");
768 blobfs.fsck().await.expect("failed to fsck blobfs");
769
770 ramdisk.destroy().await.expect("failed to destroy ramdisk");
771 }
772
773 #[fuchsia::test]
774 async fn blobfs_format_serve_write_query_restart_read_shutdown() {
775 let block_size = 512;
776 let mut ramdisk = ramdisk(block_size).await;
777 let mut blobfs = new_fs(&mut ramdisk, Blobfs::default()).await;
778
779 blobfs.format().await.expect("failed to format blobfs");
780
781 let serving = blobfs.serve().await.expect("failed to serve blobfs the first time");
782
783 let fs_info1 =
785 serving.query().await.expect("failed to query filesystem info after first serving");
786
787 let merkle = "be901a14ec42ee0a8ee220eb119294cdd40d26d573139ee3d51e4430e7d08c28";
789 let content = String::from("test content").into_bytes();
790
791 {
792 let test_file = fuchsia_fs::directory::open_file(
793 serving.root(),
794 merkle,
795 fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_WRITABLE,
796 )
797 .await
798 .expect("failed to create test file");
799 let () = test_file
800 .resize(content.len() as u64)
801 .await
802 .expect("failed to send resize FIDL")
803 .map_err(Status::from_raw)
804 .expect("failed to resize file");
805 let _: u64 = test_file
806 .write(&content)
807 .await
808 .expect("failed to write to test file")
809 .map_err(Status::from_raw)
810 .expect("write error");
811 }
812
813 let fs_info2 = serving.query().await.expect("failed to query filesystem info after write");
815 assert_eq!(
816 fs_info2.used_bytes - fs_info1.used_bytes,
817 fs_info2.block_size as u64 );
819
820 serving.shutdown().await.expect("failed to shutdown blobfs the first time");
821 let serving = blobfs.serve().await.expect("failed to serve blobfs the second time");
822 {
823 let test_file =
824 fuchsia_fs::directory::open_file(serving.root(), merkle, fio::PERM_READABLE)
825 .await
826 .expect("failed to open test file");
827 let read_content =
828 fuchsia_fs::file::read(&test_file).await.expect("failed to read from test file");
829 assert_eq!(content, read_content);
830 }
831
832 let fs_info3 = serving.query().await.expect("failed to query filesystem info after read");
834 assert_eq!(
835 fs_info3.used_bytes - fs_info1.used_bytes,
836 fs_info3.block_size as u64 );
838
839 serving.shutdown().await.expect("failed to shutdown blobfs the second time");
840
841 ramdisk.destroy().await.expect("failed to destroy ramdisk");
842 }
843
844 #[fuchsia::test]
845 async fn blobfs_bind_to_path() {
846 let block_size = 512;
847 let merkle = "be901a14ec42ee0a8ee220eb119294cdd40d26d573139ee3d51e4430e7d08c28";
848 let test_content = b"test content";
849 let mut ramdisk = ramdisk(block_size).await;
850 let mut blobfs = new_fs(&mut ramdisk, Blobfs::default()).await;
851
852 blobfs.format().await.expect("failed to format blobfs");
853 let mut serving = blobfs.serve().await.expect("failed to serve blobfs");
854 serving.bind_to_path("/test-blobfs-path").expect("bind_to_path failed");
855 let test_path = format!("/test-blobfs-path/{}", merkle);
856
857 {
858 let mut file = std::fs::File::create(&test_path).expect("failed to create test file");
859 file.set_len(test_content.len() as u64).expect("failed to set size");
860 file.write_all(test_content).expect("write bytes");
861 }
862
863 {
864 let mut file = std::fs::File::open(&test_path).expect("failed to open test file");
865 let mut buf = Vec::new();
866 file.read_to_end(&mut buf).expect("failed to read test file");
867 assert_eq!(buf, test_content);
868 }
869
870 serving.shutdown().await.expect("failed to shutdown blobfs");
871
872 std::fs::File::open(&test_path).expect_err("test file was not unbound");
873 }
874
875 #[fuchsia::test]
876 async fn minfs_custom_config() {
877 let block_size = 512;
878 let mut ramdisk = ramdisk(block_size).await;
879 let config = Minfs {
880 verbose: true,
881 readonly: true,
882 fsck_after_every_transaction: true,
883 ..Default::default()
884 };
885 let mut minfs = new_fs(&mut ramdisk, config).await;
886
887 minfs.format().await.expect("failed to format minfs");
888 minfs.fsck().await.expect("failed to fsck minfs");
889 let _ = minfs.serve().await.expect("failed to serve minfs");
890
891 ramdisk.destroy().await.expect("failed to destroy ramdisk");
892 }
893
894 #[fuchsia::test]
895 async fn minfs_format_fsck_success() {
896 let block_size = 8192;
897 let mut ramdisk = ramdisk(block_size).await;
898 let mut minfs = new_fs(&mut ramdisk, Minfs::default()).await;
899
900 minfs.format().await.expect("failed to format minfs");
901 minfs.fsck().await.expect("failed to fsck minfs");
902
903 ramdisk.destroy().await.expect("failed to destroy ramdisk");
904 }
905
906 #[fuchsia::test]
907 async fn minfs_format_serve_write_query_restart_read_shutdown() {
908 let block_size = 8192;
909 let mut ramdisk = ramdisk(block_size).await;
910 let mut minfs = new_fs(&mut ramdisk, Minfs::default()).await;
911
912 minfs.format().await.expect("failed to format minfs");
913 let serving = minfs.serve().await.expect("failed to serve minfs the first time");
914
915 let fs_info1 =
917 serving.query().await.expect("failed to query filesystem info after first serving");
918
919 let filename = "test_file";
920 let content = String::from("test content").into_bytes();
921
922 {
923 let test_file = fuchsia_fs::directory::open_file(
924 serving.root(),
925 filename,
926 fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_WRITABLE,
927 )
928 .await
929 .expect("failed to create test file");
930 let _: u64 = test_file
931 .write(&content)
932 .await
933 .expect("failed to write to test file")
934 .map_err(Status::from_raw)
935 .expect("write error");
936 }
937
938 let fs_info2 = serving.query().await.expect("failed to query filesystem info after write");
940 assert_eq!(
941 fs_info2.used_bytes - fs_info1.used_bytes,
942 fs_info2.block_size as u64 );
944
945 serving.shutdown().await.expect("failed to shutdown minfs the first time");
946 let serving = minfs.serve().await.expect("failed to serve minfs the second time");
947
948 {
949 let test_file =
950 fuchsia_fs::directory::open_file(serving.root(), filename, fio::PERM_READABLE)
951 .await
952 .expect("failed to open test file");
953 let read_content =
954 fuchsia_fs::file::read(&test_file).await.expect("failed to read from test file");
955 assert_eq!(content, read_content);
956 }
957
958 let fs_info3 = serving.query().await.expect("failed to query filesystem info after read");
960 assert_eq!(
961 fs_info3.used_bytes - fs_info1.used_bytes,
962 fs_info3.block_size as u64 );
964
965 let _ = serving.shutdown().await.expect("failed to shutdown minfs the second time");
966
967 ramdisk.destroy().await.expect("failed to destroy ramdisk");
968 }
969
970 #[fuchsia::test]
971 async fn minfs_bind_to_path() {
972 let block_size = 8192;
973 let test_content = b"test content";
974 let mut ramdisk = ramdisk(block_size).await;
975 let mut minfs = new_fs(&mut ramdisk, Minfs::default()).await;
976
977 minfs.format().await.expect("failed to format minfs");
978 let mut serving = minfs.serve().await.expect("failed to serve minfs");
979 serving.bind_to_path("/test-minfs-path").expect("bind_to_path failed");
980 let test_path = "/test-minfs-path/test_file";
981
982 {
983 let mut file = std::fs::File::create(test_path).expect("failed to create test file");
984 file.write_all(test_content).expect("write bytes");
985 }
986
987 {
988 let mut file = std::fs::File::open(test_path).expect("failed to open test file");
989 let mut buf = Vec::new();
990 file.read_to_end(&mut buf).expect("failed to read test file");
991 assert_eq!(buf, test_content);
992 }
993
994 serving.shutdown().await.expect("failed to shutdown minfs");
995
996 std::fs::File::open(test_path).expect_err("test file was not unbound");
997 }
998
999 #[fuchsia::test]
1000 async fn minfs_take_exposed_dir_does_not_drop() {
1001 let block_size = 512;
1002 let test_content = b"test content";
1003 let test_file_name = "test-file";
1004 let mut ramdisk = ramdisk(block_size).await;
1005 let mut minfs = new_fs(&mut ramdisk, Minfs::default()).await;
1006
1007 minfs.format().await.expect("failed to format fxfs");
1008
1009 let fs = minfs.serve().await.expect("failed to serve fxfs");
1010 let file = {
1011 let file = fuchsia_fs::directory::open_file(
1012 fs.root(),
1013 test_file_name,
1014 fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_READABLE | fio::PERM_WRITABLE,
1015 )
1016 .await
1017 .unwrap();
1018 fuchsia_fs::file::write(&file, test_content).await.unwrap();
1019 file.close().await.expect("close fidl error").expect("close error");
1020 fuchsia_fs::directory::open_file(fs.root(), test_file_name, fio::PERM_READABLE)
1021 .await
1022 .unwrap()
1023 };
1024
1025 let exposed_dir = fs.take_exposed_dir();
1026
1027 assert_eq!(fuchsia_fs::file::read(&file).await.unwrap(), test_content);
1028
1029 connect_to_protocol_at_dir_root::<fidl_fuchsia_fs::AdminMarker>(&exposed_dir)
1030 .expect("connecting to admin marker")
1031 .shutdown()
1032 .await
1033 .expect("shutdown failed");
1034 }
1035
1036 #[fuchsia::test]
1037 async fn f2fs_format_fsck_success() {
1038 let block_size = 4096;
1039 let mut ramdisk = ramdisk(block_size).await;
1040 let mut f2fs = new_fs(&mut ramdisk, F2fs::default()).await;
1041
1042 f2fs.format().await.expect("failed to format f2fs");
1043 f2fs.fsck().await.expect("failed to fsck f2fs");
1044
1045 ramdisk.destroy().await.expect("failed to destroy ramdisk");
1046 }
1047
1048 #[fuchsia::test]
1049 async fn f2fs_format_serve_write_query_restart_read_shutdown() {
1050 let block_size = 4096;
1051 let mut ramdisk = ramdisk(block_size).await;
1052 let mut f2fs = new_fs(&mut ramdisk, F2fs::default()).await;
1053
1054 f2fs.format().await.expect("failed to format f2fs");
1055 let serving = f2fs.serve().await.expect("failed to serve f2fs the first time");
1056
1057 let fs_info1 =
1059 serving.query().await.expect("failed to query filesystem info after first serving");
1060
1061 let filename = "test_file";
1062 let content = String::from("test content").into_bytes();
1063
1064 {
1065 let test_file = fuchsia_fs::directory::open_file(
1066 serving.root(),
1067 filename,
1068 fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_WRITABLE,
1069 )
1070 .await
1071 .expect("failed to create test file");
1072 let _: u64 = test_file
1073 .write(&content)
1074 .await
1075 .expect("failed to write to test file")
1076 .map_err(Status::from_raw)
1077 .expect("write error");
1078 }
1079
1080 let fs_info2 = serving.query().await.expect("failed to query filesystem info after write");
1082 let expected_size2 = fs_info2.block_size * 2;
1087 assert_eq!(fs_info2.used_bytes - fs_info1.used_bytes, expected_size2 as u64);
1088
1089 serving.shutdown().await.expect("failed to shutdown f2fs the first time");
1090 let serving = f2fs.serve().await.expect("failed to serve f2fs the second time");
1091
1092 {
1093 let test_file =
1094 fuchsia_fs::directory::open_file(serving.root(), filename, fio::PERM_READABLE)
1095 .await
1096 .expect("failed to open test file");
1097 let read_content =
1098 fuchsia_fs::file::read(&test_file).await.expect("failed to read from test file");
1099 assert_eq!(content, read_content);
1100 }
1101
1102 let fs_info3 = serving.query().await.expect("failed to query filesystem info after read");
1104 let expected_size3 = fs_info3.block_size * 2;
1106 assert_eq!(fs_info3.used_bytes - fs_info1.used_bytes, expected_size3 as u64);
1107
1108 serving.shutdown().await.expect("failed to shutdown f2fs the second time");
1109 f2fs.fsck().await.expect("failed to fsck f2fs after shutting down the second time");
1110
1111 ramdisk.destroy().await.expect("failed to destroy ramdisk");
1112 }
1113
1114 #[fuchsia::test]
1115 async fn f2fs_bind_to_path() {
1116 let block_size = 4096;
1117 let test_content = b"test content";
1118 let mut ramdisk = ramdisk(block_size).await;
1119 let mut f2fs = new_fs(&mut ramdisk, F2fs::default()).await;
1120
1121 f2fs.format().await.expect("failed to format f2fs");
1122 let mut serving = f2fs.serve().await.expect("failed to serve f2fs");
1123 serving.bind_to_path("/test-f2fs-path").expect("bind_to_path failed");
1124 let test_path = "/test-f2fs-path/test_file";
1125
1126 {
1127 let mut file = std::fs::File::create(test_path).expect("failed to create test file");
1128 file.write_all(test_content).expect("write bytes");
1129 }
1130
1131 {
1132 let mut file = std::fs::File::open(test_path).expect("failed to open test file");
1133 let mut buf = Vec::new();
1134 file.read_to_end(&mut buf).expect("failed to read test file");
1135 assert_eq!(buf, test_content);
1136 }
1137
1138 serving.shutdown().await.expect("failed to shutdown f2fs");
1139
1140 std::fs::File::open(test_path).expect_err("test file was not unbound");
1141 }
1142
1143 #[ignore]
1146 #[fuchsia::test]
1147 async fn fxfs_shutdown_component_when_dropped() {
1148 let block_size = 512;
1149 let mut ramdisk = ramdisk(block_size).await;
1150 let mut fxfs = new_fs(&mut ramdisk, Fxfs::default()).await;
1151
1152 fxfs.format().await.expect("failed to format fxfs");
1153 {
1154 let _fs = fxfs.serve_multi_volume().await.expect("failed to serve fxfs");
1155
1156 assert!(
1158 fxfs.serve_multi_volume().await.is_err(),
1159 "serving succeeded when already mounted"
1160 );
1161 }
1162
1163 let mut attempts = 0;
1165 loop {
1166 if let Ok(_) = fxfs.serve_multi_volume().await {
1167 break;
1168 }
1169 attempts += 1;
1170 assert!(attempts < 10);
1171 fasync::Timer::new(Duration::from_secs(1)).await;
1172 }
1173 }
1174
1175 #[fuchsia::test]
1176 async fn fxfs_open_volume() {
1177 let block_size = 512;
1178 let mut ramdisk = ramdisk(block_size).await;
1179 let mut fxfs = new_fs(&mut ramdisk, Fxfs::default()).await;
1180
1181 fxfs.format().await.expect("failed to format fxfs");
1182
1183 let mut fs = fxfs.serve_multi_volume().await.expect("failed to serve fxfs");
1184
1185 assert_eq!(fs.has_volume("foo").await.expect("has_volume"), false);
1186 assert!(
1187 fs.open_volume("foo", MountOptions::default()).await.is_err(),
1188 "Opening nonexistent volume should fail"
1189 );
1190
1191 let vol = fs
1192 .create_volume("foo", CreateOptions::default(), MountOptions::default())
1193 .await
1194 .expect("Create volume failed");
1195 vol.query().await.expect("Query volume failed");
1196 assert_eq!(fs.has_volume("foo").await.expect("has_volume"), true);
1202 }
1203
1204 #[fuchsia::test]
1205 async fn fxfs_take_exposed_dir_does_not_drop() {
1206 let block_size = 512;
1207 let test_content = b"test content";
1208 let test_file_name = "test-file";
1209 let mut ramdisk = ramdisk(block_size).await;
1210 let mut fxfs = new_fs(&mut ramdisk, Fxfs::default()).await;
1211
1212 fxfs.format().await.expect("failed to format fxfs");
1213
1214 let fs = fxfs.serve_multi_volume().await.expect("failed to serve fxfs");
1215 let file = {
1216 let vol = fs
1217 .create_volume("foo", CreateOptions::default(), MountOptions::default())
1218 .await
1219 .expect("Create volume failed");
1220 let file = fuchsia_fs::directory::open_file(
1221 vol.root(),
1222 test_file_name,
1223 fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_READABLE | fio::PERM_WRITABLE,
1224 )
1225 .await
1226 .unwrap();
1227 fuchsia_fs::file::write(&file, test_content).await.unwrap();
1228 file.close().await.expect("close fidl error").expect("close error");
1229 fuchsia_fs::directory::open_file(vol.root(), test_file_name, fio::PERM_READABLE)
1230 .await
1231 .unwrap()
1232 };
1233
1234 let exposed_dir = fs.take_exposed_dir();
1235
1236 assert_eq!(fuchsia_fs::file::read(&file).await.unwrap(), test_content);
1237
1238 connect_to_protocol_at_dir_root::<fidl_fuchsia_fs::AdminMarker>(&exposed_dir)
1239 .expect("connecting to admin marker")
1240 .shutdown()
1241 .await
1242 .expect("shutdown failed");
1243 }
1244}