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_component_decl as fdecl;
13use fidl_fuchsia_fs::AdminMarker;
14use fidl_fuchsia_fs_startup::{
15 CheckOptions, CreateOptions, MountOptions, StartupMarker, VolumesMarker,
16};
17use fidl_fuchsia_io as fio;
18use fidl_fuchsia_storage_block::{self as fblock, BlockMarker};
19use fuchsia_component_client::{
20 connect_to_named_protocol_at_dir_root, connect_to_protocol, connect_to_protocol_at_dir_root,
21 connect_to_protocol_at_dir_svc, open_childs_exposed_directory,
22};
23use std::sync::Arc;
24use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
25use zx::Status;
26
27pub trait BlockConnector: Send + Sync {
39 fn connect_channel_to_block(&self, server_end: ServerEnd<BlockMarker>) -> Result<(), Error>;
40 fn connect_block(&self) -> Result<ClientEnd<BlockMarker>, Error> {
41 let (client, server) = fidl::endpoints::create_endpoints();
42 self.connect_channel_to_block(server)?;
43 Ok(client)
44 }
45}
46
47#[derive(Clone, Debug)]
49pub struct DirBasedBlockConnector(fio::DirectoryProxy, String);
50
51impl DirBasedBlockConnector {
52 pub fn new(dir: fio::DirectoryProxy, path: String) -> Self {
55 Self(dir, path)
56 }
57
58 pub fn dir(&self) -> &fio::DirectoryProxy {
59 &self.0
60 }
61
62 pub fn path(&self) -> &str {
64 &self.1
65 }
66}
67
68impl BlockConnector for DirBasedBlockConnector {
69 fn connect_channel_to_block(&self, server_end: ServerEnd<BlockMarker>) -> Result<(), Error> {
70 self.0.open(
71 self.path(),
72 fio::Flags::PROTOCOL_SERVICE,
73 &fio::Options::default(),
74 server_end.into_channel(),
75 )?;
76 Ok(())
77 }
78}
79
80impl BlockConnector for fidl_fuchsia_device::ControllerProxy {
81 fn connect_channel_to_block(&self, server_end: ServerEnd<BlockMarker>) -> Result<(), Error> {
82 let () = self.connect_to_device_fidl(server_end.into_channel())?;
83 Ok(())
84 }
85}
86
87impl BlockConnector for fidl_fuchsia_storage_partitions::PartitionServiceProxy {
88 fn connect_channel_to_block(&self, server_end: ServerEnd<BlockMarker>) -> Result<(), Error> {
89 self.connect_channel_to_volume(server_end)?;
90 Ok(())
91 }
92}
93
94impl<T: BlockConnector> BlockConnector for Arc<T> {
98 fn connect_channel_to_block(&self, server_end: ServerEnd<BlockMarker>) -> Result<(), Error> {
99 self.as_ref().connect_channel_to_block(server_end)
100 }
101}
102
103impl<F> BlockConnector for F
104where
105 F: Fn(ServerEnd<BlockMarker>) -> Result<(), Error> + Send + Sync,
106{
107 fn connect_channel_to_block(&self, server_end: ServerEnd<BlockMarker>) -> Result<(), Error> {
108 self(server_end)
109 }
110}
111
112pub struct Filesystem {
114 config: Box<dyn FSConfig>,
121 block_connector: Box<dyn BlockConnector>,
122 component: Option<Arc<DynamicComponentInstance>>,
123}
124
125static COLLECTION_COUNTER: AtomicU64 = AtomicU64::new(0);
127
128impl Filesystem {
129 pub fn config(&self) -> &dyn FSConfig {
130 self.config.as_ref()
131 }
132
133 pub fn into_config(self) -> Box<dyn FSConfig> {
134 self.config
135 }
136
137 pub fn new<B: BlockConnector + 'static, FSC: FSConfig>(
139 block_connector: B,
140 config: FSC,
141 ) -> Self {
142 Self::from_boxed_config(Box::new(block_connector), Box::new(config))
143 }
144
145 pub fn from_boxed_config(
147 block_connector: Box<dyn BlockConnector>,
148 config: Box<dyn FSConfig>,
149 ) -> Self {
150 Self { config, block_connector, component: None }
151 }
152
153 pub async fn get_component_moniker(&mut self) -> Result<String, Error> {
156 let _ = self.get_component_exposed_dir().await?;
157 Ok(match self.config.options().component_type {
158 ComponentType::StaticChild => self.config.options().component_name.to_string(),
159 ComponentType::DynamicChild { .. } => {
160 let component = self.component.as_ref().unwrap();
161 format!("{}:{}", component.collection, component.name)
162 }
163 })
164 }
165
166 async fn get_component_exposed_dir(&mut self) -> Result<fio::DirectoryProxy, Error> {
167 let options = self.config.options();
168 let component_name = options.component_name;
169 match options.component_type {
170 ComponentType::StaticChild => open_childs_exposed_directory(component_name, None).await,
171 ComponentType::DynamicChild { collection_name } => {
172 if let Some(component) = &self.component {
173 return open_childs_exposed_directory(
174 component.name.clone(),
175 Some(component.collection.clone()),
176 )
177 .await;
178 }
179
180 let name = format!(
184 "{}-{}-{}",
185 component_name,
186 fuchsia_runtime::process_self().koid().unwrap().raw_koid(),
187 COLLECTION_COUNTER.fetch_add(1, Ordering::Relaxed)
188 );
189
190 let collection_ref = fdecl::CollectionRef { name: collection_name };
191 let child_decls = vec![
192 fdecl::Child {
193 name: Some(format!("{}-relative", name)),
194 url: Some(format!("#meta/{}.cm", component_name)),
195 startup: Some(fdecl::StartupMode::Lazy),
196 ..Default::default()
197 },
198 fdecl::Child {
199 name: Some(name),
200 url: Some(format!(
201 "fuchsia-boot:///{}#meta/{}.cm",
202 component_name, component_name
203 )),
204 startup: Some(fdecl::StartupMode::Lazy),
205 ..Default::default()
206 },
207 ];
208 let realm_proxy = connect_to_protocol::<RealmMarker>()?;
209 for child_decl in child_decls {
210 realm_proxy
212 .create_child(
213 &collection_ref,
214 &child_decl,
215 fcomponent::CreateChildArgs::default(),
216 )
217 .await?
218 .map_err(|e| anyhow!("create_child failed: {:?}", e))?;
219
220 let component = Arc::new(DynamicComponentInstance {
221 name: child_decl.name.unwrap(),
222 collection: collection_ref.name.clone(),
223 should_not_drop: AtomicBool::new(false),
224 });
225
226 if let Ok(proxy) = open_childs_exposed_directory(
227 component.name.clone(),
228 Some(component.collection.clone()),
229 )
230 .await
231 {
232 self.component = Some(component);
233 return Ok(proxy);
234 }
235 }
236 Err(anyhow!("Failed to open exposed directory"))
237 }
238 }
239 }
240
241 pub async fn format(&mut self) -> Result<(), Error> {
254 let channel = self.block_connector.connect_block()?;
255
256 let exposed_dir = self.get_component_exposed_dir().await?;
257 let proxy = connect_to_protocol_at_dir_root::<StartupMarker>(&exposed_dir)?;
258 proxy
259 .format(channel, &self.config().options().format_options)
260 .await?
261 .map_err(Status::err_from_raw)?;
262
263 Ok(())
264 }
265
266 pub async fn fsck(&mut self) -> Result<(), Error> {
279 let channel = self.block_connector.connect_block()?;
280 let exposed_dir = self.get_component_exposed_dir().await?;
281 let proxy = connect_to_protocol_at_dir_root::<StartupMarker>(&exposed_dir)?;
282 proxy.check(channel, CheckOptions::default()).await?.map_err(Status::err_from_raw)?;
283 Ok(())
284 }
285
286 pub async fn serve(mut self) -> Result<ServingSingleVolumeFilesystem, Error> {
293 if self.config.is_multi_volume() {
294 bail!("Can't serve a multivolume filesystem; use serve_multi_volume");
295 }
296 let Options { start_options, reuse_component_after_serving, .. } = self.config.options();
297
298 let exposed_dir = self.get_component_exposed_dir().await?;
299 let proxy = connect_to_protocol_at_dir_root::<StartupMarker>(&exposed_dir)?;
300 proxy
301 .start(self.block_connector.connect_block()?, &start_options)
302 .await?
303 .map_err(Status::err_from_raw)?;
304
305 let (root_dir, server_end) = create_endpoints::<fio::NodeMarker>();
306 exposed_dir.open(
307 "root",
308 fio::PERM_READABLE | fio::Flags::PERM_INHERIT_WRITE | fio::Flags::PERM_INHERIT_EXECUTE,
309 &Default::default(),
310 server_end.into_channel(),
311 )?;
312 let component = self.component.clone();
313 if !reuse_component_after_serving {
314 self.component = None;
315 }
316 Ok(ServingSingleVolumeFilesystem {
317 component,
318 exposed_dir: Some(exposed_dir),
319 root_dir: ClientEnd::<fio::DirectoryMarker>::new(root_dir.into_channel()).into_proxy(),
320 binding: None,
321 })
322 }
323
324 pub async fn serve_multi_volume(mut self) -> Result<ServingMultiVolumeFilesystem, Error> {
332 if !self.config.is_multi_volume() {
333 bail!("Can't serve_multi_volume a single-volume filesystem; use serve");
334 }
335
336 let exposed_dir = self.get_component_exposed_dir().await?;
337 let proxy = connect_to_protocol_at_dir_root::<StartupMarker>(&exposed_dir)?;
338 proxy
339 .start(self.block_connector.connect_block()?, &self.config.options().start_options)
340 .await?
341 .map_err(Status::err_from_raw)?;
342
343 Ok(ServingMultiVolumeFilesystem {
344 component: self.component,
345 exposed_dir: Some(exposed_dir),
346 })
347 }
348}
349
350struct DynamicComponentInstance {
352 name: String,
353 collection: String,
354 should_not_drop: AtomicBool,
355}
356
357impl DynamicComponentInstance {
358 fn forget(&self) {
359 self.should_not_drop.store(true, Ordering::Relaxed);
360 }
361}
362
363impl Drop for DynamicComponentInstance {
364 fn drop(&mut self) {
365 if self.should_not_drop.load(Ordering::Relaxed) {
366 return;
367 }
368 if let Ok(realm_proxy) = connect_to_protocol::<RealmMarker>() {
369 let _ = realm_proxy.destroy_child(&fdecl::ChildRef {
370 name: self.name.clone(),
371 collection: Some(self.collection.clone()),
372 });
373 }
374 }
375}
376
377#[derive(Default)]
380pub struct NamespaceBinding(String);
381
382impl NamespaceBinding {
383 pub fn create(root_dir: &fio::DirectoryProxy, path: String) -> Result<NamespaceBinding, Error> {
384 let (client_end, server_end) = create_endpoints();
385 root_dir.clone(ServerEnd::new(server_end.into_channel()))?;
386 let namespace = fdio::Namespace::installed()?;
387 namespace.bind(&path, client_end)?;
388 Ok(Self(path))
389 }
390}
391
392impl std::ops::Deref for NamespaceBinding {
393 type Target = str;
394 fn deref(&self) -> &Self::Target {
395 &self.0
396 }
397}
398
399impl Drop for NamespaceBinding {
400 fn drop(&mut self) {
401 if let Ok(namespace) = fdio::Namespace::installed() {
402 let _ = namespace.unbind(&self.0);
403 }
404 }
405}
406
407pub type ServingFilesystem = ServingSingleVolumeFilesystem;
409
410pub struct ServingSingleVolumeFilesystem {
412 component: Option<Arc<DynamicComponentInstance>>,
413 exposed_dir: Option<fio::DirectoryProxy>,
415 root_dir: fio::DirectoryProxy,
416
417 binding: Option<NamespaceBinding>,
419}
420
421impl ServingSingleVolumeFilesystem {
422 pub fn exposed_dir(&self) -> &fio::DirectoryProxy {
424 self.exposed_dir.as_ref().unwrap()
425 }
426
427 pub fn root(&self) -> &fio::DirectoryProxy {
429 &self.root_dir
430 }
431
432 pub fn bind_to_path(&mut self, path: &str) -> Result<(), Error> {
440 ensure!(self.binding.is_none(), "Already bound");
441 self.binding = Some(NamespaceBinding::create(&self.root_dir, path.to_string())?);
442 Ok(())
443 }
444
445 pub fn bound_path(&self) -> Option<&str> {
446 self.binding.as_deref()
447 }
448
449 pub async fn query(&self) -> Result<Box<fio::FilesystemInfo>, QueryError> {
455 let (status, info) = self.root_dir.query_filesystem().await?;
456 Status::ok(status).map_err(QueryError::DirectoryQuery)?;
457 info.ok_or(QueryError::DirectoryEmptyResult)
458 }
459
460 pub fn take_exposed_dir(mut self) -> fio::DirectoryProxy {
464 self.component.take().expect("BUG: component missing").forget();
465 self.exposed_dir.take().expect("BUG: exposed dir missing")
466 }
467
468 pub async fn shutdown(mut self) -> Result<(), ShutdownError> {
476 connect_to_protocol_at_dir_root::<fidl_fuchsia_fs::AdminMarker>(
477 &self.exposed_dir.take().expect("BUG: exposed dir missing"),
478 )?
479 .shutdown()
480 .await?;
481 Ok(())
482 }
483
484 pub async fn kill(self) -> Result<(), Error> {
491 self.shutdown().await?;
495 Ok(())
496 }
497}
498
499impl Drop for ServingSingleVolumeFilesystem {
500 fn drop(&mut self) {
501 if let Some(exposed_dir) = self.exposed_dir.take() {
503 if let Ok(proxy) =
504 connect_to_protocol_at_dir_root::<fidl_fuchsia_fs::AdminMarker>(&exposed_dir)
505 {
506 let _ = proxy.shutdown();
507 }
508 }
509 }
510}
511
512pub struct ServingMultiVolumeFilesystem {
515 component: Option<Arc<DynamicComponentInstance>>,
516 exposed_dir: Option<fio::DirectoryProxy>,
518}
519
520pub struct ServingVolume {
522 root_dir: fio::DirectoryProxy,
523 binding: Option<NamespaceBinding>,
524 exposed_dir: fio::DirectoryProxy,
525}
526
527impl ServingVolume {
528 fn new(exposed_dir: fio::DirectoryProxy) -> Result<Self, Error> {
529 let (root_dir, server_end) = create_endpoints::<fio::NodeMarker>();
530 exposed_dir.open(
531 "root",
532 fio::PERM_READABLE | fio::Flags::PERM_INHERIT_WRITE | fio::Flags::PERM_INHERIT_EXECUTE,
533 &Default::default(),
534 server_end.into_channel(),
535 )?;
536 Ok(ServingVolume {
537 root_dir: ClientEnd::<fio::DirectoryMarker>::new(root_dir.into_channel()).into_proxy(),
538 binding: None,
539 exposed_dir,
540 })
541 }
542
543 pub fn root(&self) -> &fio::DirectoryProxy {
545 &self.root_dir
546 }
547
548 pub fn exposed_dir(&self) -> &fio::DirectoryProxy {
550 &self.exposed_dir
551 }
552
553 pub fn bind_to_path(&mut self, path: &str) -> Result<(), Error> {
561 ensure!(self.binding.is_none(), "Already bound");
562 self.binding = Some(NamespaceBinding::create(&self.root_dir, path.to_string())?);
563 Ok(())
564 }
565
566 pub fn unbind_path(&mut self) {
570 let _ = self.binding.take();
571 }
572
573 pub fn bound_path(&self) -> Option<&str> {
574 self.binding.as_deref()
575 }
576
577 pub async fn query(&self) -> Result<Box<fio::FilesystemInfo>, QueryError> {
583 let (status, info) = self.root_dir.query_filesystem().await?;
584 Status::ok(status).map_err(QueryError::DirectoryQuery)?;
585 info.ok_or(QueryError::DirectoryEmptyResult)
586 }
587
588 pub async fn shutdown(self) -> Result<(), Error> {
591 let admin_proxy = connect_to_protocol_at_dir_svc::<AdminMarker>(self.exposed_dir())?;
592 admin_proxy.shutdown().await.context("failed to shutdown volume")?;
593 Ok(())
594 }
595}
596
597impl ServingMultiVolumeFilesystem {
598 pub async fn has_volume(&self, volume: &str) -> Result<bool, Error> {
600 let path = format!("volumes/{}", volume);
601 fuchsia_fs::directory::open_node(self.exposed_dir(), &path, fio::Flags::PROTOCOL_NODE)
602 .await
603 .map(|_| true)
604 .or_else(|e| {
605 if let fuchsia_fs::node::OpenError::OpenError(status) = &e {
606 if *status == zx::Status::NOT_FOUND {
607 return Ok(false);
608 }
609 }
610 Err(e.into())
611 })
612 }
613
614 pub async fn create_volume(
618 &self,
619 volume: &str,
620 create_options: CreateOptions,
621 options: MountOptions,
622 ) -> Result<ServingVolume, Error> {
623 let (exposed_dir, server) = create_proxy::<fio::DirectoryMarker>();
624 connect_to_protocol_at_dir_root::<VolumesMarker>(self.exposed_dir())?
625 .create(volume, server, create_options, options)
626 .await?
627 .map_err(|e| anyhow!(zx::Status::err_from_raw(e)))?;
628 ServingVolume::new(exposed_dir)
629 }
630
631 pub async fn remove_volume(&self, volume: &str) -> Result<(), Error> {
633 connect_to_protocol_at_dir_root::<VolumesMarker>(self.exposed_dir())?
634 .remove(volume)
635 .await?
636 .map_err(|e| anyhow!(zx::Status::err_from_raw(e)))
637 }
638
639 pub async fn open_volume(
642 &self,
643 volume: &str,
644 options: MountOptions,
645 ) -> Result<ServingVolume, Error> {
646 let (exposed_dir, server) = create_proxy::<fio::DirectoryMarker>();
647 let path = format!("volumes/{}", volume);
648 connect_to_named_protocol_at_dir_root::<fidl_fuchsia_fs_startup::VolumeMarker>(
649 self.exposed_dir(),
650 &path,
651 )?
652 .mount(server, options)
653 .await?
654 .map_err(|e| anyhow!(zx::Status::err_from_raw(e)))?;
655
656 ServingVolume::new(exposed_dir)
657 }
658
659 pub async fn get_volume_info(
661 &self,
662 volume: &str,
663 ) -> Result<fidl_fuchsia_fs_startup::VolumeInfo, Error> {
664 let path = format!("volumes/{}", volume);
665 connect_to_named_protocol_at_dir_root::<fidl_fuchsia_fs_startup::VolumeMarker>(
666 self.exposed_dir(),
667 &path,
668 )?
669 .get_info()
670 .await?
671 .map_err(|e| anyhow!(zx::Status::err_from_raw(e)))
672 }
673
674 pub async fn set_byte_limit(&self, volume: &str, byte_limit: u64) -> Result<(), Error> {
676 if byte_limit == 0 {
677 return Ok(());
678 }
679 let path = format!("volumes/{}", volume);
680 connect_to_named_protocol_at_dir_root::<fidl_fuchsia_fs_startup::VolumeMarker>(
681 self.exposed_dir(),
682 &path,
683 )?
684 .set_limit(byte_limit)
685 .await?
686 .map_err(|e| anyhow!(zx::Status::err_from_raw(e)))
687 }
688
689 pub async fn check_volume(&self, volume: &str, options: CheckOptions) -> Result<(), Error> {
690 let path = format!("volumes/{}", volume);
691 connect_to_named_protocol_at_dir_root::<fidl_fuchsia_fs_startup::VolumeMarker>(
692 self.exposed_dir(),
693 &path,
694 )?
695 .check(options)
696 .await?
697 .map_err(|e| anyhow!(zx::Status::err_from_raw(e)))?;
698 Ok(())
699 }
700
701 pub fn exposed_dir(&self) -> &fio::DirectoryProxy {
704 self.exposed_dir.as_ref().expect("BUG: exposed dir missing")
705 }
706
707 pub async fn shutdown(mut self) -> Result<(), ShutdownError> {
714 connect_to_protocol_at_dir_root::<fidl_fuchsia_fs::AdminMarker>(
715 &self.exposed_dir.take().expect("BUG: exposed dir missing"),
717 )?
718 .shutdown()
719 .await?;
720 Ok(())
721 }
722
723 pub fn take_exposed_dir(mut self) -> fio::DirectoryProxy {
727 self.component.take().expect("BUG: missing component").forget();
728 self.exposed_dir.take().expect("BUG: exposed dir missing")
729 }
730
731 pub async fn list_volumes(&self) -> Result<Vec<String>, Error> {
733 let volumes_dir = fuchsia_fs::directory::open_async::<fio::DirectoryMarker>(
734 self.exposed_dir(),
735 "volumes",
736 fio::PERM_READABLE,
737 )
738 .unwrap();
739 fuchsia_fs::directory::readdir(&volumes_dir)
740 .await
741 .map(|entries| entries.into_iter().map(|e| e.name).collect())
742 .map_err(|e| anyhow!("failed to read volumes dir: {}", e))
743 }
744
745 pub async fn get_info(&self) -> Result<fblock::VolumeManagerInfo, Error> {
747 Ok(*connect_to_protocol_at_dir_root::<VolumesMarker>(self.exposed_dir())?
748 .get_info()
749 .await?
750 .map_err(|e| anyhow!(zx::Status::err_from_raw(e)))?
751 .ok_or_else(|| anyhow!("Missing info"))?)
752 }
753}
754
755impl Drop for ServingMultiVolumeFilesystem {
756 fn drop(&mut self) {
757 if let Some(exposed_dir) = self.exposed_dir.take() {
758 if let Ok(proxy) =
760 connect_to_protocol_at_dir_root::<fidl_fuchsia_fs::AdminMarker>(&exposed_dir)
761 {
762 let _ = proxy.shutdown();
763 }
764 }
765 }
766}
767
768#[cfg(test)]
769mod tests {
770 use super::*;
771 use crate::{Blobfs, F2fs, Fxfs, Minfs};
772 use delivery_blob::{CompressionMode, Type1Blob};
773 use fidl::endpoints::DiscoverableProtocolMarker;
774 use fidl_fuchsia_fxfs::{BlobCreatorMarker, BlobReaderMarker};
775 use ramdevice_client::RamdiskClient;
776 use std::io::{Read as _, Write as _};
777
778 async fn ramdisk(block_size: u64) -> RamdiskClient {
779 RamdiskClient::create(block_size, 1 << 16).await.unwrap()
780 }
781
782 async fn new_fs<FSC: FSConfig>(ramdisk: &RamdiskClient, config: FSC) -> Filesystem {
783 let block_dir = fuchsia_fs::directory::clone(ramdisk.outgoing()).unwrap();
784 let block_connector =
785 DirBasedBlockConnector::new(block_dir, format!("svc/{}", BlockMarker::PROTOCOL_NAME));
786 Filesystem::new(block_connector, config)
787 }
788
789 #[fuchsia::test]
790 async fn blobfs_custom_config() {
791 let block_size = 512;
792 let ramdisk = ramdisk(block_size).await;
793 let config = Blobfs { verbose: true, readonly: true, ..Default::default() };
794 let mut blobfs = new_fs(&ramdisk, config).await;
795
796 blobfs.format().await.expect("failed to format blobfs");
797 blobfs.fsck().await.expect("failed to fsck blobfs");
798 let serving = blobfs.serve().await.expect("failed to serve blobfs");
799 serving.shutdown().await.expect("failed to shutdown blobfs");
800 }
801
802 #[fuchsia::test]
803 async fn blobfs_format_fsck_success() {
804 let block_size = 512;
805 let ramdisk = ramdisk(block_size).await;
806 let mut blobfs = new_fs(&ramdisk, Blobfs::default()).await;
807
808 blobfs.format().await.expect("failed to format blobfs");
809 blobfs.fsck().await.expect("failed to fsck blobfs");
810 }
811
812 #[fuchsia::test]
813 async fn blobfs_format_serve_write_query_restart_read_shutdown() {
814 let block_size = 512;
815 let ramdisk = ramdisk(block_size).await;
816 let mut blobfs = new_fs(&ramdisk, Blobfs::default()).await;
817
818 blobfs.format().await.expect("failed to format blobfs");
819
820 let serving = blobfs.serve().await.expect("failed to serve blobfs the first time");
821
822 let fs_info1 =
824 serving.query().await.expect("failed to query filesystem info after first serving");
825
826 let content = b"test content";
828 let merkle = fuchsia_merkle::root_from_slice(content);
829 let delivery_blob = Type1Blob::generate(content, CompressionMode::Never);
830
831 {
832 let creator = fuchsia_component_client::connect_to_protocol_at_dir_root::<
833 BlobCreatorMarker,
834 >(serving.exposed_dir())
835 .unwrap();
836 let writer = creator.create(&merkle.into(), false).await.unwrap().unwrap();
837 let mut writer =
838 blob_writer::BlobWriter::create(writer.into_proxy(), delivery_blob.len() as u64)
839 .await
840 .unwrap();
841 writer.write(&delivery_blob).await.unwrap();
842 }
843
844 let fs_info2 = serving.query().await.expect("failed to query filesystem info after write");
846 assert_eq!(
847 fs_info2.used_bytes - fs_info1.used_bytes,
848 fs_info2.block_size as u64 );
850
851 serving.shutdown().await.expect("failed to shutdown blobfs the first time");
852 let blobfs = new_fs(&ramdisk, Blobfs::default()).await;
853 let serving = blobfs.serve().await.expect("failed to serve blobfs the second time");
854 {
855 let reader = fuchsia_component_client::connect_to_protocol_at_dir_root::<
856 BlobReaderMarker,
857 >(serving.exposed_dir())
858 .unwrap();
859 let vmo = reader.get_vmo(&merkle.into()).await.unwrap().unwrap();
860 let read_content = vmo.read_to_vec::<u8>(0, content.len() as u64).unwrap();
861 assert_eq!(read_content, content);
862 }
863
864 let fs_info3 = serving.query().await.expect("failed to query filesystem info after read");
866 assert_eq!(
867 fs_info3.used_bytes - fs_info1.used_bytes,
868 fs_info3.block_size as u64 );
870
871 serving.shutdown().await.expect("failed to shutdown blobfs the second time");
872 }
873
874 #[fuchsia::test]
875 async fn blobfs_bind_to_path() {
876 let block_size = 512;
877 let test_content = b"test content";
878 let merkle = fuchsia_merkle::root_from_slice(test_content);
879 let delivery_blob = Type1Blob::generate(test_content, CompressionMode::Never);
880 let ramdisk = ramdisk(block_size).await;
881 let mut blobfs = new_fs(&ramdisk, Blobfs::default()).await;
882
883 blobfs.format().await.expect("failed to format blobfs");
884 let mut serving = blobfs.serve().await.expect("failed to serve blobfs");
885 serving.bind_to_path("/test-blobfs-path").expect("bind_to_path failed");
886
887 {
888 let creator = fuchsia_component_client::connect_to_protocol_at_dir_root::<
889 BlobCreatorMarker,
890 >(serving.exposed_dir())
891 .unwrap();
892 let writer = creator.create(&merkle.into(), false).await.unwrap().unwrap();
893 let mut writer =
894 blob_writer::BlobWriter::create(writer.into_proxy(), delivery_blob.len() as u64)
895 .await
896 .unwrap();
897 writer.write(&delivery_blob).await.unwrap();
898 }
899
900 let entries = std::fs::read_dir("/test-blobfs-path")
901 .unwrap()
902 .map(|entry| entry.unwrap().file_name().into_string().unwrap())
903 .collect::<Vec<_>>();
904 assert_eq!(entries, &[merkle.to_string()]);
905
906 serving.shutdown().await.expect("failed to shutdown blobfs");
907 }
908
909 #[fuchsia::test]
910 async fn minfs_custom_config() {
911 let block_size = 512;
912 let ramdisk = ramdisk(block_size).await;
913 let config = Minfs {
914 verbose: true,
915 readonly: true,
916 fsck_after_every_transaction: true,
917 ..Default::default()
918 };
919 let mut minfs = new_fs(&ramdisk, config).await;
920
921 minfs.format().await.expect("failed to format minfs");
922 minfs.fsck().await.expect("failed to fsck minfs");
923 let serving = minfs.serve().await.expect("failed to serve minfs");
924 serving.shutdown().await.expect("failed to shutdown minfs");
925 }
926
927 #[fuchsia::test]
928 async fn minfs_format_fsck_success() {
929 let block_size = 8192;
930 let ramdisk = ramdisk(block_size).await;
931 let mut minfs = new_fs(&ramdisk, Minfs::default()).await;
932
933 minfs.format().await.expect("failed to format minfs");
934 minfs.fsck().await.expect("failed to fsck minfs");
935 }
936
937 #[fuchsia::test]
938 async fn minfs_format_serve_write_query_restart_read_shutdown() {
939 let block_size = 8192;
940 let ramdisk = ramdisk(block_size).await;
941 let mut minfs = new_fs(&ramdisk, Minfs::default()).await;
942
943 minfs.format().await.expect("failed to format minfs");
944 let serving = minfs.serve().await.expect("failed to serve minfs the first time");
945
946 let fs_info1 =
948 serving.query().await.expect("failed to query filesystem info after first serving");
949
950 let filename = "test_file";
951 let content = String::from("test content").into_bytes();
952
953 {
954 let test_file = fuchsia_fs::directory::open_file(
955 serving.root(),
956 filename,
957 fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_WRITABLE,
958 )
959 .await
960 .expect("failed to create test file");
961 let _: u64 = test_file
962 .write(&content)
963 .await
964 .expect("failed to write to test file")
965 .map_err(Status::err_from_raw)
966 .expect("write error");
967 }
968
969 let fs_info2 = serving.query().await.expect("failed to query filesystem info after write");
971 assert_eq!(
972 fs_info2.used_bytes - fs_info1.used_bytes,
973 fs_info2.block_size as u64 );
975
976 serving.shutdown().await.expect("failed to shutdown minfs the first time");
977 let minfs = new_fs(&ramdisk, Minfs::default()).await;
978 let serving = minfs.serve().await.expect("failed to serve minfs the second time");
979
980 {
981 let test_file =
982 fuchsia_fs::directory::open_file(serving.root(), filename, fio::PERM_READABLE)
983 .await
984 .expect("failed to open test file");
985 let read_content =
986 fuchsia_fs::file::read(&test_file).await.expect("failed to read from test file");
987 assert_eq!(content, read_content);
988 }
989
990 let fs_info3 = serving.query().await.expect("failed to query filesystem info after read");
992 assert_eq!(
993 fs_info3.used_bytes - fs_info1.used_bytes,
994 fs_info3.block_size as u64 );
996
997 let _ = serving.shutdown().await.expect("failed to shutdown minfs the second time");
998 }
999
1000 #[fuchsia::test]
1001 async fn minfs_bind_to_path() {
1002 let block_size = 8192;
1003 let test_content = b"test content";
1004 let ramdisk = ramdisk(block_size).await;
1005 let mut minfs = new_fs(&ramdisk, Minfs::default()).await;
1006
1007 minfs.format().await.expect("failed to format minfs");
1008 let mut serving = minfs.serve().await.expect("failed to serve minfs");
1009 serving.bind_to_path("/test-minfs-path").expect("bind_to_path failed");
1010 let test_path = "/test-minfs-path/test_file";
1011
1012 {
1013 let mut file = std::fs::File::create(test_path).expect("failed to create test file");
1014 file.write_all(test_content).expect("write bytes");
1015 }
1016
1017 {
1018 let mut file = std::fs::File::open(test_path).expect("failed to open test file");
1019 let mut buf = Vec::new();
1020 file.read_to_end(&mut buf).expect("failed to read test file");
1021 assert_eq!(buf, test_content);
1022 }
1023
1024 serving.shutdown().await.expect("failed to shutdown minfs");
1025
1026 std::fs::File::open(test_path).expect_err("test file was not unbound");
1027 }
1028
1029 #[fuchsia::test]
1030 async fn minfs_take_exposed_dir_does_not_drop() {
1031 let block_size = 512;
1032 let test_content = b"test content";
1033 let test_file_name = "test-file";
1034 let ramdisk = ramdisk(block_size).await;
1035 let mut minfs = new_fs(&ramdisk, Minfs::default()).await;
1036
1037 minfs.format().await.expect("failed to format fxfs");
1038
1039 let fs = minfs.serve().await.expect("failed to serve fxfs");
1040 let file = {
1041 let file = fuchsia_fs::directory::open_file(
1042 fs.root(),
1043 test_file_name,
1044 fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_READABLE | fio::PERM_WRITABLE,
1045 )
1046 .await
1047 .unwrap();
1048 fuchsia_fs::file::write(&file, test_content).await.unwrap();
1049 file.close().await.expect("close fidl error").expect("close error");
1050 fuchsia_fs::directory::open_file(fs.root(), test_file_name, fio::PERM_READABLE)
1051 .await
1052 .unwrap()
1053 };
1054
1055 let exposed_dir = fs.take_exposed_dir();
1056
1057 assert_eq!(fuchsia_fs::file::read(&file).await.unwrap(), test_content);
1058
1059 connect_to_protocol_at_dir_root::<fidl_fuchsia_fs::AdminMarker>(&exposed_dir)
1060 .expect("connecting to admin marker")
1061 .shutdown()
1062 .await
1063 .expect("shutdown failed");
1064 }
1065
1066 #[fuchsia::test]
1067 async fn f2fs_format_fsck_success() {
1068 let block_size = 4096;
1069 let ramdisk = ramdisk(block_size).await;
1070 let mut f2fs = new_fs(&ramdisk, F2fs::default()).await;
1071
1072 f2fs.format().await.expect("failed to format f2fs");
1073 f2fs.fsck().await.expect("failed to fsck f2fs");
1074 }
1075
1076 #[fuchsia::test]
1077 async fn f2fs_format_serve_write_query_restart_read_shutdown() {
1078 let block_size = 4096;
1079 let ramdisk = ramdisk(block_size).await;
1080 let mut f2fs = new_fs(&ramdisk, F2fs::default()).await;
1081
1082 f2fs.format().await.expect("failed to format f2fs");
1083 let serving = f2fs.serve().await.expect("failed to serve f2fs the first time");
1084
1085 let fs_info1 =
1087 serving.query().await.expect("failed to query filesystem info after first serving");
1088
1089 let filename = "test_file";
1090 let content = String::from("test content").into_bytes();
1091
1092 {
1093 let test_file = fuchsia_fs::directory::open_file(
1094 serving.root(),
1095 filename,
1096 fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_WRITABLE,
1097 )
1098 .await
1099 .expect("failed to create test file");
1100 let _: u64 = test_file
1101 .write(&content)
1102 .await
1103 .expect("failed to write to test file")
1104 .map_err(Status::err_from_raw)
1105 .expect("write error");
1106 }
1107
1108 let fs_info2 = serving.query().await.expect("failed to query filesystem info after write");
1110 let expected_size2 = fs_info2.block_size * 2;
1115 assert_eq!(fs_info2.used_bytes - fs_info1.used_bytes, expected_size2 as u64);
1116
1117 serving.shutdown().await.expect("failed to shutdown f2fs the first time");
1118 let f2fs = new_fs(&ramdisk, F2fs::default()).await;
1119 let serving = f2fs.serve().await.expect("failed to serve f2fs the second time");
1120
1121 {
1122 let test_file =
1123 fuchsia_fs::directory::open_file(serving.root(), filename, fio::PERM_READABLE)
1124 .await
1125 .expect("failed to open test file");
1126 let read_content =
1127 fuchsia_fs::file::read(&test_file).await.expect("failed to read from test file");
1128 assert_eq!(content, read_content);
1129 }
1130
1131 let fs_info3 = serving.query().await.expect("failed to query filesystem info after read");
1133 let expected_size3 = fs_info3.block_size * 2;
1135 assert_eq!(fs_info3.used_bytes - fs_info1.used_bytes, expected_size3 as u64);
1136
1137 serving.shutdown().await.expect("failed to shutdown f2fs the second time");
1138 let mut f2fs = new_fs(&ramdisk, F2fs::default()).await;
1139 f2fs.fsck().await.expect("failed to fsck f2fs after shutting down the second time");
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}