1use crate::fuchsia::component::map_to_raw_status;
9use crate::fuchsia::directory::FxDirectory;
10use crate::fuchsia::dirent_cache::DirentCacheKey;
11use crate::fuchsia::fxblob::blob::FxBlob;
12use crate::fuchsia::fxblob::mapping_provider::BlobMappingProvider;
13use crate::fuchsia::fxblob::writer::DeliveryBlobWriter;
14use crate::fuchsia::node::{FxNode, GetResult, OpenedNode};
15use crate::fuchsia::volume::{FxVolume, RootDir};
16use anyhow::{Context as _, Error, anyhow, ensure};
17use fidl::endpoints::{ClientEnd, DiscoverableProtocolMarker, ServerEnd, create_request_stream};
18use fidl_fuchsia_fxfs::{
19 BlobCreatorMarker, BlobCreatorRequest, BlobCreatorRequestStream, BlobReaderMarker,
20 BlobReaderRequest, BlobReaderRequestStream, BlobWriterMarker, CreateBlobError,
21};
22use fidl_fuchsia_io::{self as fio, FilesystemInfo, NodeMarker, WatchMask};
23use fidl_fuchsia_storage_block as fblock;
24use fidl_fuchsia_storage_mapping::MappingProviderMarker;
25use fuchsia_hash::Hash;
26use futures::TryStreamExt;
27use fxfs::errors::FxfsError;
28use fxfs::object_store::transaction::{LockKey, lock_keys};
29use fxfs::object_store::{self, HandleOptions, ObjectDescriptor, ObjectStore, StoreObjectHandle};
30use fxfs_macros::ToWeakNode;
31use fxfs_trace::{TraceFutureExt, trace_future_args};
32use std::str::FromStr;
33use std::sync::Arc;
34use vfs::directory::dirents_sink;
35use vfs::directory::entry::{DirectoryEntry, EntryInfo, GetEntryInfo, OpenRequest};
36use vfs::directory::entry_container::{
37 Directory as VfsDirectory, DirectoryWatcher, MutableDirectory,
38};
39use vfs::directory::helper::DirectlyMutable;
40use vfs::directory::mutable::connection::MutableConnection;
41use vfs::directory::simple::Simple;
42use vfs::directory::traversal_position::TraversalPosition;
43use vfs::execution_scope::ExecutionScope;
44use vfs::path::Path;
45use vfs::{ObjectRequestRef, ProtocolsExt, ToObjectRequest};
46use zx::Status;
47
48#[derive(ToWeakNode)]
53pub struct BlobDirectory {
54 directory: Arc<FxDirectory>,
55}
56
57pub(crate) struct Identifier {
60 pub string: String,
61 pub hash: Hash,
62}
63
64impl TryFrom<&str> for Identifier {
65 type Error = FxfsError;
66 fn try_from(value: &str) -> Result<Self, Self::Error> {
67 Ok(Self {
68 string: value.to_owned(),
69 hash: Hash::from_str(value).map_err(|_| FxfsError::InvalidArgs)?,
70 })
71 }
72}
73
74impl From<Hash> for Identifier {
75 fn from(hash: Hash) -> Self {
76 Self { string: hash.to_string(), hash }
77 }
78}
79
80impl RootDir for BlobDirectory {
81 fn as_directory_entry(self: Arc<Self>) -> Arc<dyn DirectoryEntry> {
82 self
83 }
84
85 fn serve(self: Arc<Self>, flags: fio::Flags, server_end: ServerEnd<fio::DirectoryMarker>) {
86 let scope = self.volume().scope().clone();
87 vfs::directory::serve_on(self, flags, scope, server_end);
88 }
89
90 fn as_node(self: Arc<Self>) -> Arc<dyn FxNode> {
91 self as Arc<dyn FxNode>
92 }
93
94 fn register_additional_volume_services(self: Arc<Self>, svc_dir: &Simple) -> Result<(), Error> {
95 let this = self.clone();
96 svc_dir.add_entry(
97 BlobCreatorMarker::PROTOCOL_NAME,
98 vfs::service::host(move |r| this.clone().handle_blob_creator_requests(r)),
99 )?;
100
101 let mapping_provider = Arc::new(BlobMappingProvider::new(self.clone())?);
102 svc_dir.add_entry(
103 MappingProviderMarker::PROTOCOL_NAME,
104 vfs::service::host(move |r| {
105 mapping_provider.clone().handle_mapping_provider_requests(r)
106 }),
107 )?;
108
109 let this = self.clone();
110 svc_dir.add_entry(
111 BlobReaderMarker::PROTOCOL_NAME,
112 vfs::service::host(move |r| this.clone().handle_blob_reader_requests(r)),
113 )?;
114
115 svc_dir.add_entry(
116 fblock::MapperMarker::PROTOCOL_NAME,
117 vfs::service::endpoint(move |scope, channel| {
118 let device = self.store().device().clone();
119 scope.spawn(async move {
120 if let Err(status) =
121 device.connect_mapper(channel.into_zx_channel().into()).await
122 {
123 log::warn!(status:?; "Failed to connect to Mapper");
124 }
125 });
126 }),
127 )?;
128
129 Ok(())
130 }
131}
132
133impl BlobDirectory {
134 fn new(directory: FxDirectory) -> Self {
135 Self { directory: Arc::new(directory) }
136 }
137
138 pub fn directory(&self) -> &Arc<FxDirectory> {
139 &self.directory
140 }
141
142 pub fn volume(&self) -> &Arc<FxVolume> {
143 self.directory.volume()
144 }
145
146 fn store(&self) -> &ObjectStore {
147 self.directory.store()
148 }
149
150 pub(crate) async fn open_blob_get_vmo(
153 self: &Arc<Self>,
154 id: &Identifier,
155 ) -> Result<(Arc<FxBlob>, zx::Vmo), Error> {
156 let store = self.store();
157 let fs = store.filesystem();
158 let keys = lock_keys![LockKey::object(store.store_object_id(), self.directory.object_id())];
159 let _guard = fs.lock_manager().read_lock(keys.clone()).await;
161 let blob = self.open_blob_locked(id).await?.ok_or(FxfsError::NotFound)?;
162 let vmo = blob.create_child_vmo()?;
163 Ok((blob.clone(), vmo))
165 }
166
167 pub(crate) async fn open_blob(
169 self: &Arc<Self>,
170 id: &Identifier,
171 ) -> Result<Option<OpenedNode<FxBlob>>, Error> {
172 let store = self.store();
173 let fs = store.filesystem();
174 let keys = lock_keys![LockKey::object(store.store_object_id(), self.directory.object_id())];
175 let _guard = fs.lock_manager().read_lock(keys.clone()).await;
177 self.open_blob_locked(id).await
178 }
179
180 async fn open_blob_locked(
184 self: &Arc<Self>,
185 id: &Identifier,
186 ) -> Result<Option<OpenedNode<FxBlob>>, Error> {
187 let node = match self.directory.directory().owner().dirent_cache().lookup(&(
188 self.directory.object_id(),
189 &id.string,
190 false,
191 )) {
192 Some(node) => Some(node),
193 None => {
194 if let Some((object_id, _, _)) =
195 self.directory.directory().lookup(&id.string).await?
196 {
197 let node = self.get_or_load_node(object_id, &id).await?;
198 self.directory.directory().owner().dirent_cache().insert(
199 DirentCacheKey::new(self.directory.object_id(), id.string.clone(), false),
200 node.clone(),
201 );
202 Some(node)
203 } else {
204 None
205 }
206 }
207 };
208 let Some(node) = node else {
209 return Ok(None);
210 };
211 if node.object_descriptor() != ObjectDescriptor::File {
212 return Err(FxfsError::Inconsistent)
213 .with_context(|| format!("Blob {} has invalid object descriptor!", id.string));
214 }
215 node.into_any()
216 .downcast::<FxBlob>()
217 .map(|node| Some(OpenedNode::new(node)))
218 .map_err(|_| FxfsError::Inconsistent)
219 .with_context(|| format!("Blob {} has incorrect node type!", id.string))
220 }
221
222 async fn get_or_load_node(
226 self: &Arc<Self>,
227 object_id: u64,
228 id: &Identifier,
229 ) -> Result<Arc<dyn FxNode>, Error> {
230 let volume = self.volume();
231 match volume.cache().get_or_reserve(object_id).await {
232 GetResult::Node(node) => {
233 let blob = node.into_any().downcast::<FxBlob>().map_err(|_| {
237 anyhow!(FxfsError::Inconsistent).context("Loaded non-blob from cache")
238 })?;
239 ensure!(
240 blob.root() == id.hash,
241 anyhow!(FxfsError::Inconsistent)
242 .context("Loaded blob by node that did not match the given hash")
243 );
244 Ok(blob as Arc<dyn FxNode>)
245 }
246 GetResult::Placeholder(placeholder) => {
247 let handle = StoreObjectHandle::new(
248 volume.clone(),
249 object_id,
250 false,
251 HandleOptions::default(),
252 false,
253 );
254 let node = FxBlob::new(handle, id.hash).await? as Arc<dyn FxNode>;
255 placeholder.commit(&node);
256 Ok(node)
257 }
258 }
259 }
260
261 async fn create_blob_writer(
265 self: &Arc<Self>,
266 hash: Hash,
267 allow_existing: bool,
268 ) -> Result<ClientEnd<BlobWriterMarker>, CreateBlobError> {
269 let id = hash.into();
270 let blob_exists = self
271 .open_blob(&id)
272 .await
273 .map_err(|e| {
274 log::error!("Failed to lookup blob: {:?}", e);
275 CreateBlobError::Internal
276 })?
277 .is_some();
278 if blob_exists && !allow_existing {
279 return Err(CreateBlobError::AlreadyExists);
280 }
281 let (client_end, request_stream) = create_request_stream::<BlobWriterMarker>();
282 let writer = DeliveryBlobWriter::new(self, hash).await.map_err(|e| {
283 log::error!("Failed to create blob writer: {:?}", e);
284 CreateBlobError::Internal
285 })?;
286 self.volume().scope().spawn(async move {
287 if let Err(e) = writer.handle_requests(request_stream).await {
288 log::error!("Failed to handle BlobWriter requests: {}", e);
289 }
290 });
291 return Ok(client_end);
292 }
293
294 async fn needs_overwrite(&self, blob_hash: Identifier) -> Result<bool, Error> {
295 if self
299 .volume()
300 .dirent_cache()
301 .lookup(&(self.object_id(), &blob_hash.string, false))
302 .is_some()
303 {
304 return Ok(false);
305 }
306 match self.directory.directory().lookup(&blob_hash.string).await? {
307 Some(_) => Ok(false),
308 None => Err(FxfsError::NotFound.into()),
309 }
310 }
311
312 async fn handle_blob_creator_requests(self: Arc<Self>, mut requests: BlobCreatorRequestStream) {
313 while let Ok(Some(request)) = requests.try_next().await {
314 match request {
315 BlobCreatorRequest::Create { responder, hash, allow_existing } => {
316 async {
317 responder
318 .send(self.create_blob_writer(Hash::from(hash), allow_existing).await)
319 .unwrap_or_else(|error| {
320 log::error!(error:?; "failed to send Create response");
321 });
322 }
323 .trace(trace_future_args!("BlobCreator::Create"))
324 .await;
325 }
326 BlobCreatorRequest::NeedsOverwrite { blob_hash, responder } => {
327 async {
328 let _ = responder.send(
329 self.needs_overwrite(Hash::from(blob_hash).into())
330 .await
331 .map_err(map_to_raw_status),
332 );
333 }
334 .trace(trace_future_args!("BlobCreator::NeedsOverwrite"))
335 .await;
336 }
337 }
338 }
339 }
340
341 async fn handle_blob_reader_requests(self: Arc<Self>, mut requests: BlobReaderRequestStream) {
342 while let Ok(Some(request)) = requests.try_next().await {
343 match request {
344 BlobReaderRequest::GetVmo { blob_hash, responder } => {
345 async {
346 responder
347 .send(
348 self.get_blob_vmo(blob_hash.into())
349 .await
350 .map_err(map_to_raw_status),
351 )
352 .unwrap_or_else(|error| {
353 log::error!(error:?; "failed to send GetVmo response");
354 });
355 }
356 .trace(trace_future_args!("BlobReader::GetVmo"))
357 .await;
358 }
359 };
360 }
361 }
362
363 async fn open_impl(
364 self: Arc<Self>,
365 scope: ExecutionScope,
366 path: Path,
367 flags: impl ProtocolsExt,
368 object_request: ObjectRequestRef<'_>,
369 ) -> Result<(), Status> {
370 if path.is_empty() {
371 object_request
372 .create_connection::<MutableConnection<_>, _>(
373 scope,
374 OpenedNode::new(self).take(),
375 flags,
376 )
377 .await
378 } else {
379 Err(Status::NOT_SUPPORTED)
380 }
381 }
382}
383
384impl FxNode for BlobDirectory {
385 fn object_id(&self) -> u64 {
386 self.directory.object_id()
387 }
388
389 fn parent(&self) -> Option<Arc<FxDirectory>> {
390 self.directory.parent()
391 }
392
393 fn set_parent(&self, _parent: Arc<FxDirectory>) {
394 unreachable!();
396 }
397
398 fn open_count_add_one(&self) {}
399 fn open_count_sub_one(self: Arc<Self>) {}
400
401 fn object_descriptor(&self) -> ObjectDescriptor {
402 ObjectDescriptor::Directory
403 }
404}
405
406impl MutableDirectory for BlobDirectory {
407 async fn unlink(self: Arc<Self>, name: &str, must_be_directory: bool) -> Result<(), Status> {
408 if must_be_directory {
409 return Err(Status::INVALID_ARGS);
410 }
411 self.directory.clone().unlink(name, must_be_directory).await
412 }
413
414 async fn update_attributes(
415 &self,
416 attributes: fio::MutableNodeAttributes,
417 ) -> Result<(), Status> {
418 self.directory.update_attributes(attributes).await
419 }
420
421 async fn sync(&self) -> Result<(), Status> {
422 self.directory.sync().await
423 }
424}
425
426impl DirectoryEntry for BlobDirectory {
428 fn open_entry(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), Status> {
429 request.open_dir(self)
430 }
431
432 fn scope(&self) -> Option<ExecutionScope> {
433 Some(self.volume().scope().clone())
434 }
435}
436
437impl GetEntryInfo for BlobDirectory {
438 fn entry_info(&self) -> EntryInfo {
439 self.directory.entry_info()
440 }
441}
442
443impl vfs::node::Node for BlobDirectory {
444 async fn get_attributes(
445 &self,
446 requested_attributes: fio::NodeAttributesQuery,
447 ) -> Result<fio::NodeAttributes2, Status> {
448 self.directory.get_attributes(requested_attributes).await
449 }
450
451 fn query_filesystem(&self) -> Result<FilesystemInfo, Status> {
452 self.directory.query_filesystem()
453 }
454}
455
456impl VfsDirectory for BlobDirectory {
458 fn deprecated_open(
459 self: Arc<Self>,
460 scope: ExecutionScope,
461 flags: fio::OpenFlags,
462 path: Path,
463 server_end: ServerEnd<NodeMarker>,
464 ) {
465 scope.clone().spawn(flags.to_object_request(server_end).handle_async(
466 async move |object_request| self.open_impl(scope, path, flags, object_request).await,
467 ));
468 }
469
470 fn open(
471 self: Arc<Self>,
472 scope: ExecutionScope,
473 path: Path,
474 flags: fio::Flags,
475 object_request: ObjectRequestRef<'_>,
476 ) -> Result<(), Status> {
477 scope.clone().spawn(object_request.take().handle_async(async move |object_request| {
478 self.open_impl(scope, path, flags, object_request).await
479 }));
480 Ok(())
481 }
482
483 async fn open_async(
484 self: Arc<Self>,
485 scope: ExecutionScope,
486 path: Path,
487 flags: fio::Flags,
488 object_request: ObjectRequestRef<'_>,
489 ) -> Result<(), Status> {
490 self.open_impl(scope, path, flags, object_request).await
491 }
492
493 async fn read_dirents(
494 &self,
495 pos: &TraversalPosition,
496 sink: Box<dyn dirents_sink::Sink>,
497 ) -> Result<(TraversalPosition, Box<dyn dirents_sink::Sealed>), Status> {
498 self.directory.read_dirents(pos, sink).await
499 }
500
501 fn register_watcher(
502 self: Arc<Self>,
503 scope: ExecutionScope,
504 mask: WatchMask,
505 watcher: DirectoryWatcher,
506 ) -> Result<(), Status> {
507 self.directory.clone().register_watcher(scope, mask, watcher)
508 }
509
510 fn unregister_watcher(self: Arc<Self>, key: usize) {
511 self.directory.clone().unregister_watcher(key)
512 }
513}
514
515impl From<object_store::Directory<FxVolume>> for BlobDirectory {
516 fn from(dir: object_store::Directory<FxVolume>) -> Self {
517 Self::new(dir.into())
518 }
519}
520
521#[cfg(test)]
522mod tests {
523 use super::*;
524 use crate::fuchsia::fxblob::testing::{BlobFixture, new_blob_fixture, open_blob_fixture};
525 use assert_matches::assert_matches;
526 use blob_writer::BlobWriter;
527 use delivery_blob::{CompressionMode, Type1Blob};
528 use fidl_fuchsia_fxfs::BlobReaderMarker;
529 use fuchsia_async::{DurationExt as _, TimeoutExt as _};
530 use fuchsia_component_client::connect_to_protocol_at_dir_svc;
531 use fuchsia_fs::directory::{
532 DirEntry, DirentKind, WatchEvent, WatchMessage, Watcher, readdir_inclusive,
533 };
534
535 use futures::StreamExt as _;
536 use std::path::PathBuf;
537
538 use crate::fuchsia::testing::{TestFixture, TestFixtureOptions};
539 use fidl_fuchsia_storage_mapping::MappingProviderMarker;
540 use fuchsia_fs::directory::open_directory_async;
541 use storage_device::DeviceHolder;
542 use storage_device::fake_device::FakeDevice;
543
544 #[fuchsia::test(threads = 10)]
545 async fn test_unlink() {
546 let fixture = new_blob_fixture().await;
547
548 let data = [1; 1000];
549
550 let hash = fixture.write_blob(&data, CompressionMode::Never).await;
551
552 assert_eq!(fixture.read_blob(hash).await, data);
553
554 fixture
555 .root()
556 .unlink(&format!("{}", hash), &fio::UnlinkOptions::default())
557 .await
558 .expect("FIDL failed")
559 .expect("unlink failed");
560
561 fixture.close().await;
562 }
563
564 #[fuchsia::test(threads = 10)]
565 async fn test_readdir() {
566 let fixture = new_blob_fixture().await;
567
568 let data = [0xab; 2];
569 let hash;
570 {
571 hash = fuchsia_merkle::root_from_slice(&data);
572 let compressed_data: Vec<u8> = Type1Blob::generate(&data, CompressionMode::Always);
573
574 let blob_proxy =
575 connect_to_protocol_at_dir_svc::<fidl_fuchsia_fxfs::BlobCreatorMarker>(
576 fixture.volume_out_dir(),
577 )
578 .expect("failed to connect to the Blob service");
579
580 let blob_writer_client_end = blob_proxy
581 .create(&hash.into(), false)
582 .await
583 .expect("transport error on create")
584 .expect("failed to create blob");
585
586 let writer = blob_writer_client_end.into_proxy();
587 let mut blob_writer = BlobWriter::create(writer, compressed_data.len() as u64)
588 .await
589 .expect("failed to create BlobWriter");
590 blob_writer.write(&compressed_data[..1]).await.unwrap();
591
592 assert_eq!(
594 readdir_inclusive(fixture.root()).await.ok(),
595 Some(vec![DirEntry { name: ".".to_string(), kind: DirentKind::Directory }])
596 );
597
598 blob_writer.write(&compressed_data[1..]).await.unwrap();
599 }
600
601 assert_eq!(
602 readdir_inclusive(fixture.root()).await.ok(),
603 Some(vec![
604 DirEntry { name: ".".to_string(), kind: DirentKind::Directory },
605 DirEntry { name: format! {"{}", hash}, kind: DirentKind::File },
606 ])
607 );
608
609 fixture.close().await;
610 }
611
612 #[fuchsia::test(threads = 10)]
613 async fn test_watchers() {
614 let fixture = new_blob_fixture().await;
615
616 let mut watcher = Watcher::new(fixture.root()).await.unwrap();
617 assert_eq!(
618 watcher.next().await,
619 Some(Ok(WatchMessage { event: WatchEvent::EXISTING, filename: PathBuf::from(".") }))
620 );
621 assert_matches!(
622 watcher.next().await,
623 Some(Ok(WatchMessage { event: WatchEvent::IDLE, .. }))
624 );
625
626 let data = vec![vec![0xab; 2], vec![0xcd; 65_536]];
627 let mut hashes = vec![];
628 let mut filenames = vec![];
629 for datum in data {
630 let hash = fuchsia_merkle::root_from_slice(&datum);
631 let filename = PathBuf::from(format!("{}", hash));
632 hashes.push(hash.clone());
633 filenames.push(filename.clone());
634
635 let compressed_data: Vec<u8> = Type1Blob::generate(&datum, CompressionMode::Always);
636
637 let blob_proxy =
638 connect_to_protocol_at_dir_svc::<fidl_fuchsia_fxfs::BlobCreatorMarker>(
639 fixture.volume_out_dir(),
640 )
641 .expect("failed to connect to the Blob service");
642
643 let blob_writer_client_end = blob_proxy
644 .create(&hash.into(), false)
645 .await
646 .expect("transport error on create")
647 .expect("failed to create blob");
648
649 let writer = blob_writer_client_end.into_proxy();
650 let mut blob_writer = BlobWriter::create(writer, compressed_data.len() as u64)
651 .await
652 .expect("failed to create BlobWriter");
653 blob_writer.write(&compressed_data[..compressed_data.len() - 1]).await.unwrap();
654
655 assert_matches!(
657 watcher
658 .next()
659 .on_timeout(zx::MonotonicDuration::from_millis(500).after_now(), || None)
660 .await,
661 None
662 );
663
664 blob_writer.write(&compressed_data[compressed_data.len() - 1..]).await.unwrap();
665
666 assert_eq!(
667 watcher.next().await,
668 Some(Ok(WatchMessage { event: WatchEvent::ADD_FILE, filename }))
669 );
670 }
671
672 for (hash, filename) in hashes.iter().zip(filenames) {
673 fixture
674 .root()
675 .unlink(&format!("{}", hash), &fio::UnlinkOptions::default())
676 .await
677 .expect("FIDL call failed")
678 .expect("unlink failed");
679 assert_eq!(
680 watcher.next().await,
681 Some(Ok(WatchMessage { event: WatchEvent::REMOVE_FILE, filename }))
682 );
683 }
684
685 std::mem::drop(watcher);
686 fixture.close().await;
687 }
688
689 #[fuchsia::test(threads = 10)]
690 async fn test_rename_fails() {
691 let fixture = new_blob_fixture().await;
692
693 let data = vec![];
694 let hash = fixture.write_blob(&data, CompressionMode::Never).await;
695
696 let (status, token) = fixture.root().get_token().await.expect("FIDL failed");
697 Status::ok(status).unwrap();
698 fixture
699 .root()
700 .rename(&format!("{}", hash), token.unwrap().into(), "foo")
701 .await
702 .expect("FIDL failed")
703 .expect_err("rename should fail");
704
705 fixture.close().await;
706 }
707
708 #[fuchsia::test(threads = 10)]
709 async fn test_link_fails() {
710 let fixture = new_blob_fixture().await;
711
712 let data = vec![];
713 let hash = fixture.write_blob(&data, CompressionMode::Never).await;
714
715 let (status, token) = fixture.root().get_token().await.expect("FIDL failed");
716 Status::ok(status).unwrap();
717 let status = fixture
718 .root()
719 .link(&format!("{}", hash), token.unwrap().into(), "foo")
720 .await
721 .expect("FIDL failed");
722 assert_eq!(Status::ok(status), Err(Status::NOT_SUPPORTED));
723
724 fixture.close().await;
725 }
726
727 #[fuchsia::test(threads = 10)]
728 async fn test_verify_cached_hash_node() {
729 let fixture = new_blob_fixture().await;
730
731 let data = vec![];
732 let hash = fixture.write_blob(&data, CompressionMode::Never).await;
733 let evil_hash =
734 Hash::from_str("2222222222222222222222222222222222222222222222222222222222222222")
735 .unwrap();
736
737 {
740 let root = fixture
741 .volume()
742 .root()
743 .clone()
744 .as_node()
745 .into_any()
746 .downcast::<BlobDirectory>()
747 .unwrap()
748 .directory()
749 .clone();
750 root.clone()
751 .link(evil_hash.to_string(), root, &hash.to_string())
752 .await
753 .expect("Linking file");
754 }
755 let device = fixture.close().await;
756
757 let fixture = open_blob_fixture(device).await;
758 {
759 let _vmo = fixture.get_blob_vmo(hash).await;
761
762 let blob_reader =
764 connect_to_protocol_at_dir_svc::<BlobReaderMarker>(fixture.volume_out_dir())
765 .expect("failed to connect to the BlobReader service");
766 blob_reader
767 .get_vmo(&evil_hash.into())
768 .await
769 .expect("transport error on BlobReader.GetVmo")
770 .expect_err("Hashes should mismatch");
771 }
772 fixture.close().await;
773 }
774
775 #[fuchsia::test(threads = 10)]
776 async fn test_blob_needs_overwrite_verifies_existence() {
777 let fixture = new_blob_fixture().await;
778 let data = vec![42u8; 32];
779 let hash = fixture.write_blob(&data, CompressionMode::Never).await;
780
781 let blob_creator =
782 connect_to_protocol_at_dir_svc::<BlobCreatorMarker>(fixture.volume_out_dir())
783 .expect("failed to connect to the BlobCreator service");
784 fixture.volume().volume().dirent_cache().clear();
786 assert!(
787 !blob_creator
788 .needs_overwrite(&hash.into())
789 .await
790 .expect("fidl transport")
791 .expect("Find new blob")
792 );
793
794 let blob_reader =
796 connect_to_protocol_at_dir_svc::<BlobReaderMarker>(fixture.volume_out_dir())
797 .expect("failed to connect to the BlobReader service");
798 blob_reader
799 .get_vmo(&hash.into())
800 .await
801 .expect("transport error on BlobReader.GetVmo")
802 .expect("Opening blob");
803 assert!(
804 !blob_creator
805 .needs_overwrite(&hash.into())
806 .await
807 .expect("fidl transport")
808 .expect("Find new blob")
809 );
810
811 blob_creator
813 .needs_overwrite(&[1u8; 32].into())
814 .await
815 .expect("fidl transport")
816 .expect_err("Blob should not exist");
817 fixture.close().await;
818 }
819
820 #[fuchsia::test]
821 async fn test_mapping_provider_exposure() {
822 let fixture = TestFixture::open(
823 DeviceHolder::new(FakeDevice::new(16384, 512)),
824 TestFixtureOptions { encrypted: false, as_blob: true, ..Default::default() },
825 )
826 .await;
827
828 let svc_dir = open_directory_async(fixture.volume_out_dir(), "svc", fio::PERM_READABLE)
829 .expect("failed to open svc dir");
830
831 let entries = readdir_inclusive(&svc_dir).await.expect("readdir");
832 let is_exposed = entries.iter().any(|e| e.name == MappingProviderMarker::PROTOCOL_NAME);
833 assert!(is_exposed, "MappingProvider not exposed");
834 let mapper_exposed = entries.iter().any(|e| e.name == fblock::MapperMarker::PROTOCOL_NAME);
835 assert!(mapper_exposed, "Mapper not exposed");
836
837 let mapper =
838 connect_to_protocol_at_dir_svc::<fblock::MapperMarker>(fixture.volume_out_dir())
839 .expect("failed to connect to the Mapper service");
840 assert_matches::assert_matches!(
841 mapper.take_event_stream().try_next().await,
842 Err(fidl::Error::ClientChannelClosed {
843 epitaph: fidl::Epitaph::Explicit(Err(Status::NOT_SUPPORTED)),
844 ..
845 })
846 );
847
848 fixture.close().await;
849 }
850}