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