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