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 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 if self.volume().use_driver_blob_paging() {
162 log::warn!(
163 "BlobReader.GetVmo called with driver blob paging enabled; \
164 falling back to Fxfs blob pager"
165 );
166 self.volume().set_use_driver_blob_paging(false);
167 }
168 let blob = self.open_blob_locked(id).await?.ok_or(FxfsError::NotFound)?;
169 let vmo = blob.create_child_vmo()?;
170 Ok((blob.clone(), vmo))
172 }
173
174 pub(crate) async fn open_blob(
176 self: &Arc<Self>,
177 id: &Identifier,
178 ) -> Result<Option<OpenedNode<FxBlob>>, Error> {
179 let store = self.store();
180 let fs = store.filesystem();
181 let keys = lock_keys![LockKey::object(store.store_object_id(), self.directory.object_id())];
182 let _guard = fs.lock_manager().read_lock(keys.clone()).await;
184 self.open_blob_locked(id).await
185 }
186
187 async fn open_blob_locked(
191 self: &Arc<Self>,
192 id: &Identifier,
193 ) -> Result<Option<OpenedNode<FxBlob>>, Error> {
194 let node = match self.directory.directory().owner().dirent_cache().lookup(&(
195 self.directory.object_id(),
196 &id.string,
197 false,
198 )) {
199 Some(node) => Some(node),
200 None => {
201 if let Some((object_id, _, _)) =
202 self.directory.directory().lookup(&id.string).await?
203 {
204 let node = self.get_or_load_node(object_id, &id).await?;
205 self.directory.directory().owner().dirent_cache().insert(
206 DirentCacheKey::new(self.directory.object_id(), id.string.clone(), false),
207 node.clone(),
208 );
209 Some(node)
210 } else {
211 None
212 }
213 }
214 };
215 let Some(node) = node else {
216 return Ok(None);
217 };
218 if node.object_descriptor() != ObjectDescriptor::File {
219 return Err(FxfsError::Inconsistent)
220 .with_context(|| format!("Blob {} has invalid object descriptor!", id.string));
221 }
222 let mut blob = node
223 .into_any()
224 .downcast::<FxBlob>()
225 .map_err(|_| FxfsError::Inconsistent)
226 .with_context(|| format!("Blob {} has incorrect node type!", id.string))?;
227 if !self.volume().use_driver_blob_paging() && !blob.has_fxfs_pager() {
228 let object_id = blob.object_id();
229 self.volume().cache().remove(blob.as_ref());
230 drop(blob);
231 let node = self.get_or_load_node(object_id, id).await?;
232 self.directory.directory().owner().dirent_cache().insert(
233 DirentCacheKey::new(self.directory.object_id(), id.string.clone(), false),
234 node.clone(),
235 );
236 blob = node
237 .into_any()
238 .downcast::<FxBlob>()
239 .map_err(|_| FxfsError::Inconsistent)
240 .with_context(|| format!("Blob {} has incorrect node type!", id.string))?;
241 }
242 Ok(Some(OpenedNode::new(blob)))
243 }
244
245 async fn get_or_load_node(
249 self: &Arc<Self>,
250 object_id: u64,
251 id: &Identifier,
252 ) -> Result<Arc<dyn FxNode>, Error> {
253 let volume = self.volume();
254 match volume.cache().get_or_reserve(object_id).await {
255 GetResult::Node(node) => {
256 let blob = node.into_any().downcast::<FxBlob>().map_err(|_| {
260 anyhow!(FxfsError::Inconsistent).context("Loaded non-blob from cache")
261 })?;
262 ensure!(
263 blob.root() == id.hash,
264 anyhow!(FxfsError::Inconsistent)
265 .context("Loaded blob by node that did not match the given hash")
266 );
267 Ok(blob as Arc<dyn FxNode>)
268 }
269 GetResult::Placeholder(placeholder) => {
270 let handle = StoreObjectHandle::new(
271 volume.clone(),
272 object_id,
273 false,
274 HandleOptions::default(),
275 false,
276 );
277 let node = FxBlob::new(handle, id.hash).await? as Arc<dyn FxNode>;
278 placeholder.commit(&node);
279 Ok(node)
280 }
281 }
282 }
283
284 async fn create_blob_writer(
288 self: &Arc<Self>,
289 hash: Hash,
290 allow_existing: bool,
291 ) -> Result<ClientEnd<BlobWriterMarker>, CreateBlobError> {
292 let id = hash.into();
293 let blob_exists = self
294 .open_blob(&id)
295 .await
296 .map_err(|e| {
297 log::error!("Failed to lookup blob: {:?}", e);
298 CreateBlobError::Internal
299 })?
300 .is_some();
301 if blob_exists && !allow_existing {
302 return Err(CreateBlobError::AlreadyExists);
303 }
304 let (client_end, request_stream) = create_request_stream::<BlobWriterMarker>();
305 let writer = DeliveryBlobWriter::new(self, hash).await.map_err(|e| {
306 log::error!("Failed to create blob writer: {:?}", e);
307 CreateBlobError::Internal
308 })?;
309 self.volume().scope().spawn(async move {
310 if let Err(e) = writer.handle_requests(request_stream).await {
311 log::error!("Failed to handle BlobWriter requests: {}", e);
312 }
313 });
314 return Ok(client_end);
315 }
316
317 async fn needs_overwrite(&self, blob_hash: Identifier) -> Result<bool, Error> {
318 if self
322 .volume()
323 .dirent_cache()
324 .lookup(&(self.object_id(), &blob_hash.string, false))
325 .is_some()
326 {
327 return Ok(false);
328 }
329 match self.directory.directory().lookup(&blob_hash.string).await? {
330 Some(_) => Ok(false),
331 None => Err(FxfsError::NotFound.into()),
332 }
333 }
334
335 async fn handle_blob_creator_requests(self: Arc<Self>, mut requests: BlobCreatorRequestStream) {
336 while let Ok(Some(request)) = requests.try_next().await {
337 match request {
338 BlobCreatorRequest::Create { responder, hash, allow_existing } => {
339 async {
340 responder
341 .send(self.create_blob_writer(Hash::from(hash), allow_existing).await)
342 .unwrap_or_else(|error| {
343 log::error!(error:?; "failed to send Create response");
344 });
345 }
346 .trace(trace_future_args!("BlobCreator::Create"))
347 .await;
348 }
349 BlobCreatorRequest::NeedsOverwrite { blob_hash, responder } => {
350 async {
351 let _ = responder.send(
352 self.needs_overwrite(Hash::from(blob_hash).into())
353 .await
354 .map_err(map_to_raw_status),
355 );
356 }
357 .trace(trace_future_args!("BlobCreator::NeedsOverwrite"))
358 .await;
359 }
360 }
361 }
362 }
363
364 async fn handle_blob_reader_requests(self: Arc<Self>, mut requests: BlobReaderRequestStream) {
365 while let Ok(Some(request)) = requests.try_next().await {
366 match request {
367 BlobReaderRequest::GetVmo { blob_hash, responder } => {
368 async {
369 responder
370 .send(
371 self.get_blob_vmo(blob_hash.into())
372 .await
373 .map_err(map_to_raw_status),
374 )
375 .unwrap_or_else(|error| {
376 log::error!(error:?; "failed to send GetVmo response");
377 });
378 }
379 .trace(trace_future_args!("BlobReader::GetVmo"))
380 .await;
381 }
382 };
383 }
384 }
385
386 async fn open_impl(
387 self: Arc<Self>,
388 scope: ExecutionScope,
389 path: Path,
390 flags: impl ProtocolsExt,
391 object_request: ObjectRequestRef<'_>,
392 ) -> Result<(), Status> {
393 if path.is_empty() {
394 object_request
395 .create_connection::<MutableConnection<_>, _>(
396 scope,
397 OpenedNode::new(self).take(),
398 flags,
399 )
400 .await
401 } else {
402 Err(Status::NOT_SUPPORTED)
403 }
404 }
405}
406
407impl FxNode for BlobDirectory {
408 fn object_id(&self) -> u64 {
409 self.directory.object_id()
410 }
411
412 fn parent(&self) -> Option<Arc<FxDirectory>> {
413 self.directory.parent()
414 }
415
416 fn set_parent(&self, _parent: Arc<FxDirectory>) {
417 unreachable!();
419 }
420
421 fn open_count_add_one(&self) {}
422 fn open_count_sub_one(self: Arc<Self>) {}
423
424 fn object_descriptor(&self) -> ObjectDescriptor {
425 ObjectDescriptor::Directory
426 }
427}
428
429impl MutableDirectory for BlobDirectory {
430 async fn unlink(self: Arc<Self>, name: &str, must_be_directory: bool) -> Result<(), Status> {
431 if must_be_directory {
432 return Err(Status::INVALID_ARGS);
433 }
434 self.directory.clone().unlink(name, must_be_directory).await
435 }
436
437 async fn update_attributes(
438 &self,
439 attributes: fio::MutableNodeAttributes,
440 ) -> Result<(), Status> {
441 self.directory.update_attributes(attributes).await
442 }
443
444 async fn sync(&self) -> Result<(), Status> {
445 self.directory.sync().await
446 }
447}
448
449impl DirectoryEntry for BlobDirectory {
451 fn open_entry(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), Status> {
452 request.open_dir(self)
453 }
454
455 fn scope(&self) -> Option<ExecutionScope> {
456 Some(self.volume().scope().clone())
457 }
458}
459
460impl GetEntryInfo for BlobDirectory {
461 fn entry_info(&self) -> EntryInfo {
462 self.directory.entry_info()
463 }
464}
465
466impl vfs::node::Node for BlobDirectory {
467 async fn get_attributes(
468 &self,
469 requested_attributes: fio::NodeAttributesQuery,
470 ) -> Result<fio::NodeAttributes2, Status> {
471 self.directory.get_attributes(requested_attributes).await
472 }
473
474 fn query_filesystem(&self) -> Result<FilesystemInfo, Status> {
475 self.directory.query_filesystem()
476 }
477}
478
479impl VfsDirectory for BlobDirectory {
481 fn deprecated_open(
482 self: Arc<Self>,
483 scope: ExecutionScope,
484 flags: fio::OpenFlags,
485 path: Path,
486 server_end: ServerEnd<NodeMarker>,
487 ) {
488 scope.clone().spawn(flags.to_object_request(server_end).handle_async(
489 async move |object_request| self.open_impl(scope, path, flags, object_request).await,
490 ));
491 }
492
493 fn open(
494 self: Arc<Self>,
495 scope: ExecutionScope,
496 path: Path,
497 flags: fio::Flags,
498 object_request: ObjectRequestRef<'_>,
499 ) -> Result<(), Status> {
500 scope.clone().spawn(object_request.take().handle_async(async move |object_request| {
501 self.open_impl(scope, path, flags, object_request).await
502 }));
503 Ok(())
504 }
505
506 async fn open_async(
507 self: Arc<Self>,
508 scope: ExecutionScope,
509 path: Path,
510 flags: fio::Flags,
511 object_request: ObjectRequestRef<'_>,
512 ) -> Result<(), Status> {
513 self.open_impl(scope, path, flags, object_request).await
514 }
515
516 async fn read_dirents(
517 &self,
518 pos: &TraversalPosition,
519 sink: Box<dyn dirents_sink::Sink>,
520 ) -> Result<(TraversalPosition, Box<dyn dirents_sink::Sealed>), Status> {
521 self.directory.read_dirents(pos, sink).await
522 }
523
524 fn register_watcher(
525 self: Arc<Self>,
526 scope: ExecutionScope,
527 mask: WatchMask,
528 watcher: DirectoryWatcher,
529 ) -> Result<(), Status> {
530 self.directory.clone().register_watcher(scope, mask, watcher)
531 }
532
533 fn unregister_watcher(self: Arc<Self>, key: usize) {
534 self.directory.clone().unregister_watcher(key)
535 }
536}
537
538impl From<object_store::Directory<FxVolume>> for BlobDirectory {
539 fn from(dir: object_store::Directory<FxVolume>) -> Self {
540 Self::new(dir.into())
541 }
542}
543
544#[cfg(test)]
545mod tests {
546 use super::*;
547 use crate::fuchsia::fxblob::testing::{BlobFixture, new_blob_fixture, open_blob_fixture};
548 use assert_matches::assert_matches;
549 use blob_writer::BlobWriter;
550 use delivery_blob::{CompressionMode, Type1Blob};
551 use fidl_fuchsia_fxfs::BlobReaderMarker;
552 use fuchsia_async::{DurationExt as _, TimeoutExt as _};
553 use fuchsia_component_client::connect_to_protocol_at_dir_svc;
554 use fuchsia_fs::directory::{
555 DirEntry, DirentKind, WatchEvent, WatchMessage, Watcher, readdir_inclusive,
556 };
557
558 use futures::StreamExt as _;
559 use std::path::PathBuf;
560
561 use crate::fuchsia::testing::{TestFixture, TestFixtureOptions};
562 use fidl_fuchsia_storage_mapping::MappingProviderMarker;
563 use fuchsia_fs::directory::open_directory_async;
564 use storage_device::DeviceHolder;
565 use storage_device::fake_device::FakeDevice;
566
567 #[fuchsia::test(threads = 10)]
568 async fn test_unlink() {
569 let fixture = new_blob_fixture().await;
570
571 let data = [1; 1000];
572
573 let hash = fixture.write_blob(&data, CompressionMode::Never).await;
574
575 assert_eq!(fixture.read_blob(hash).await, data);
576
577 fixture
578 .root()
579 .unlink(&format!("{}", hash), &fio::UnlinkOptions::default())
580 .await
581 .expect("FIDL failed")
582 .expect("unlink failed");
583
584 fixture.close().await;
585 }
586
587 #[fuchsia::test(threads = 10)]
588 async fn test_readdir() {
589 let fixture = new_blob_fixture().await;
590
591 let data = [0xab; 2];
592 let hash;
593 {
594 hash = fuchsia_merkle::root_from_slice(&data);
595 let compressed_data: Vec<u8> = Type1Blob::generate(&data, CompressionMode::Always);
596
597 let blob_proxy =
598 connect_to_protocol_at_dir_svc::<fidl_fuchsia_fxfs::BlobCreatorMarker>(
599 fixture.volume_out_dir(),
600 )
601 .expect("failed to connect to the Blob service");
602
603 let blob_writer_client_end = blob_proxy
604 .create(&hash.into(), false)
605 .await
606 .expect("transport error on create")
607 .expect("failed to create blob");
608
609 let writer = blob_writer_client_end.into_proxy();
610 let mut blob_writer = BlobWriter::create(writer, compressed_data.len() as u64)
611 .await
612 .expect("failed to create BlobWriter");
613 blob_writer.write(&compressed_data[..1]).await.unwrap();
614
615 assert_eq!(
617 readdir_inclusive(fixture.root()).await.ok(),
618 Some(vec![DirEntry { name: ".".to_string(), kind: DirentKind::Directory }])
619 );
620
621 blob_writer.write(&compressed_data[1..]).await.unwrap();
622 }
623
624 assert_eq!(
625 readdir_inclusive(fixture.root()).await.ok(),
626 Some(vec![
627 DirEntry { name: ".".to_string(), kind: DirentKind::Directory },
628 DirEntry { name: format! {"{}", hash}, kind: DirentKind::File },
629 ])
630 );
631
632 fixture.close().await;
633 }
634
635 #[fuchsia::test(threads = 10)]
636 async fn test_watchers() {
637 let fixture = new_blob_fixture().await;
638
639 let mut watcher = Watcher::new(fixture.root()).await.unwrap();
640 assert_eq!(
641 watcher.next().await,
642 Some(Ok(WatchMessage { event: WatchEvent::EXISTING, filename: PathBuf::from(".") }))
643 );
644 assert_matches!(
645 watcher.next().await,
646 Some(Ok(WatchMessage { event: WatchEvent::IDLE, .. }))
647 );
648
649 let data = vec![vec![0xab; 2], vec![0xcd; 65_536]];
650 let mut hashes = vec![];
651 let mut filenames = vec![];
652 for datum in data {
653 let hash = fuchsia_merkle::root_from_slice(&datum);
654 let filename = PathBuf::from(format!("{}", hash));
655 hashes.push(hash.clone());
656 filenames.push(filename.clone());
657
658 let compressed_data: Vec<u8> = Type1Blob::generate(&datum, CompressionMode::Always);
659
660 let blob_proxy =
661 connect_to_protocol_at_dir_svc::<fidl_fuchsia_fxfs::BlobCreatorMarker>(
662 fixture.volume_out_dir(),
663 )
664 .expect("failed to connect to the Blob service");
665
666 let blob_writer_client_end = blob_proxy
667 .create(&hash.into(), false)
668 .await
669 .expect("transport error on create")
670 .expect("failed to create blob");
671
672 let writer = blob_writer_client_end.into_proxy();
673 let mut blob_writer = BlobWriter::create(writer, compressed_data.len() as u64)
674 .await
675 .expect("failed to create BlobWriter");
676 blob_writer.write(&compressed_data[..compressed_data.len() - 1]).await.unwrap();
677
678 assert_matches!(
680 watcher
681 .next()
682 .on_timeout(zx::MonotonicDuration::from_millis(500).after_now(), || None)
683 .await,
684 None
685 );
686
687 blob_writer.write(&compressed_data[compressed_data.len() - 1..]).await.unwrap();
688
689 assert_eq!(
690 watcher.next().await,
691 Some(Ok(WatchMessage { event: WatchEvent::ADD_FILE, filename }))
692 );
693 }
694
695 for (hash, filename) in hashes.iter().zip(filenames) {
696 fixture
697 .root()
698 .unlink(&format!("{}", hash), &fio::UnlinkOptions::default())
699 .await
700 .expect("FIDL call failed")
701 .expect("unlink failed");
702 assert_eq!(
703 watcher.next().await,
704 Some(Ok(WatchMessage { event: WatchEvent::REMOVE_FILE, filename }))
705 );
706 }
707
708 std::mem::drop(watcher);
709 fixture.close().await;
710 }
711
712 #[fuchsia::test(threads = 10)]
713 async fn test_rename_fails() {
714 let fixture = new_blob_fixture().await;
715
716 let data = vec![];
717 let hash = fixture.write_blob(&data, CompressionMode::Never).await;
718
719 let (status, token) = fixture.root().deprecated_get_token().await.expect("FIDL failed");
720 Status::ok(status).unwrap();
721 fixture
722 .root()
723 .rename(&format!("{}", hash), token.unwrap().into(), "foo")
724 .await
725 .expect("FIDL failed")
726 .expect_err("rename should fail");
727
728 fixture.close().await;
729 }
730
731 #[fuchsia::test(threads = 10)]
732 async fn test_link_fails() {
733 let fixture = new_blob_fixture().await;
734
735 let data = vec![];
736 let hash = fixture.write_blob(&data, CompressionMode::Never).await;
737
738 let (status, token) = fixture.root().deprecated_get_token().await.expect("FIDL failed");
739 Status::ok(status).unwrap();
740 let status = fixture
741 .root()
742 .link(&format!("{}", hash), token.unwrap().into(), "foo")
743 .await
744 .expect("FIDL failed");
745 assert_eq!(Status::ok(status), Err(Status::NOT_SUPPORTED));
746
747 fixture.close().await;
748 }
749
750 #[fuchsia::test(threads = 10)]
751 async fn test_verify_cached_hash_node() {
752 let fixture = new_blob_fixture().await;
753
754 let data = vec![];
755 let hash = fixture.write_blob(&data, CompressionMode::Never).await;
756 let evil_hash =
757 Hash::from_str("2222222222222222222222222222222222222222222222222222222222222222")
758 .unwrap();
759
760 {
763 let root = fixture
764 .volume()
765 .root()
766 .clone()
767 .as_node()
768 .into_any()
769 .downcast::<BlobDirectory>()
770 .unwrap()
771 .directory()
772 .clone();
773 root.clone()
774 .link(evil_hash.to_string(), root, &hash.to_string())
775 .await
776 .expect("Linking file");
777 }
778 let device = fixture.close().await;
779
780 let fixture = open_blob_fixture(device).await;
781 {
782 let _vmo = fixture.get_blob_vmo(hash).await;
784
785 let blob_reader =
787 connect_to_protocol_at_dir_svc::<BlobReaderMarker>(fixture.volume_out_dir())
788 .expect("failed to connect to the BlobReader service");
789 blob_reader
790 .get_vmo(&evil_hash.into())
791 .await
792 .expect("transport error on BlobReader.GetVmo")
793 .expect_err("Hashes should mismatch");
794 }
795 fixture.close().await;
796 }
797
798 #[fuchsia::test(threads = 10)]
799 async fn test_blob_needs_overwrite_verifies_existence() {
800 let fixture = new_blob_fixture().await;
801 let data = vec![42u8; 32];
802 let hash = fixture.write_blob(&data, CompressionMode::Never).await;
803
804 let blob_creator =
805 connect_to_protocol_at_dir_svc::<BlobCreatorMarker>(fixture.volume_out_dir())
806 .expect("failed to connect to the BlobCreator service");
807 fixture.volume().volume().dirent_cache().clear();
809 assert!(
810 !blob_creator
811 .needs_overwrite(&hash.into())
812 .await
813 .expect("fidl transport")
814 .expect("Find new blob")
815 );
816
817 let blob_reader =
819 connect_to_protocol_at_dir_svc::<BlobReaderMarker>(fixture.volume_out_dir())
820 .expect("failed to connect to the BlobReader service");
821 blob_reader
822 .get_vmo(&hash.into())
823 .await
824 .expect("transport error on BlobReader.GetVmo")
825 .expect("Opening blob");
826 assert!(
827 !blob_creator
828 .needs_overwrite(&hash.into())
829 .await
830 .expect("fidl transport")
831 .expect("Find new blob")
832 );
833
834 blob_creator
836 .needs_overwrite(&[1u8; 32].into())
837 .await
838 .expect("fidl transport")
839 .expect_err("Blob should not exist");
840 fixture.close().await;
841 }
842
843 #[fuchsia::test]
844 async fn test_mapping_provider_exposure() {
845 let fixture = TestFixture::open(
846 DeviceHolder::new(FakeDevice::new(16384, 512)),
847 TestFixtureOptions { encrypted: false, as_blob: true, ..Default::default() },
848 )
849 .await;
850
851 let svc_dir = open_directory_async(fixture.volume_out_dir(), "svc", fio::PERM_READABLE)
852 .expect("failed to open svc dir");
853
854 let entries = readdir_inclusive(&svc_dir).await.expect("readdir");
855 let is_exposed = entries.iter().any(|e| e.name == MappingProviderMarker::PROTOCOL_NAME);
856 assert!(is_exposed, "MappingProvider not exposed");
857 let mapper_exposed = entries.iter().any(|e| e.name == fblock::MapperMarker::PROTOCOL_NAME);
858 assert!(mapper_exposed, "Mapper not exposed");
859
860 let mapper =
861 connect_to_protocol_at_dir_svc::<fblock::MapperMarker>(fixture.volume_out_dir())
862 .expect("failed to connect to the Mapper service");
863 assert_matches::assert_matches!(
864 mapper.take_event_stream().try_next().await,
865 Err(fidl::Error::ClientChannelClosed {
866 epitaph: fidl::Epitaph::Explicit(Err(Status::NOT_SUPPORTED)),
867 ..
868 })
869 );
870
871 fixture.close().await;
872 }
873}