1use crate::constants::*;
9use crate::fuchsia::directory::FxDirectory;
10use crate::fuchsia::errors::map_to_status;
11use crate::fuchsia::node::{FxNode, OpenedNode};
12use crate::fuchsia::pager::{
13 MarkDirtyRange, PageInRange, PagerBacked, PagerPacketReceiverRegistration, default_page_in,
14};
15use crate::fuchsia::volume::{FxVolume, READ_AHEAD_SIZE};
16use anyhow::{Context, Error, anyhow, bail, ensure};
17use delivery_blob::compression::{CompressionAlgorithm, CompressionInfo};
18use fidl_fuchsia_feedback::{Annotation, Attachment, CrashReport};
19use fidl_fuchsia_mem::Buffer as MemBuffer;
20use fuchsia_component_client::connect_to_protocol;
21use fuchsia_merkle::{Hash, MerkleVerifier, ReadSizedMerkleVerifier};
22use futures::{StreamExt, TryStreamExt, try_join};
23use fxfs::blob_metadata::{BlobFormat, BlobMetadata, FxfsBlobMetadataExt};
24use fxfs::errors::FxfsError;
25use fxfs::lock_keys;
26use fxfs::log::*;
27use fxfs::object_handle::ObjectHandle;
28use fxfs::object_store::transaction::LockKey;
29use fxfs::object_store::{AttributeId, DataObjectHandle, ObjectDescriptor, StoreObjectHandle};
30use fxfs::round::round_down;
31use fxfs_macros::ToWeakNode;
32use mapping::Extent as MappingExtent;
33use refaults_vmo::AtomicBitVec;
34use std::ops::Range;
35use std::sync::Arc;
36use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
37use storage_device::buffer::{Buffer, BufferFuture, MutableBufferRef};
38use storage_units::page_size;
39use zx::Status;
40
41pub struct BlobExtents {
46 pub data: Vec<MappingExtent>,
47 pub merkle: Vec<MappingExtent>,
48}
49
50const PURGED: usize = 1 << (usize::BITS - 1);
54
55const EMPTY_BLOB_HASH: Hash = Hash::from_array([
56 0x15, 0xec, 0x7b, 0xf0, 0xb5, 0x07, 0x32, 0xb4, 0x9f, 0x82, 0x28, 0xe0, 0x7d, 0x24, 0x36, 0x53,
57 0x38, 0xf9, 0xe3, 0xab, 0x99, 0x4b, 0x00, 0xaf, 0x08, 0xe5, 0xa3, 0xbf, 0xfe, 0x55, 0xfd, 0x8b,
58]);
59
60struct FxBlobPagerState {
65 vmo: zx::Vmo,
66 merkle_verifier: ReadSizedMerkleVerifier,
67 compression_info: Option<CompressionInfo>,
68 pager_packet_receiver_registration: Arc<PagerPacketReceiverRegistration<FxBlob>>,
69 chunks_supplied: AtomicBitVec,
70}
71
72#[derive(ToWeakNode)]
74pub struct FxBlob {
75 handle: StoreObjectHandle<FxVolume>,
76 open_count: AtomicUsize,
77 merkle_root: Hash,
78 uncompressed_size: u64, pager_state: Option<FxBlobPagerState>,
80}
81
82static_assertions::const_assert!(size_of::<FxBlob>() <= 192);
84
85impl FxBlob {
86 pub async fn new(
87 handle: StoreObjectHandle<FxVolume>,
88 merkle_root: Hash,
89 ) -> Result<Arc<Self>, Error> {
90 let stored_size =
91 handle.store().get_attribute_size(handle.object_id(), AttributeId::DATA).await?;
92 let metadata = BlobMetadata::read_from(&handle).await?;
93 let uncompressed_size = match &metadata.format {
94 BlobFormat::Uncompressed => stored_size,
95 BlobFormat::ChunkedZstd { uncompressed_size, .. }
96 | BlobFormat::ChunkedLz4 { uncompressed_size, .. } => *uncompressed_size,
97 };
98
99 let expect_merkle_hashes = if uncompressed_size <= fuchsia_merkle::BLOCK_SIZE as u64 {
104 0
105 } else {
106 uncompressed_size.div_ceil(fuchsia_merkle::BLOCK_SIZE as u64)
107 };
108 ensure!(
109 metadata.merkle_leaves.len() as u64 == expect_merkle_hashes,
110 FxfsError::IntegrityError
111 );
112 ensure!(
114 (uncompressed_size == 0) == (merkle_root == EMPTY_BLOB_HASH),
115 FxfsError::IntegrityError
116 );
117
118 if handle.owner().use_driver_blob_paging() {
119 return Ok(Arc::new(Self {
120 handle,
121 open_count: AtomicUsize::new(0),
122 merkle_root,
123 uncompressed_size,
124 pager_state: None,
125 }));
126 }
127
128 let compression_info = match &metadata.format {
129 BlobFormat::Uncompressed => None,
130 BlobFormat::ChunkedZstd { chunk_size, compressed_offsets, .. } => {
131 Some(CompressionInfo::new(
132 *chunk_size,
133 stored_size,
134 compressed_offsets,
135 CompressionAlgorithm::Zstd,
136 )?)
137 }
138 BlobFormat::ChunkedLz4 { chunk_size, compressed_offsets, .. } => {
139 Some(CompressionInfo::new(
140 *chunk_size,
141 stored_size,
142 compressed_offsets,
143 CompressionAlgorithm::Lz4,
144 )?)
145 }
146 };
147 let merkle_verifier = metadata.into_merkle_verifier(merkle_root)?;
148
149 let min_chunk_size = min_chunk_size(&compression_info);
150 let merkle_verifier =
151 ReadSizedMerkleVerifier::new(merkle_verifier, min_chunk_size as usize)?;
152 let chunks_supplied = AtomicBitVec::new(uncompressed_size.div_ceil(min_chunk_size));
153
154 Ok(Arc::new_cyclic(|weak| {
155 let (vmo, pager_packet_receiver_registration) = handle
156 .owner()
157 .pager()
158 .create_vmo(weak.clone(), uncompressed_size, zx::VmoOptions::empty())
159 .unwrap();
160 set_vmo_name(&vmo, &merkle_root);
161 Self {
162 handle,
163 open_count: AtomicUsize::new(0),
164 merkle_root,
165 uncompressed_size,
166 pager_state: Some(FxBlobPagerState {
167 vmo,
168 merkle_verifier,
169 compression_info,
170 pager_packet_receiver_registration: Arc::new(
171 pager_packet_receiver_registration,
172 ),
173 chunks_supplied,
174 }),
175 }
176 }))
177 }
178
179 pub fn overwrite_me(
181 self: &Arc<Self>,
182 handle: DataObjectHandle<FxVolume>,
183 pager_info: Option<(MerkleVerifier, Option<CompressionInfo>)>,
187 ) -> Arc<Self> {
188 let pager_state =
189 if let (Some(old_pager_state), Some((merkle_verifier, compression_info))) =
190 (&self.pager_state, pager_info)
191 {
192 let min_chunk_size = min_chunk_size(&compression_info);
193 let merkle_verifier =
194 ReadSizedMerkleVerifier::new(merkle_verifier, min_chunk_size as usize).expect(
195 "The chunk size should have been validated by the delivery blob parser",
196 );
197 let chunks_supplied =
200 AtomicBitVec::new(self.uncompressed_size.div_ceil(min_chunk_size));
201 let vmo = old_pager_state.vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap();
202
203 Some(FxBlobPagerState {
204 vmo,
205 merkle_verifier,
206 compression_info,
207 pager_packet_receiver_registration: old_pager_state
208 .pager_packet_receiver_registration
209 .clone(),
210 chunks_supplied,
211 })
212 } else {
213 None
214 };
215
216 let new_blob = Arc::new(Self {
217 handle: handle.into_store_object_handle(),
218 open_count: AtomicUsize::new(0),
219 merkle_root: self.merkle_root,
220 uncompressed_size: self.uncompressed_size,
221 pager_state,
222 });
223
224 self.handle.owner().cache().remove(self.as_ref());
227
228 if let Some(old_pager_state) = &self.pager_state {
229 let receiver_lock = old_pager_state
232 .pager_packet_receiver_registration
233 .receiver()
234 .set_receiver(&new_blob);
235 if receiver_lock.is_strong() {
236 new_blob.open_count_add_one();
243 self.clone().open_count_sub_one();
244 }
245 }
246 new_blob
247 }
248
249 pub fn root(&self) -> Hash {
250 self.merkle_root
251 }
252
253 pub fn byte_size(&self) -> u64 {
254 self.uncompressed_size
255 }
256
257 pub fn has_fxfs_pager(&self) -> bool {
258 self.pager_state.is_some()
259 }
260
261 fn pager_state(&self) -> &FxBlobPagerState {
262 self.pager_state
263 .as_ref()
264 .expect("FxBlob pager state not initialized when use_driver_blob_paging is enabled")
265 }
266
267 pub async fn get_mapping_extents(&self) -> Result<BlobExtents, Error> {
271 let tree = &self.handle.store().tree();
272 let layer_set = tree.layer_set();
273
274 let mut merger = layer_set.merger();
276 let stream = self.handle.extent_stream(&mut merger, AttributeId::DATA).await?;
277 let data = stream
279 .map(|res| {
280 let extent = res?;
281 MappingExtent::try_new(extent.logical_range(), Some(extent.device_range().start))
282 .map_err(|_| anyhow!(FxfsError::Inconsistent))
283 })
284 .try_collect()
285 .await?;
286
287 let mut merger = layer_set.merger();
289 let stream = self.handle.extent_stream(&mut merger, AttributeId::BLOB_METADATA).await?;
290 let merkle = stream
291 .map(|res| {
292 let extent = res?;
293 MappingExtent::try_new(extent.logical_range(), Some(extent.device_range().start))
294 .map_err(|_| anyhow!(FxfsError::Inconsistent))
295 })
296 .try_collect()
297 .await?;
298
299 Ok(BlobExtents { data, merkle })
300 }
301
302 pub async fn stored_size(&self) -> Result<u64, Error> {
304 self.handle.store().get_attribute_size(self.handle.object_id(), AttributeId::DATA).await
305 }
306
307 fn record_page_fault_metric(&self, range: &Range<u64>) {
308 let pager_state = self.pager_state();
309 let page_size = page_size();
310 let total_pages = (range.end - range.start) / page_size;
311 if total_pages > 0 {
312 self.handle
313 .owner()
314 .page_fault_counter()
315 .increment_faults(total_pages, Ordering::Relaxed);
316 }
317
318 let chunk_size: u64 = min_chunk_size(&pager_state.compression_info);
319 let pages_per_chunk = chunk_size / page_size;
320
321 let first_chunk = range.start / chunk_size;
322 let full_end_chunk = range.end / chunk_size;
323
324 let mut supplied_pages =
325 pager_state.chunks_supplied.test_and_set_range(first_chunk, full_end_chunk)
326 * pages_per_chunk;
327
328 let trailing_pages = (range.end % chunk_size) / page_size;
330 if trailing_pages > 0 {
331 supplied_pages +=
332 pager_state.chunks_supplied.test_and_set_range(full_end_chunk, full_end_chunk + 1)
333 * trailing_pages;
334 }
335
336 if supplied_pages > 0 {
337 self.handle
338 .owner()
339 .page_fault_counter()
340 .increment_refaults(supplied_pages, Ordering::Relaxed);
341 }
342 }
343
344 fn allocate_buffer(&self, size: u64) -> BufferFuture<'_> {
346 self.handle.store().device().allocate_buffer(size as usize)
347 }
348
349 fn allocate_trusted_buffer(&self, size: u64) -> BufferFuture<'_> {
352 self.handle.owner().blob_allocator().allocate_buffer(size as usize)
353 }
354
355 async fn read_blocks(&self, offset: u64, buf: MutableBufferRef<'_>) -> Result<(), Error> {
356 let fs = self.handle.store().filesystem();
357 let guard = fs
358 .lock_manager()
359 .read_lock(lock_keys![LockKey::object_attribute(
360 self.handle.store().store_object_id(),
361 self.handle.object_id(),
362 AttributeId::DATA,
363 )])
364 .await;
365 self.handle.read_aligned_unchecked(AttributeId::DATA, offset, buf, &guard).await
366 }
367}
368
369impl Drop for FxBlob {
370 fn drop(&mut self) {
371 let volume = self.handle.owner();
372 volume.cache().remove(self);
373 }
374}
375
376impl OpenedNode<FxBlob> {
377 pub fn create_child_vmo(&self) -> Result<zx::Vmo, Status> {
386 let blob = self.0.as_ref();
387 let pager_state = blob.pager_state.as_ref().ok_or(Status::NOT_SUPPORTED)?;
388 let child_vmo = pager_state.vmo.create_child(
389 zx::VmoChildOptions::REFERENCE | zx::VmoChildOptions::NO_WRITE,
390 0,
391 0,
392 )?;
393 if blob.handle.owner().pager().watch_for_zero_children(blob).map_err(map_to_status)? {
394 blob.open_count_add_one();
398 }
399 Ok(child_vmo)
400 }
401}
402
403impl FxNode for FxBlob {
404 fn object_id(&self) -> u64 {
405 self.handle.object_id()
406 }
407
408 fn parent(&self) -> Option<Arc<FxDirectory>> {
409 unreachable!(); }
411
412 fn set_parent(&self, _parent: Arc<FxDirectory>) {
413 }
415
416 fn open_count_add_one(&self) {
417 let old = self.open_count.fetch_add(1, Ordering::Relaxed);
418 assert!(old != PURGED && old != PURGED - 1);
419 }
420
421 fn open_count_sub_one(self: Arc<Self>) {
422 let old = self.open_count.fetch_sub(1, Ordering::Relaxed);
423 assert!(old & !PURGED > 0);
424 if old == PURGED + 1 {
425 let store = self.handle.store();
426 store
427 .filesystem()
428 .ensure_local()
429 .graveyard()
430 .queue_tombstone_object(store.store_object_id(), self.object_id());
431 }
432 }
433
434 fn object_descriptor(&self) -> ObjectDescriptor {
435 ObjectDescriptor::File
436 }
437
438 fn terminate(&self) {
439 if let Some(pager_state) = &self.pager_state {
440 pager_state.pager_packet_receiver_registration.stop_watching_for_zero_children();
441 }
442 }
443
444 fn mark_to_be_purged(self: Arc<Self>) {
445 let old = self.open_count.fetch_or(PURGED, Ordering::Relaxed);
446 assert!(old & PURGED == 0);
447 if old == 0 {
448 let store = self.handle.store();
449 store
450 .filesystem()
451 .ensure_local()
452 .graveyard()
453 .queue_tombstone_object(store.store_object_id(), self.object_id());
454 }
455 }
456}
457
458impl PagerBacked for FxBlob {
459 fn try_keep_open(self: Arc<Self>) -> Result<OpenedNode<Self>, Arc<Self>> {
460 let mut old = self.open_count.load(Ordering::Relaxed);
461 loop {
462 if old & !PURGED == 0 {
463 return Err(self);
464 }
465 assert!(old & !PURGED < PURGED - 1);
466 match self.open_count.compare_exchange_weak(
467 old,
468 old + 1,
469 Ordering::Relaxed,
470 Ordering::Relaxed,
471 ) {
472 Ok(_) => return Ok(OpenedNode(self)),
473 Err(new_value) => old = new_value,
474 }
475 }
476 }
477
478 fn pager(&self) -> &crate::pager::Pager {
479 self.handle.owner().pager()
480 }
481
482 fn pager_packet_receiver_registration(&self) -> &PagerPacketReceiverRegistration<Self> {
483 &self.pager_state().pager_packet_receiver_registration
484 }
485
486 fn vmo(&self) -> &zx::Vmo {
487 &self.pager_state().vmo
488 }
489
490 fn page_in(self: Arc<Self>, range: PageInRange<Self>) {
491 let read_ahead_size = if let Some(compression_info) = &self.pager_state().compression_info {
492 read_ahead_size_for_chunk_size(compression_info.chunk_size(), READ_AHEAD_SIZE)
493 } else {
494 READ_AHEAD_SIZE
495 };
496 default_page_in(self, range, read_ahead_size)
498 }
499
500 fn mark_dirty(self: Arc<Self>, _range: MarkDirtyRange<Self>) {
501 unreachable!();
502 }
503
504 fn on_zero_children(self: Arc<Self>) {
505 self.open_count_sub_one();
506 }
507
508 fn byte_size(&self) -> u64 {
509 self.uncompressed_size
510 }
511
512 async fn aligned_read(&self, range: Range<u64>) -> Result<Buffer<'_>, Error> {
513 ensure!(range.start < self.uncompressed_size, FxfsError::InvalidArgs);
516 self.record_page_fault_metric(&range);
517
518 let pager_state = self.pager_state();
519 let mut buffer = match pager_state.compression_info {
520 Some(_) => self.allocate_trusted_buffer(range.end - range.start).await,
521 None => self.allocate_buffer(range.end - range.start).await,
522 };
523 let unaligned_bytes =
524 (std::cmp::min(range.end, self.uncompressed_size) - range.start) as usize;
525 match &pager_state.compression_info {
526 None => self.read_blocks(range.start, buffer.as_mut()).await?,
527 Some(compression_info) => {
528 let compressed_offsets =
529 compression_info.compressed_range_for_uncompressed_range(&range)?;
530 let bs = self.handle.block_size();
531 let aligned = bs.align_range_outwards(&compressed_offsets).unwrap();
532 let mut compressed_buf = self.allocate_buffer(aligned.end - aligned.start).await;
533
534 let mut decompression_errors = 0;
535 loop {
536 try_join!(self.read_blocks(aligned.start, compressed_buf.as_mut()), async {
537 buffer
538 .allocator()
539 .buffer_source()
540 .commit_range(buffer.range())
541 .map_err(|e| e.into())
542 })
543 .with_context(|| {
544 format!(
545 "Failed to read compressed range {:?}, len {}",
546 aligned,
547 compression_info.compressed_size()
548 )
549 })?;
550 let compressed_buf_range = (compressed_offsets.start - aligned.start) as usize
551 ..(compressed_offsets.end - aligned.start) as usize;
552
553 let decompression_result = {
554 fxfs_trace::duration!("blob-decompress", "len" => unaligned_bytes);
555 compression_info.decompress(
556 compressed_buf.as_ptr_slice().subslice(compressed_buf_range),
557 buffer.subslice_mut(0..unaligned_bytes).try_as_mut_slice().unwrap(),
558 range.start,
559 )
560 };
561 match decompression_result {
562 Ok(()) => break,
563 Err(error) => {
564 record_decompression_error_crash_report(
565 &compressed_buf.to_vec(),
566 &range,
567 &compressed_offsets,
568 &self.merkle_root,
569 )
570 .await;
571 decompression_errors += 1;
572 if decompression_errors == 2 {
573 bail!(
574 anyhow!(FxfsError::IntegrityError)
575 .context(format!("Decompression error: {error:?}"))
576 );
577 } else {
578 warn!(error:?; "Decompression error; retrying");
579 }
580 }
581 }
582 } if decompression_errors > 0 {
584 info!("Read succeeded on second attempt");
585 }
586 }
587 };
588 buffer.subslice_mut(unaligned_bytes..buffer.len()).fill(0);
590 {
591 fxfs_trace::duration!("blob-verify", "len" => unaligned_bytes);
594 pager_state.merkle_verifier.verify_aligned(
595 range.start as usize,
596 buffer.as_ptr_slice().subslice(0..buffer.len()),
597 unaligned_bytes,
598 )?;
599 }
600 Ok(buffer)
601 }
602}
603
604fn set_vmo_name(vmo: &zx::Vmo, merkle_root: &Hash) {
605 let trimmed_merkle = &merkle_root.to_string()[0..BLOB_NAME_HASH_LENGTH];
606 let name = format!("{BLOB_NAME_PREFIX}{trimmed_merkle}");
607 let name = zx::Name::new(&name).unwrap();
608 vmo.set_name(&name).unwrap();
609}
610
611fn min_chunk_size(compression_info: &Option<CompressionInfo>) -> u64 {
612 if let Some(compression_info) = compression_info {
613 read_ahead_size_for_chunk_size(compression_info.chunk_size(), READ_AHEAD_SIZE)
614 } else {
615 READ_AHEAD_SIZE
616 }
617}
618
619fn read_ahead_size_for_chunk_size(chunk_size: u64, suggested_read_ahead_size: u64) -> u64 {
620 if chunk_size >= suggested_read_ahead_size {
621 chunk_size
622 } else {
623 round_down(suggested_read_ahead_size, chunk_size)
624 }
625}
626
627async fn record_decompression_error_crash_report(
628 compressed_buf: &[u8],
629 uncompressed_offsets: &Range<u64>,
630 compressed_offsets: &Range<u64>,
631 merkle_root: &Hash,
632) {
633 static DONE_ONCE: AtomicBool = AtomicBool::new(false);
634 if !DONE_ONCE.swap(true, Ordering::Relaxed) {
635 if let Ok(proxy) = connect_to_protocol::<fidl_fuchsia_feedback::CrashReporterMarker>() {
636 let size = compressed_buf.len() as u64;
637 let vmo = zx::Vmo::create(size).unwrap();
638 vmo.write(compressed_buf, 0).unwrap();
639 if let Err(e) = proxy
640 .file_report(CrashReport {
641 program_name: Some("fxfs".to_string()),
642 crash_signature: Some("fuchsia-fxfs-decompression_error".to_string()),
643 is_fatal: Some(false),
644 annotations: Some(vec![
645 Annotation {
646 key: "fxfs.range".to_string(),
647 value: format!("{:?}", uncompressed_offsets),
648 },
649 Annotation {
650 key: "fxfs.compressed_offsets".to_string(),
651 value: format!("{:?}", compressed_offsets),
652 },
653 Annotation {
654 key: "fxfs.merkle_root".to_string(),
655 value: format!("{}", merkle_root),
656 },
657 ]),
658 attachments: Some(vec![Attachment {
659 key: "fxfs_compressed_data".to_string(),
660 value: MemBuffer { vmo, size },
661 }]),
662 ..Default::default()
663 })
664 .await
665 {
666 error!(e:?; "Failed to file crash report");
667 } else {
668 warn!("Filed crash report for decompression error");
669 }
670 } else {
671 error!("Failed to connect to crash report service");
672 }
673 }
674}
675
676#[cfg(test)]
677mod tests {
678 use super::*;
679 use crate::fuchsia::fxblob::testing::{BlobFixture, new_blob_fixture};
680 use crate::fuchsia::pager::PageInRange;
681 use crate::fuchsia::testing::{TestFixture, TestFixtureOptions};
682 use crate::fxblob::testing::open_blob_fixture;
683 use assert_matches::assert_matches;
684 use blob_writer::BlobWriter;
685 use delivery_blob::compression::{ChunkedArchiveOptions, CompressionAlgorithm};
686 use delivery_blob::{CompressionMode, Type1Blob};
687 use fuchsia_async::epoch::Epoch;
688 use fuchsia_component_client::connect_to_protocol_at_dir_svc;
689 use fxfs::object_store::{HandleOptions, ObjectStore};
690 use fxfs_make_blob_image::FxBlobBuilder;
691 use storage_device::DeviceHolder;
692 use storage_device::fake_device::FakeDevice;
693
694 const BLOCK_SIZE: u64 = fuchsia_merkle::BLOCK_SIZE as u64;
695 const CHUNK_SIZE: usize = 32 * 1024;
696
697 #[fuchsia::test(threads = 10)]
698 async fn test_empty_blob() {
699 let fixture = new_blob_fixture().await;
700
701 let data = vec![];
702 let hash = fixture.write_blob(&data, CompressionMode::Never).await;
703 assert_eq!(fixture.read_blob(hash).await, data);
704
705 fixture.close().await;
706 }
707
708 #[fuchsia::test(threads = 10)]
709 async fn test_large_blob() {
710 let fixture = new_blob_fixture().await;
711
712 let data = vec![3; 3_000_000];
713 let hash = fixture.write_blob(&data, CompressionMode::Never).await;
714
715 assert_eq!(fixture.read_blob(hash).await, data);
716
717 fixture.close().await;
718 }
719
720 #[fuchsia::test(threads = 10)]
721 async fn test_get_multiple_mapping_extents() {
722 let fixture = new_blob_fixture().await;
723
724 {
725 let data = vec![0xff; 3_000_000];
726 let hash = fixture.write_blob(&data, CompressionMode::Never).await;
727 let name = format!("{}", hash);
728
729 let handle = fixture.get_blob_handle(&name).await;
730 let mut transaction =
731 handle.new_transaction().await.expect("failed to create transaction");
732 let mut buf = handle.allocate_buffer(8192).await;
733 buf.as_mut_ptr_slice().fill(0xaa);
734 handle
735 .txn_write(&mut transaction, 0, buf.as_ref())
736 .await
737 .expect("txn_write at offset 0 failed");
738 handle
739 .txn_write(&mut transaction, 16384, buf.as_ref())
740 .await
741 .expect("txn_write at offset 16384 failed");
742 transaction.commit().await.expect("failed to commit transaction");
743
744 let blob = fixture.get_blob(hash).await.expect("getting blob failed");
745 let extents = blob.get_mapping_extents().await.expect("get_mapping_extents failed");
746
747 assert!(!extents.merkle.is_empty(), "Expected at least one merkle extent");
748 assert!(
749 extents.data.len() > 1,
750 "Expected multiple data extents, got {}",
751 extents.data.len()
752 );
753
754 let overwrite_extent = extents
756 .data
757 .iter()
758 .find(|e| e.logical_range().start == 0)
759 .expect("extent at offset 0");
760 assert_eq!(
761 overwrite_extent.logical_range().end - overwrite_extent.logical_range().start,
762 8192
763 );
764 let overwrite_extent = extents
765 .data
766 .iter()
767 .find(|e| e.logical_range().start == 16384)
768 .expect("extent at offset 16384");
769 assert_eq!(
770 overwrite_extent.logical_range().end - overwrite_extent.logical_range().start,
771 8192
772 );
773 }
774
775 fixture.close().await;
776 }
777
778 #[fuchsia::test(threads = 10)]
779 async fn test_large_compressed_blob() {
780 let fixture = new_blob_fixture().await;
781
782 let data = vec![3; 3_000_000];
783 let hash = fixture.write_blob(&data, CompressionMode::Always).await;
784
785 assert_eq!(fixture.read_blob(hash).await, data);
786
787 fixture.close().await;
788 }
789
790 #[fuchsia::test(threads = 10)]
791 async fn test_non_page_aligned_blob() {
792 let fixture = new_blob_fixture().await;
793
794 let page_size = page_size().get() as usize;
795 let data = vec![0xffu8; page_size - 1];
796 let hash = fixture.write_blob(&data, CompressionMode::Never).await;
797 assert_eq!(fixture.read_blob(hash).await, data);
798
799 {
800 let vmo = fixture.get_blob_vmo(hash).await;
801 let mut buf = vec![0x11u8; page_size];
802 vmo.read(&mut buf[..], 0).expect("vmo read failed");
803 assert_eq!(data, buf[..data.len()]);
804 assert_eq!(buf[data.len()], 0);
806 }
807
808 fixture.close().await;
809 }
810
811 #[fuchsia::test(threads = 10)]
812 async fn test_blob_invalid_contents() {
813 let fixture = new_blob_fixture().await;
814
815 let data = vec![0xffu8; (READ_AHEAD_SIZE + BLOCK_SIZE) as usize];
816 let hash = fixture.write_blob(&data, CompressionMode::Never).await;
817 let name = format!("{}", hash);
818
819 {
820 let handle = fixture.get_blob_handle(&name).await;
822 let mut transaction =
823 handle.new_transaction().await.expect("failed to create transaction");
824 let mut buf = handle.allocate_buffer(BLOCK_SIZE as usize).await;
825 buf.fill(0);
826 handle
827 .txn_write(&mut transaction, READ_AHEAD_SIZE, buf.as_ref())
828 .await
829 .expect("txn_write failed");
830 transaction.commit().await.expect("failed to commit transaction");
831 }
832
833 {
834 let blob_vmo = fixture.get_blob_vmo(hash).await;
835 let mut buf = vec![0; BLOCK_SIZE as usize];
836 assert_matches!(blob_vmo.read(&mut buf[..], 0), Ok(_));
837 assert_matches!(
838 blob_vmo.read(&mut buf[..], READ_AHEAD_SIZE),
839 Err(zx::Status::IO_DATA_INTEGRITY)
840 );
841 }
842
843 fixture.close().await;
844 }
845
846 #[fuchsia::test(threads = 10)]
847 async fn test_blob_invalid_uncompressed_size() {
848 use fxfs::object_handle::WriteObjectHandle;
849
850 let fixture = new_blob_fixture().await;
851
852 {
855 let data = vec![0xaa; BLOCK_SIZE as usize];
856 let hash = fixture.write_blob(&data, CompressionMode::Never).await;
857 let handle = fixture.get_blob_handle(&hash.to_string()).await;
858 BlobMetadata { merkle_leaves: vec![hash.into()], format: BlobFormat::Uncompressed }
859 .write_to(&handle)
860 .await
861 .expect("write_to failed");
862 let err = fixture.get_blob(hash).await.err().expect("get_blob should fail");
863 assert_matches!(err.downcast_ref::<FxfsError>(), Some(FxfsError::IntegrityError));
864 }
865
866 {
869 let data = vec![0xbb; (BLOCK_SIZE * 2) as usize];
870 let hash = fixture.write_blob(&data, CompressionMode::Always).await;
871 let handle = fixture.get_blob_handle(&hash.to_string()).await;
872 let mut metadata = BlobMetadata::read_from(&handle).await.expect("read_from failed");
873 metadata.merkle_leaves.clear();
874 metadata.write_to(&handle).await.expect("write_to failed");
875 let err = fixture.get_blob(hash).await.err().expect("get_blob should fail");
876 assert_matches!(err.downcast_ref::<FxfsError>(), Some(FxfsError::IntegrityError));
877 }
878
879 {
883 let data = vec![0xcc; (BLOCK_SIZE * 2) as usize];
884 let hash = fixture.write_blob(&data, CompressionMode::Never).await;
885 let handle = fixture.get_blob_handle(&hash.to_string()).await;
886 handle.truncate(BLOCK_SIZE * 3).await.expect("truncate failed");
887 let err = fixture.get_blob(hash).await.err().expect("get_blob should fail");
888 assert_matches!(err.downcast_ref::<FxfsError>(), Some(FxfsError::IntegrityError));
889 }
890
891 {
894 let data = vec![0xdd; 100];
895 let hash = fixture.write_blob(&data, CompressionMode::Never).await;
896 let handle = fixture.get_blob_handle(&hash.to_string()).await;
897 handle.truncate(0).await.expect("truncate failed");
898 let err = fixture.get_blob(hash).await.err().expect("get_blob should fail");
899 assert_matches!(err.downcast_ref::<FxfsError>(), Some(FxfsError::IntegrityError));
900 }
901
902 {
905 let hash = fixture.write_blob(&[], CompressionMode::Never).await;
906 let handle = fixture.get_blob_handle(&hash.to_string()).await;
907 handle.truncate(100).await.expect("truncate failed");
908 let err = fixture.get_blob(hash).await.err().expect("get_blob should fail");
909 assert_matches!(err.downcast_ref::<FxfsError>(), Some(FxfsError::IntegrityError));
910 }
911
912 fixture.close().await;
913 }
914
915 #[fuchsia::test(threads = 10)]
916 async fn test_lz4_blob() {
917 let device = DeviceHolder::new(FakeDevice::new(16384, 512));
918 let blob_data = vec![0xAA; 68 * 1024];
919 let fxblob_builder = FxBlobBuilder::new(device).await.unwrap();
920 let blob = fxblob_builder
921 .generate_blob(blob_data.clone(), Some(CompressionAlgorithm::Lz4))
922 .unwrap();
923 let blob_hash = blob.hash();
924 fxblob_builder.install_blob(&blob).await.unwrap();
925 let device = fxblob_builder.finalize().await.unwrap().0;
926 device.reopen(false);
927 let fixture = open_blob_fixture(device).await;
928
929 assert_eq!(fixture.read_blob(blob_hash).await, blob_data);
930
931 fixture.close().await;
932 }
933
934 #[fuchsia::test(threads = 10)]
935 async fn test_blob_vmos_are_immutable() {
936 let fixture = new_blob_fixture().await;
937
938 let data = vec![0xffu8; 500];
939 let hash = fixture.write_blob(&data, CompressionMode::Never).await;
940 let blob_vmo = fixture.get_blob_vmo(hash).await;
941
942 assert_matches!(blob_vmo.set_size(20), Err(_));
944
945 assert_matches!(blob_vmo.write(b"overwrite", 0), Err(_));
947
948 assert_matches!(blob_vmo.set_stream_size(20), Err(_));
950
951 fixture.close().await;
952 }
953
954 const COMPRESSED_BLOB_CHUNK_SIZE: u64 = 32 * 1024;
955 const MAX_SMALL_OFFSET: u64 = u32::MAX as u64;
956 const ZSTD: CompressionAlgorithm = CompressionAlgorithm::Zstd;
957
958 #[fuchsia::test]
959 fn test_compression_info_offsets_must_start_with_zero() {
960 assert!(CompressionInfo::new(COMPRESSED_BLOB_CHUNK_SIZE, 100, &[], ZSTD).is_err());
961 assert!(CompressionInfo::new(COMPRESSED_BLOB_CHUNK_SIZE, 100, &[1], ZSTD).is_err());
962 assert!(CompressionInfo::new(COMPRESSED_BLOB_CHUNK_SIZE, 100, &[0], ZSTD).is_ok());
963 }
964
965 #[fuchsia::test]
966 fn test_compression_info_offsets_must_be_sorted() {
967 assert!(CompressionInfo::new(COMPRESSED_BLOB_CHUNK_SIZE, 100, &[0, 1, 2], ZSTD).is_ok());
968 assert!(CompressionInfo::new(COMPRESSED_BLOB_CHUNK_SIZE, 100, &[0, 2, 1], ZSTD).is_err());
969 assert!(CompressionInfo::new(COMPRESSED_BLOB_CHUNK_SIZE, 100, &[0, 1, 1], ZSTD).is_err());
970 }
971
972 #[fuchsia::test]
973 fn test_compression_info_compressed_range_for_uncompressed_range() {
974 fn check_compression_ranges(
975 offsets: &[u64],
976 compressed_size: u64,
977 expected_ranges: &[Range<u64>],
978 chunk_size: u64,
979 read_ahead_size: u64,
980 ) {
981 let compression_info =
982 CompressionInfo::new(chunk_size, compressed_size, offsets, ZSTD).unwrap();
983 for (i, expected_range) in expected_ranges.iter().enumerate() {
984 let i = i as u64;
985 let result = compression_info
986 .compressed_range_for_uncompressed_range(
987 &(i * read_ahead_size..(i + 1) * read_ahead_size),
988 )
989 .unwrap();
990 assert_eq!(&result, expected_range);
991 }
992 }
993 check_compression_ranges(
994 &[0, 10, 20, 30],
995 30,
996 &[0..10, 10..20, 20..30, 30..30],
997 COMPRESSED_BLOB_CHUNK_SIZE,
998 COMPRESSED_BLOB_CHUNK_SIZE,
999 );
1000 check_compression_ranges(
1001 &[0, 10, 20, 30],
1002 30,
1003 &[0..20, 20..30],
1004 COMPRESSED_BLOB_CHUNK_SIZE,
1005 COMPRESSED_BLOB_CHUNK_SIZE * 2,
1006 );
1007 check_compression_ranges(
1008 &[0, 10, 20, 30],
1009 30,
1010 &[0..30],
1011 COMPRESSED_BLOB_CHUNK_SIZE,
1012 COMPRESSED_BLOB_CHUNK_SIZE * 4,
1013 );
1014 check_compression_ranges(
1015 &[0, 10, 20, 30, MAX_SMALL_OFFSET + 10],
1016 MAX_SMALL_OFFSET + 10,
1017 &[0..MAX_SMALL_OFFSET + 10, MAX_SMALL_OFFSET + 10..MAX_SMALL_OFFSET + 10],
1018 COMPRESSED_BLOB_CHUNK_SIZE,
1019 COMPRESSED_BLOB_CHUNK_SIZE * 4,
1020 );
1021 check_compression_ranges(
1022 &[
1023 0,
1024 10,
1025 20,
1026 30,
1027 MAX_SMALL_OFFSET + 10,
1028 MAX_SMALL_OFFSET + 20,
1029 MAX_SMALL_OFFSET + 30,
1030 MAX_SMALL_OFFSET + 40,
1031 MAX_SMALL_OFFSET + 50,
1032 ],
1033 MAX_SMALL_OFFSET + 50,
1034 &[
1035 0..20,
1036 20..MAX_SMALL_OFFSET + 10,
1037 MAX_SMALL_OFFSET + 10..MAX_SMALL_OFFSET + 30,
1038 MAX_SMALL_OFFSET + 30..MAX_SMALL_OFFSET + 50,
1039 ],
1040 COMPRESSED_BLOB_CHUNK_SIZE,
1041 COMPRESSED_BLOB_CHUNK_SIZE * 2,
1042 );
1043 }
1044
1045 #[fuchsia::test]
1046 fn test_compression_info_compressed_range_for_uncompressed_range_errors() {
1047 let compression_info = CompressionInfo::new(
1048 COMPRESSED_BLOB_CHUNK_SIZE,
1049 MAX_SMALL_OFFSET + 50,
1050 &[
1051 0,
1052 10,
1053 20,
1054 30,
1055 MAX_SMALL_OFFSET + 10,
1056 MAX_SMALL_OFFSET + 20,
1057 MAX_SMALL_OFFSET + 30,
1058 MAX_SMALL_OFFSET + 40,
1059 MAX_SMALL_OFFSET + 50,
1060 ],
1061 ZSTD,
1062 )
1063 .unwrap();
1064
1065 assert!(
1067 compression_info
1068 .compressed_range_for_uncompressed_range(&(1..COMPRESSED_BLOB_CHUNK_SIZE),)
1069 .is_err()
1070 );
1071
1072 assert!(
1074 compression_info
1075 .compressed_range_for_uncompressed_range(
1076 &(COMPRESSED_BLOB_CHUNK_SIZE * 9..COMPRESSED_BLOB_CHUNK_SIZE * 12),
1077 )
1078 .is_err()
1079 );
1080
1081 assert!(
1083 compression_info
1084 .compressed_range_for_uncompressed_range(&(0..COMPRESSED_BLOB_CHUNK_SIZE + 1),)
1085 .is_err()
1086 );
1087 assert!(
1088 compression_info
1089 .compressed_range_for_uncompressed_range(&(0..COMPRESSED_BLOB_CHUNK_SIZE - 1),)
1090 .is_err()
1091 );
1092 assert!(
1093 compression_info
1094 .compressed_range_for_uncompressed_range(
1095 &(COMPRESSED_BLOB_CHUNK_SIZE..COMPRESSED_BLOB_CHUNK_SIZE * 2 + 1),
1096 )
1097 .is_err()
1098 );
1099 assert!(
1100 compression_info
1101 .compressed_range_for_uncompressed_range(
1102 &(COMPRESSED_BLOB_CHUNK_SIZE..COMPRESSED_BLOB_CHUNK_SIZE * 2 - 1),
1103 )
1104 .is_err()
1105 );
1106
1107 assert!(
1109 compression_info
1110 .compressed_range_for_uncompressed_range(
1111 &(COMPRESSED_BLOB_CHUNK_SIZE * 8..COMPRESSED_BLOB_CHUNK_SIZE * 8 + 4096),
1112 )
1113 .is_ok()
1114 );
1115 }
1116
1117 #[fuchsia::test]
1118 fn test_read_ahead_size_for_chunk_size() {
1119 assert_eq!(read_ahead_size_for_chunk_size(32 * 1024, 32 * 1024), 32 * 1024);
1120 assert_eq!(read_ahead_size_for_chunk_size(48 * 1024, 32 * 1024), 48 * 1024);
1121 assert_eq!(read_ahead_size_for_chunk_size(64 * 1024, 32 * 1024), 64 * 1024);
1122
1123 assert_eq!(read_ahead_size_for_chunk_size(32 * 1024, 64 * 1024), 64 * 1024);
1124 assert_eq!(read_ahead_size_for_chunk_size(48 * 1024, 64 * 1024), 48 * 1024);
1125 assert_eq!(read_ahead_size_for_chunk_size(64 * 1024, 64 * 1024), 64 * 1024);
1126 assert_eq!(read_ahead_size_for_chunk_size(96 * 1024, 64 * 1024), 96 * 1024);
1127
1128 assert_eq!(read_ahead_size_for_chunk_size(32 * 1024, 128 * 1024), 128 * 1024);
1129 assert_eq!(read_ahead_size_for_chunk_size(48 * 1024, 128 * 1024), 96 * 1024);
1130 assert_eq!(read_ahead_size_for_chunk_size(64 * 1024, 128 * 1024), 128 * 1024);
1131 assert_eq!(read_ahead_size_for_chunk_size(96 * 1024, 128 * 1024), 96 * 1024);
1132 }
1133
1134 fn build_compression_info(size: usize) -> (CompressionInfo, Vec<u8>, Vec<u8>) {
1135 let options =
1136 ChunkedArchiveOptions::V3 { compression_algorithm: CompressionAlgorithm::Lz4 };
1137 let mut compressor = options.compressor();
1138 let mut uncompressed_data = Vec::with_capacity(size);
1139 {
1140 let mut run_length = 1;
1141 let mut run_value: u8 = 0;
1142 while uncompressed_data.len() < size {
1143 uncompressed_data
1144 .resize(std::cmp::min(uncompressed_data.len() + run_length, size), run_value);
1145 run_length = (run_length + 1) % 19 + 1;
1146 run_value = (run_value + 1) % 17;
1147 }
1148 }
1149 let mut compressed_offsets = vec![0];
1150 let mut compressed_data = vec![];
1151 for chunk in uncompressed_data.chunks(CHUNK_SIZE) {
1152 let mut compressed_chunk = compressor.compress(chunk, 0).unwrap();
1153 compressed_data.append(&mut compressed_chunk);
1154 compressed_offsets.push(compressed_data.len() as u64);
1155 }
1156 compressed_offsets.pop();
1157 let compressed_size = compressed_data.len() as u64;
1158 (
1159 CompressionInfo::new(
1160 CHUNK_SIZE as u64,
1161 compressed_size,
1162 &compressed_offsets,
1163 CompressionAlgorithm::Lz4,
1164 )
1165 .unwrap(),
1166 compressed_data,
1167 uncompressed_data,
1168 )
1169 }
1170
1171 #[fuchsia::test]
1172 fn test_compression_info_decompress_single_chunk() {
1173 let (compression_info, compressed_data, uncompressed_data) =
1174 build_compression_info(CHUNK_SIZE);
1175 let mut decompressed_data = vec![0u8; CHUNK_SIZE + 1];
1176
1177 compression_info
1178 .decompress(&compressed_data[..], &mut decompressed_data[0..CHUNK_SIZE], 0)
1179 .expect("failed to decompress");
1180 assert_eq!(uncompressed_data, decompressed_data[0..CHUNK_SIZE]);
1181
1182 compression_info
1184 .decompress(&compressed_data[..], &mut decompressed_data[0..CHUNK_SIZE - 1], 0)
1185 .expect_err("decompression should fail");
1186
1187 compression_info
1189 .decompress(&compressed_data[..], &mut decompressed_data[0..CHUNK_SIZE - 1], 0)
1190 .expect_err("decompression should fail");
1191 }
1192
1193 #[fuchsia::test]
1194 fn test_compression_info_decompress_multiple_chunks() {
1195 fn slice_for_chunks<'a>(
1196 compressed_data: &'a [u8],
1197 compression_info: &CompressionInfo,
1198 chunks: Range<u64>,
1199 ) -> &'a [u8] {
1200 let range = compression_info
1201 .compressed_range_for_uncompressed_range(
1202 &(chunks.start * CHUNK_SIZE as u64..chunks.end * CHUNK_SIZE as u64),
1203 )
1204 .unwrap();
1205 &compressed_data[range.start as usize..range.end as usize]
1206 }
1207
1208 const BLOB_SIZE: usize = CHUNK_SIZE * 4 + 4096;
1209 let (compression_info, compressed_data, uncompressed_data) =
1210 build_compression_info(BLOB_SIZE);
1211 let mut decompressed_data = vec![0u8; BLOB_SIZE];
1212
1213 compression_info
1215 .decompress(&compressed_data[..], &mut decompressed_data[..], 0)
1216 .expect("failed to decompress");
1217 assert_eq!(uncompressed_data, decompressed_data);
1218
1219 compression_info
1221 .decompress(
1222 slice_for_chunks(&compressed_data, &compression_info, 0..4),
1223 &mut decompressed_data[0..CHUNK_SIZE * 4],
1224 0,
1225 )
1226 .expect("failed to decompress");
1227 assert_eq!(&uncompressed_data[0..CHUNK_SIZE], &decompressed_data[0..CHUNK_SIZE]);
1228
1229 compression_info
1231 .decompress(
1232 slice_for_chunks(&compressed_data, &compression_info, 0..4),
1233 &mut decompressed_data[0..CHUNK_SIZE * 4 - 1],
1234 0,
1235 )
1236 .expect_err("decompression should fail");
1237
1238 compression_info
1240 .decompress(
1241 slice_for_chunks(&compressed_data, &compression_info, 0..4),
1242 &mut decompressed_data[0..CHUNK_SIZE * 4 + 1],
1243 0,
1244 )
1245 .expect_err("decompression should fail");
1246
1247 let partial_chunk = slice_for_chunks(&compressed_data, &compression_info, 4..5);
1249 compression_info
1250 .decompress(partial_chunk, &mut decompressed_data[0..4096], CHUNK_SIZE as u64 * 4)
1251 .expect("failed to decompress");
1252 assert_eq!(&uncompressed_data[CHUNK_SIZE * 4..], &decompressed_data[0..4096]);
1253
1254 compression_info
1256 .decompress(partial_chunk, &mut decompressed_data[0..4095], CHUNK_SIZE as u64 * 4)
1257 .expect_err("decompression should fail");
1258
1259 compression_info
1261 .decompress(partial_chunk, &mut decompressed_data[0..4097], CHUNK_SIZE as u64 * 4)
1262 .expect_err("decompression should fail");
1263 }
1264
1265 #[fuchsia::test(threads = 10)]
1266 async fn test_refault_metric() {
1267 let fixture = new_blob_fixture().await;
1268 {
1269 let volume = fixture.volume().volume().clone();
1270 const FILE_SIZE: u64 = READ_AHEAD_SIZE * 4 - 4096;
1271 let data = vec![0xffu8; FILE_SIZE as usize];
1272 let hash = fixture.write_blob(&data, CompressionMode::Never).await;
1273
1274 let blob = fixture.get_opened_blob(hash).await.unwrap();
1275 assert_eq!(
1277 &blob.pager_state().chunks_supplied.to_vec(4),
1278 &[false, false, false, false]
1279 );
1280
1281 blob.pager_state().vmo.read_to_vec::<u8>(4096, 4096).unwrap();
1282
1283 assert_eq!(&blob.pager_state().chunks_supplied.to_vec(4), &[true, false, false, false]);
1284
1285 blob.pager_state()
1286 .vmo
1287 .read_to_vec::<u8>(READ_AHEAD_SIZE * 2 + 4096, READ_AHEAD_SIZE)
1288 .unwrap();
1289 assert_eq!(&blob.pager_state().chunks_supplied.to_vec(4), &[true, false, true, true]);
1290
1291 let pages_per_chunk = READ_AHEAD_SIZE / page_size();
1293 assert_eq!(volume.page_fault_counter().read_refaults(Ordering::SeqCst), 0);
1294 assert_eq!(
1295 volume.page_fault_counter().read_faults(Ordering::SeqCst),
1296 3 * pages_per_chunk - 1
1297 );
1298
1299 blob.clone().page_in(PageInRange::new(
1304 FILE_SIZE - READ_AHEAD_SIZE..FILE_SIZE,
1305 blob.dup(),
1306 Epoch::global().guard(),
1307 ));
1308 Epoch::global().barrier().await;
1309
1310 assert_eq!(
1313 volume.page_fault_counter().read_refaults(Ordering::SeqCst),
1314 2 * pages_per_chunk - 1
1315 );
1316 assert_eq!(
1317 volume.page_fault_counter().read_faults(Ordering::SeqCst),
1318 5 * pages_per_chunk - 2
1319 );
1320 }
1321
1322 fixture.close().await;
1323 }
1324
1325 #[fuchsia::test(threads = 10)]
1326 async fn test_driver_blob_paging_does_not_create_vmo() {
1327 let fixture = TestFixture::open(
1328 DeviceHolder::new(FakeDevice::new(16384, 512)),
1329 TestFixtureOptions {
1330 encrypted: false,
1331 as_blob: true,
1332 use_driver_blob_paging: true,
1333 ..Default::default()
1334 },
1335 )
1336 .await;
1337
1338 {
1339 let data = vec![0xabu8; 65_536];
1340 let hash = fixture.write_blob(&data, CompressionMode::Always).await;
1341
1342 let opened_blob = fixture.get_opened_blob(hash).await.expect("get_opened_blob failed");
1343 let old_object_id = opened_blob.object_id();
1344 assert!(!opened_blob.has_fxfs_pager());
1345 assert_eq!(opened_blob.byte_size(), data.len() as u64);
1346 assert!(opened_blob.stored_size().await.expect("stored_size failed") > 0);
1347 let extents =
1348 opened_blob.get_mapping_extents().await.expect("get_mapping_extents failed");
1349 assert!(!extents.data.is_empty());
1350 assert!(!extents.merkle.is_empty());
1351
1352 assert_eq!(
1355 opened_blob.create_child_vmo().expect_err("create_child_vmo succeeded"),
1356 Status::NOT_SUPPORTED
1357 );
1358
1359 let delivery_data = Type1Blob::generate(&data, CompressionMode::Never);
1362 let writer = fixture.create_blob(&hash.into(), true).await.expect("create_blob failed");
1363 let mut blob_writer = BlobWriter::create(writer, delivery_data.len() as u64)
1364 .await
1365 .expect("BlobWriter::create failed");
1366 blob_writer.write(&delivery_data).await.expect("blob_writer.write failed");
1367
1368 fixture.fs().graveyard().flush().await;
1369 assert!(
1370 ObjectStore::open_object(
1371 fixture.volume().volume(),
1372 old_object_id,
1373 HandleOptions::default(),
1374 None,
1375 )
1376 .await
1377 .is_ok(),
1378 "Overwritten blob object should remain alive while OpenedNode is held"
1379 );
1380
1381 drop(opened_blob);
1382 fixture.fs().graveyard().flush().await;
1383 assert_matches!(
1384 ObjectStore::open_object(
1385 fixture.volume().volume(),
1386 old_object_id,
1387 HandleOptions::default(),
1388 None,
1389 )
1390 .await
1391 .err()
1392 .expect("Overwritten blob object should be purged after OpenedNode is dropped")
1393 .downcast_ref::<FxfsError>(),
1394 Some(FxfsError::NotFound)
1395 );
1396
1397 let overwritten_blob =
1398 fixture.get_opened_blob(hash).await.expect("get_opened_blob after overwrite");
1399 assert!(!overwritten_blob.has_fxfs_pager());
1400 assert_eq!(overwritten_blob.byte_size(), data.len() as u64);
1401 assert_eq!(
1402 overwritten_blob.stored_size().await.expect("stored_size failed"),
1403 data.len() as u64
1404 );
1405 drop(overwritten_blob);
1406
1407 let blob_reader =
1409 connect_to_protocol_at_dir_svc::<fidl_fuchsia_fxfs::BlobReaderMarker>(
1410 fixture.volume_out_dir(),
1411 )
1412 .expect("connect_to_protocol_at_dir_svc failed");
1413 let vmo = blob_reader
1414 .get_vmo(&hash.into())
1415 .await
1416 .expect("get_vmo FIDL failed")
1417 .map_err(Status::err_from_raw)
1418 .expect("get_vmo failed");
1419 let mut read_buf = vec![0u8; data.len()];
1420 vmo.read(&mut read_buf, 0).expect("vmo read failed");
1421 assert_eq!(read_buf, data);
1422 }
1423
1424 fixture.close().await;
1425 }
1426}