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