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 zx::Status;
39
40pub struct BlobExtents {
45 pub data: Vec<MappingExtent>,
46 pub merkle: Vec<MappingExtent>,
47}
48
49const PURGED: usize = 1 << (usize::BITS - 1);
53
54const EMPTY_BLOB_HASH: Hash = Hash::from_array([
55 0x15, 0xec, 0x7b, 0xf0, 0xb5, 0x07, 0x32, 0xb4, 0x9f, 0x82, 0x28, 0xe0, 0x7d, 0x24, 0x36, 0x53,
56 0x38, 0xf9, 0xe3, 0xab, 0x99, 0x4b, 0x00, 0xaf, 0x08, 0xe5, 0xa3, 0xbf, 0xfe, 0x55, 0xfd, 0x8b,
57]);
58
59#[derive(ToWeakNode)]
61pub struct FxBlob {
62 handle: StoreObjectHandle<FxVolume>,
63 vmo: zx::Vmo,
64 open_count: AtomicUsize,
65 merkle_root: Hash,
66 merkle_verifier: ReadSizedMerkleVerifier,
67 compression_info: Option<CompressionInfo>,
68 uncompressed_size: u64, pager_packet_receiver_registration: Arc<PagerPacketReceiverRegistration<Self>>,
70 chunks_supplied: AtomicBitVec,
71}
72
73static_assertions::const_assert!(size_of::<FxBlob>() <= 192);
75
76impl FxBlob {
77 pub async fn new(
78 handle: StoreObjectHandle<FxVolume>,
79 merkle_root: Hash,
80 ) -> Result<Arc<Self>, Error> {
81 let stored_size =
82 handle.store().get_attribute_size(handle.object_id(), AttributeId::DATA).await?;
83 let metadata = BlobMetadata::read_from(&handle).await?;
84 let (uncompressed_size, compression_info) = match &metadata.format {
85 BlobFormat::Uncompressed => (stored_size, None),
86 BlobFormat::ChunkedZstd { uncompressed_size, chunk_size, compressed_offsets } => (
87 *uncompressed_size,
88 Some(CompressionInfo::new(
89 *chunk_size,
90 stored_size,
91 compressed_offsets,
92 CompressionAlgorithm::Zstd,
93 )?),
94 ),
95 BlobFormat::ChunkedLz4 { uncompressed_size, chunk_size, compressed_offsets } => (
96 *uncompressed_size,
97 Some(CompressionInfo::new(
98 *chunk_size,
99 stored_size,
100 compressed_offsets,
101 CompressionAlgorithm::Lz4,
102 )?),
103 ),
104 };
105
106 let expect_merkle_hashes = if uncompressed_size <= fuchsia_merkle::BLOCK_SIZE as u64 {
111 0
112 } else {
113 uncompressed_size.div_ceil(fuchsia_merkle::BLOCK_SIZE as u64)
114 };
115 ensure!(
116 metadata.merkle_leaves.len() as u64 == expect_merkle_hashes,
117 FxfsError::IntegrityError
118 );
119 ensure!(
121 (uncompressed_size == 0) == (merkle_root == EMPTY_BLOB_HASH),
122 FxfsError::IntegrityError
123 );
124 let merkle_verifier = metadata.into_merkle_verifier(merkle_root)?;
125
126 let min_chunk_size = min_chunk_size(&compression_info);
127 let merkle_verifier =
128 ReadSizedMerkleVerifier::new(merkle_verifier, min_chunk_size as usize)?;
129 let chunks_supplied = AtomicBitVec::new(uncompressed_size.div_ceil(min_chunk_size));
130
131 Ok(Arc::new_cyclic(|weak| {
132 let (vmo, pager_packet_receiver_registration) = handle
133 .owner()
134 .pager()
135 .create_vmo(weak.clone(), uncompressed_size, zx::VmoOptions::empty())
136 .unwrap();
137 set_vmo_name(&vmo, &merkle_root);
138 Self {
139 handle,
140 vmo,
141 open_count: AtomicUsize::new(0),
142 merkle_root,
143 merkle_verifier,
144 compression_info,
145 uncompressed_size,
146 pager_packet_receiver_registration: Arc::new(pager_packet_receiver_registration),
147 chunks_supplied,
148 }
149 }))
150 }
151
152 pub fn overwrite_me(
154 self: &Arc<Self>,
155 handle: DataObjectHandle<FxVolume>,
156 merkle_verifier: MerkleVerifier,
157 compression_info: Option<CompressionInfo>,
158 ) -> Arc<Self> {
159 let min_chunk_size = min_chunk_size(&compression_info);
160 let merkle_verifier =
161 ReadSizedMerkleVerifier::new(merkle_verifier, min_chunk_size as usize)
162 .expect("The chunk size should have been validated by the delivery blob parser");
163 let chunks_supplied = AtomicBitVec::new(self.uncompressed_size.div_ceil(min_chunk_size));
166 let vmo = self.vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap();
167
168 let new_blob = Arc::new(Self {
169 handle: handle.into_store_object_handle(),
170 vmo,
171 open_count: AtomicUsize::new(0),
172 merkle_root: self.merkle_root,
173 merkle_verifier,
174 compression_info,
175 uncompressed_size: self.uncompressed_size,
176 pager_packet_receiver_registration: self.pager_packet_receiver_registration.clone(),
177 chunks_supplied,
178 });
179
180 self.handle.owner().cache().remove(self.as_ref());
183
184 let receiver_lock =
187 self.pager_packet_receiver_registration.receiver().set_receiver(&new_blob);
188 if receiver_lock.is_strong() {
189 new_blob.open_count_add_one();
195 self.clone().open_count_sub_one();
196 }
197 new_blob
198 }
199
200 pub fn root(&self) -> Hash {
201 self.merkle_root
202 }
203
204 pub async fn get_mapping_extents(&self) -> Result<BlobExtents, Error> {
208 let tree = &self.handle.store().tree();
209 let layer_set = tree.layer_set();
210
211 let mut merger = layer_set.merger();
213 let stream = self.handle.extent_stream(&mut merger, AttributeId::DATA).await?;
214 let data = stream
216 .map(|res| {
217 let extent = res?;
218 MappingExtent::try_new(extent.logical_range(), Some(extent.device_range().start))
219 .map_err(|_| anyhow!(FxfsError::Inconsistent))
220 })
221 .try_collect()
222 .await?;
223
224 let mut merger = layer_set.merger();
226 let stream = self.handle.extent_stream(&mut merger, AttributeId::BLOB_METADATA).await?;
227 let merkle = stream
228 .map(|res| {
229 let extent = res?;
230 MappingExtent::try_new(extent.logical_range(), Some(extent.device_range().start))
231 .map_err(|_| anyhow!(FxfsError::Inconsistent))
232 })
233 .try_collect()
234 .await?;
235
236 Ok(BlobExtents { data, merkle })
237 }
238
239 pub async fn stored_size(&self) -> Result<u64, Error> {
241 self.handle.store().get_attribute_size(self.handle.object_id(), AttributeId::DATA).await
242 }
243
244 fn record_page_fault_metric(&self, range: &Range<u64>) {
245 let chunk_size: u64 = min_chunk_size(&self.compression_info);
246
247 let first_chunk = range.start / chunk_size;
248 let last_chunk = range.end.div_ceil(chunk_size);
250
251 let supplied_count = self.chunks_supplied.test_and_set_range(first_chunk, last_chunk);
252
253 if supplied_count > 0 {
254 let pages_per_chunk = chunk_size / zx::system_get_page_size() as u64;
257 self.handle
258 .owner()
259 .blob_resupplied_count()
260 .increment(supplied_count * pages_per_chunk, Ordering::Relaxed);
261 }
262 }
263
264 fn allocate_buffer(&self, size: u64) -> BufferFuture<'_> {
266 self.handle.store().device().allocate_buffer(size as usize)
267 }
268
269 fn allocate_trusted_buffer(&self, size: u64) -> BufferFuture<'_> {
272 self.handle.owner().blob_allocator().allocate_buffer(size as usize)
273 }
274
275 async fn read_blocks(&self, offset: u64, buf: MutableBufferRef<'_>) -> Result<(), Error> {
276 let fs = self.handle.store().filesystem();
277 let guard = fs
278 .lock_manager()
279 .read_lock(lock_keys![LockKey::object_attribute(
280 self.handle.store().store_object_id(),
281 self.handle.object_id(),
282 AttributeId::DATA,
283 )])
284 .await;
285 self.handle.read_aligned_unchecked(AttributeId::DATA, offset, buf, &guard).await
286 }
287}
288
289impl Drop for FxBlob {
290 fn drop(&mut self) {
291 let volume = self.handle.owner();
292 volume.cache().remove(self);
293 }
294}
295
296impl OpenedNode<FxBlob> {
297 pub fn create_child_vmo(&self) -> Result<zx::Vmo, Status> {
304 let blob = self.0.as_ref();
305 let child_vmo = blob.vmo.create_child(
306 zx::VmoChildOptions::REFERENCE | zx::VmoChildOptions::NO_WRITE,
307 0,
308 0,
309 )?;
310 if blob.handle.owner().pager().watch_for_zero_children(blob).map_err(map_to_status)? {
311 blob.open_count_add_one();
315 }
316 Ok(child_vmo)
317 }
318}
319
320impl FxNode for FxBlob {
321 fn object_id(&self) -> u64 {
322 self.handle.object_id()
323 }
324
325 fn parent(&self) -> Option<Arc<FxDirectory>> {
326 unreachable!(); }
328
329 fn set_parent(&self, _parent: Arc<FxDirectory>) {
330 }
332
333 fn open_count_add_one(&self) {
334 let old = self.open_count.fetch_add(1, Ordering::Relaxed);
335 assert!(old != PURGED && old != PURGED - 1);
336 }
337
338 fn open_count_sub_one(self: Arc<Self>) {
339 let old = self.open_count.fetch_sub(1, Ordering::Relaxed);
340 assert!(old & !PURGED > 0);
341 if old == PURGED + 1 {
342 let store = self.handle.store();
343 store
344 .filesystem()
345 .graveyard()
346 .queue_tombstone_object(store.store_object_id(), self.object_id());
347 }
348 }
349
350 fn object_descriptor(&self) -> ObjectDescriptor {
351 ObjectDescriptor::File
352 }
353
354 fn terminate(&self) {
355 self.pager_packet_receiver_registration.stop_watching_for_zero_children();
356 }
357
358 fn mark_to_be_purged(self: Arc<Self>) {
359 let old = self.open_count.fetch_or(PURGED, Ordering::Relaxed);
360 assert!(old & PURGED == 0);
361 if old == 0 {
362 let store = self.handle.store();
363 store
364 .filesystem()
365 .graveyard()
366 .queue_tombstone_object(store.store_object_id(), self.object_id());
367 }
368 }
369}
370
371impl PagerBacked for FxBlob {
372 fn try_keep_open(self: Arc<Self>) -> Result<OpenedNode<Self>, Arc<Self>> {
373 let mut old = self.open_count.load(Ordering::Relaxed);
374 loop {
375 if old & !PURGED == 0 {
376 return Err(self);
377 }
378 assert!(old & !PURGED < PURGED - 1);
379 match self.open_count.compare_exchange_weak(
380 old,
381 old + 1,
382 Ordering::Relaxed,
383 Ordering::Relaxed,
384 ) {
385 Ok(_) => return Ok(OpenedNode(self)),
386 Err(new_value) => old = new_value,
387 }
388 }
389 }
390
391 fn pager(&self) -> &crate::pager::Pager {
392 self.handle.owner().pager()
393 }
394
395 fn pager_packet_receiver_registration(&self) -> &PagerPacketReceiverRegistration<Self> {
396 &self.pager_packet_receiver_registration
397 }
398
399 fn vmo(&self) -> &zx::Vmo {
400 &self.vmo
401 }
402
403 fn page_in(self: Arc<Self>, range: PageInRange<Self>) {
404 let read_ahead_size = if let Some(compression_info) = &self.compression_info {
405 read_ahead_size_for_chunk_size(compression_info.chunk_size(), READ_AHEAD_SIZE)
406 } else {
407 READ_AHEAD_SIZE
408 };
409 default_page_in(self, range, read_ahead_size)
411 }
412
413 fn mark_dirty(self: Arc<Self>, _range: MarkDirtyRange<Self>) {
414 unreachable!();
415 }
416
417 fn on_zero_children(self: Arc<Self>) {
418 self.open_count_sub_one();
419 }
420
421 fn byte_size(&self) -> u64 {
422 self.uncompressed_size
423 }
424
425 async fn aligned_read(&self, range: Range<u64>) -> Result<Buffer<'_>, Error> {
426 ensure!(range.start < self.uncompressed_size, FxfsError::InvalidArgs);
429 self.record_page_fault_metric(&range);
430
431 let mut buffer = match self.compression_info {
432 Some(_) => self.allocate_trusted_buffer(range.end - range.start).await,
433 None => self.allocate_buffer(range.end - range.start).await,
434 };
435 let unaligned_bytes =
436 (std::cmp::min(range.end, self.uncompressed_size) - range.start) as usize;
437 match &self.compression_info {
438 None => self.read_blocks(range.start, buffer.as_mut()).await?,
439 Some(compression_info) => {
440 let compressed_offsets =
441 compression_info.compressed_range_for_uncompressed_range(&range)?;
442 let bs = self.handle.block_size();
443 let aligned = bs.align_range_outwards(&compressed_offsets).unwrap();
444 let mut compressed_buf = self.allocate_buffer(aligned.end - aligned.start).await;
445
446 let mut decompression_errors = 0;
447 loop {
448 try_join!(self.read_blocks(aligned.start, compressed_buf.as_mut()), async {
449 buffer
450 .allocator()
451 .buffer_source()
452 .commit_range(buffer.range())
453 .map_err(|e| e.into())
454 })
455 .with_context(|| {
456 format!(
457 "Failed to read compressed range {:?}, len {}",
458 aligned,
459 compression_info.compressed_size()
460 )
461 })?;
462 let compressed_buf_range = (compressed_offsets.start - aligned.start) as usize
463 ..(compressed_offsets.end - aligned.start) as usize;
464
465 let decompression_result = {
466 fxfs_trace::duration!("blob-decompress", "len" => unaligned_bytes);
467 compression_info.decompress(
468 compressed_buf.as_ptr_slice().subslice(compressed_buf_range),
469 buffer.subslice_mut(0..unaligned_bytes).try_as_mut_slice().unwrap(),
470 range.start,
471 )
472 };
473 match decompression_result {
474 Ok(()) => break,
475 Err(error) => {
476 record_decompression_error_crash_report(
477 &compressed_buf.to_vec(),
478 &range,
479 &compressed_offsets,
480 &self.merkle_root,
481 )
482 .await;
483 decompression_errors += 1;
484 if decompression_errors == 2 {
485 bail!(
486 anyhow!(FxfsError::IntegrityError)
487 .context(format!("Decompression error: {error:?}"))
488 );
489 } else {
490 warn!(error:?; "Decompression error; retrying");
491 }
492 }
493 }
494 } if decompression_errors > 0 {
496 info!("Read succeeded on second attempt");
497 }
498 }
499 };
500 buffer.subslice_mut(unaligned_bytes..buffer.len()).fill(0);
502 {
503 fxfs_trace::duration!("blob-verify", "len" => unaligned_bytes);
506 self.merkle_verifier.verify_aligned(
507 range.start as usize,
508 buffer.as_ptr_slice().subslice(0..buffer.len()),
509 unaligned_bytes,
510 )?;
511 }
512 Ok(buffer)
513 }
514}
515
516fn set_vmo_name(vmo: &zx::Vmo, merkle_root: &Hash) {
517 let trimmed_merkle = &merkle_root.to_string()[0..BLOB_NAME_HASH_LENGTH];
518 let name = format!("{BLOB_NAME_PREFIX}{trimmed_merkle}");
519 let name = zx::Name::new(&name).unwrap();
520 vmo.set_name(&name).unwrap();
521}
522
523fn min_chunk_size(compression_info: &Option<CompressionInfo>) -> u64 {
524 if let Some(compression_info) = compression_info {
525 read_ahead_size_for_chunk_size(compression_info.chunk_size(), READ_AHEAD_SIZE)
526 } else {
527 READ_AHEAD_SIZE
528 }
529}
530
531fn read_ahead_size_for_chunk_size(chunk_size: u64, suggested_read_ahead_size: u64) -> u64 {
532 if chunk_size >= suggested_read_ahead_size {
533 chunk_size
534 } else {
535 round_down(suggested_read_ahead_size, chunk_size)
536 }
537}
538
539async fn record_decompression_error_crash_report(
540 compressed_buf: &[u8],
541 uncompressed_offsets: &Range<u64>,
542 compressed_offsets: &Range<u64>,
543 merkle_root: &Hash,
544) {
545 static DONE_ONCE: AtomicBool = AtomicBool::new(false);
546 if !DONE_ONCE.swap(true, Ordering::Relaxed) {
547 if let Ok(proxy) = connect_to_protocol::<fidl_fuchsia_feedback::CrashReporterMarker>() {
548 let size = compressed_buf.len() as u64;
549 let vmo = zx::Vmo::create(size).unwrap();
550 vmo.write(compressed_buf, 0).unwrap();
551 if let Err(e) = proxy
552 .file_report(CrashReport {
553 program_name: Some("fxfs".to_string()),
554 crash_signature: Some("fuchsia-fxfs-decompression_error".to_string()),
555 is_fatal: Some(false),
556 annotations: Some(vec![
557 Annotation {
558 key: "fxfs.range".to_string(),
559 value: format!("{:?}", uncompressed_offsets),
560 },
561 Annotation {
562 key: "fxfs.compressed_offsets".to_string(),
563 value: format!("{:?}", compressed_offsets),
564 },
565 Annotation {
566 key: "fxfs.merkle_root".to_string(),
567 value: format!("{}", merkle_root),
568 },
569 ]),
570 attachments: Some(vec![Attachment {
571 key: "fxfs_compressed_data".to_string(),
572 value: MemBuffer { vmo, size },
573 }]),
574 ..Default::default()
575 })
576 .await
577 {
578 error!(e:?; "Failed to file crash report");
579 } else {
580 warn!("Filed crash report for decompression error");
581 }
582 } else {
583 error!("Failed to connect to crash report service");
584 }
585 }
586}
587
588#[cfg(test)]
589mod tests {
590 use super::*;
591 use crate::fuchsia::fxblob::testing::{BlobFixture, new_blob_fixture};
592 use crate::fuchsia::pager::PageInRange;
593 use crate::fxblob::testing::open_blob_fixture;
594 use assert_matches::assert_matches;
595 use delivery_blob::CompressionMode;
596 use delivery_blob::compression::{ChunkedArchiveOptions, CompressionAlgorithm};
597
598 use fuchsia_async::epoch::Epoch;
599 use fxfs_make_blob_image::FxBlobBuilder;
600 use storage_device::DeviceHolder;
601 use storage_device::fake_device::FakeDevice;
602 use storage_units::page_size;
603
604 const BLOCK_SIZE: u64 = fuchsia_merkle::BLOCK_SIZE as u64;
605 const CHUNK_SIZE: usize = 32 * 1024;
606
607 #[fuchsia::test(threads = 10)]
608 async fn test_empty_blob() {
609 let fixture = new_blob_fixture().await;
610
611 let data = vec![];
612 let hash = fixture.write_blob(&data, CompressionMode::Never).await;
613 assert_eq!(fixture.read_blob(hash).await, data);
614
615 fixture.close().await;
616 }
617
618 #[fuchsia::test(threads = 10)]
619 async fn test_large_blob() {
620 let fixture = new_blob_fixture().await;
621
622 let data = vec![3; 3_000_000];
623 let hash = fixture.write_blob(&data, CompressionMode::Never).await;
624
625 assert_eq!(fixture.read_blob(hash).await, data);
626
627 fixture.close().await;
628 }
629
630 #[fuchsia::test(threads = 10)]
631 async fn test_get_multiple_mapping_extents() {
632 let fixture = new_blob_fixture().await;
633
634 {
635 let data = vec![0xff; 3_000_000];
636 let hash = fixture.write_blob(&data, CompressionMode::Never).await;
637 let name = format!("{}", hash);
638
639 let handle = fixture.get_blob_handle(&name).await;
640 let mut transaction =
641 handle.new_transaction().await.expect("failed to create transaction");
642 let mut buf = handle.allocate_buffer(8192).await;
643 buf.as_mut_ptr_slice().fill(0xaa);
644 handle
645 .txn_write(&mut transaction, 0, buf.as_ref())
646 .await
647 .expect("txn_write at offset 0 failed");
648 handle
649 .txn_write(&mut transaction, 16384, buf.as_ref())
650 .await
651 .expect("txn_write at offset 16384 failed");
652 transaction.commit().await.expect("failed to commit transaction");
653
654 let blob = fixture.get_blob(hash).await.expect("getting blob failed");
655 let extents = blob.get_mapping_extents().await.expect("get_mapping_extents failed");
656
657 assert!(!extents.merkle.is_empty(), "Expected at least one merkle extent");
658 assert!(
659 extents.data.len() > 1,
660 "Expected multiple data extents, got {}",
661 extents.data.len()
662 );
663
664 let overwrite_extent = extents
666 .data
667 .iter()
668 .find(|e| e.logical_range().start == 0)
669 .expect("extent at offset 0");
670 assert_eq!(
671 overwrite_extent.logical_range().end - overwrite_extent.logical_range().start,
672 8192
673 );
674 let overwrite_extent = extents
675 .data
676 .iter()
677 .find(|e| e.logical_range().start == 16384)
678 .expect("extent at offset 16384");
679 assert_eq!(
680 overwrite_extent.logical_range().end - overwrite_extent.logical_range().start,
681 8192
682 );
683 }
684
685 fixture.close().await;
686 }
687
688 #[fuchsia::test(threads = 10)]
689 async fn test_large_compressed_blob() {
690 let fixture = new_blob_fixture().await;
691
692 let data = vec![3; 3_000_000];
693 let hash = fixture.write_blob(&data, CompressionMode::Always).await;
694
695 assert_eq!(fixture.read_blob(hash).await, data);
696
697 fixture.close().await;
698 }
699
700 #[fuchsia::test(threads = 10)]
701 async fn test_non_page_aligned_blob() {
702 let fixture = new_blob_fixture().await;
703
704 let page_size = page_size().get() as usize;
705 let data = vec![0xffu8; page_size - 1];
706 let hash = fixture.write_blob(&data, CompressionMode::Never).await;
707 assert_eq!(fixture.read_blob(hash).await, data);
708
709 {
710 let vmo = fixture.get_blob_vmo(hash).await;
711 let mut buf = vec![0x11u8; page_size];
712 vmo.read(&mut buf[..], 0).expect("vmo read failed");
713 assert_eq!(data, buf[..data.len()]);
714 assert_eq!(buf[data.len()], 0);
716 }
717
718 fixture.close().await;
719 }
720
721 #[fuchsia::test(threads = 10)]
722 async fn test_blob_invalid_contents() {
723 let fixture = new_blob_fixture().await;
724
725 let data = vec![0xffu8; (READ_AHEAD_SIZE + BLOCK_SIZE) as usize];
726 let hash = fixture.write_blob(&data, CompressionMode::Never).await;
727 let name = format!("{}", hash);
728
729 {
730 let handle = fixture.get_blob_handle(&name).await;
732 let mut transaction =
733 handle.new_transaction().await.expect("failed to create transaction");
734 let mut buf = handle.allocate_buffer(BLOCK_SIZE as usize).await;
735 buf.fill(0);
736 handle
737 .txn_write(&mut transaction, READ_AHEAD_SIZE, buf.as_ref())
738 .await
739 .expect("txn_write failed");
740 transaction.commit().await.expect("failed to commit transaction");
741 }
742
743 {
744 let blob_vmo = fixture.get_blob_vmo(hash).await;
745 let mut buf = vec![0; BLOCK_SIZE as usize];
746 assert_matches!(blob_vmo.read(&mut buf[..], 0), Ok(_));
747 assert_matches!(
748 blob_vmo.read(&mut buf[..], READ_AHEAD_SIZE),
749 Err(zx::Status::IO_DATA_INTEGRITY)
750 );
751 }
752
753 fixture.close().await;
754 }
755
756 #[fuchsia::test(threads = 10)]
757 async fn test_blob_invalid_uncompressed_size() {
758 use fxfs::object_handle::WriteObjectHandle;
759
760 let fixture = new_blob_fixture().await;
761
762 {
765 let data = vec![0xaa; BLOCK_SIZE as usize];
766 let hash = fixture.write_blob(&data, CompressionMode::Never).await;
767 let handle = fixture.get_blob_handle(&hash.to_string()).await;
768 BlobMetadata { merkle_leaves: vec![hash.into()], format: BlobFormat::Uncompressed }
769 .write_to(&handle)
770 .await
771 .expect("write_to failed");
772 let err = fixture.get_blob(hash).await.err().expect("get_blob should fail");
773 assert_matches!(err.downcast_ref::<FxfsError>(), Some(FxfsError::IntegrityError));
774 }
775
776 {
779 let data = vec![0xbb; (BLOCK_SIZE * 2) as usize];
780 let hash = fixture.write_blob(&data, CompressionMode::Always).await;
781 let handle = fixture.get_blob_handle(&hash.to_string()).await;
782 let mut metadata = BlobMetadata::read_from(&handle).await.expect("read_from failed");
783 metadata.merkle_leaves.clear();
784 metadata.write_to(&handle).await.expect("write_to failed");
785 let err = fixture.get_blob(hash).await.err().expect("get_blob should fail");
786 assert_matches!(err.downcast_ref::<FxfsError>(), Some(FxfsError::IntegrityError));
787 }
788
789 {
793 let data = vec![0xcc; (BLOCK_SIZE * 2) as usize];
794 let hash = fixture.write_blob(&data, CompressionMode::Never).await;
795 let handle = fixture.get_blob_handle(&hash.to_string()).await;
796 handle.truncate(BLOCK_SIZE * 3).await.expect("truncate failed");
797 let err = fixture.get_blob(hash).await.err().expect("get_blob should fail");
798 assert_matches!(err.downcast_ref::<FxfsError>(), Some(FxfsError::IntegrityError));
799 }
800
801 {
804 let data = vec![0xdd; 100];
805 let hash = fixture.write_blob(&data, CompressionMode::Never).await;
806 let handle = fixture.get_blob_handle(&hash.to_string()).await;
807 handle.truncate(0).await.expect("truncate failed");
808 let err = fixture.get_blob(hash).await.err().expect("get_blob should fail");
809 assert_matches!(err.downcast_ref::<FxfsError>(), Some(FxfsError::IntegrityError));
810 }
811
812 {
815 let hash = fixture.write_blob(&[], CompressionMode::Never).await;
816 let handle = fixture.get_blob_handle(&hash.to_string()).await;
817 handle.truncate(100).await.expect("truncate failed");
818 let err = fixture.get_blob(hash).await.err().expect("get_blob should fail");
819 assert_matches!(err.downcast_ref::<FxfsError>(), Some(FxfsError::IntegrityError));
820 }
821
822 fixture.close().await;
823 }
824
825 #[fuchsia::test(threads = 10)]
826 async fn test_lz4_blob() {
827 let device = DeviceHolder::new(FakeDevice::new(16384, 512));
828 let blob_data = vec![0xAA; 68 * 1024];
829 let fxblob_builder = FxBlobBuilder::new(device).await.unwrap();
830 let blob = fxblob_builder
831 .generate_blob(blob_data.clone(), Some(CompressionAlgorithm::Lz4))
832 .unwrap();
833 let blob_hash = blob.hash();
834 fxblob_builder.install_blob(&blob).await.unwrap();
835 let device = fxblob_builder.finalize().await.unwrap().0;
836 device.reopen(false);
837 let fixture = open_blob_fixture(device).await;
838
839 assert_eq!(fixture.read_blob(blob_hash).await, blob_data);
840
841 fixture.close().await;
842 }
843
844 #[fuchsia::test(threads = 10)]
845 async fn test_blob_vmos_are_immutable() {
846 let fixture = new_blob_fixture().await;
847
848 let data = vec![0xffu8; 500];
849 let hash = fixture.write_blob(&data, CompressionMode::Never).await;
850 let blob_vmo = fixture.get_blob_vmo(hash).await;
851
852 assert_matches!(blob_vmo.set_size(20), Err(_));
854
855 assert_matches!(blob_vmo.write(b"overwrite", 0), Err(_));
857
858 assert_matches!(blob_vmo.set_stream_size(20), Err(_));
860
861 fixture.close().await;
862 }
863
864 const COMPRESSED_BLOB_CHUNK_SIZE: u64 = 32 * 1024;
865 const MAX_SMALL_OFFSET: u64 = u32::MAX as u64;
866 const ZSTD: CompressionAlgorithm = CompressionAlgorithm::Zstd;
867
868 #[fuchsia::test]
869 fn test_compression_info_offsets_must_start_with_zero() {
870 assert!(CompressionInfo::new(COMPRESSED_BLOB_CHUNK_SIZE, 100, &[], ZSTD).is_err());
871 assert!(CompressionInfo::new(COMPRESSED_BLOB_CHUNK_SIZE, 100, &[1], ZSTD).is_err());
872 assert!(CompressionInfo::new(COMPRESSED_BLOB_CHUNK_SIZE, 100, &[0], ZSTD).is_ok());
873 }
874
875 #[fuchsia::test]
876 fn test_compression_info_offsets_must_be_sorted() {
877 assert!(CompressionInfo::new(COMPRESSED_BLOB_CHUNK_SIZE, 100, &[0, 1, 2], ZSTD).is_ok());
878 assert!(CompressionInfo::new(COMPRESSED_BLOB_CHUNK_SIZE, 100, &[0, 2, 1], ZSTD).is_err());
879 assert!(CompressionInfo::new(COMPRESSED_BLOB_CHUNK_SIZE, 100, &[0, 1, 1], ZSTD).is_err());
880 }
881
882 #[fuchsia::test]
883 fn test_compression_info_compressed_range_for_uncompressed_range() {
884 fn check_compression_ranges(
885 offsets: &[u64],
886 compressed_size: u64,
887 expected_ranges: &[Range<u64>],
888 chunk_size: u64,
889 read_ahead_size: u64,
890 ) {
891 let compression_info =
892 CompressionInfo::new(chunk_size, compressed_size, offsets, ZSTD).unwrap();
893 for (i, expected_range) in expected_ranges.iter().enumerate() {
894 let i = i as u64;
895 let result = compression_info
896 .compressed_range_for_uncompressed_range(
897 &(i * read_ahead_size..(i + 1) * read_ahead_size),
898 )
899 .unwrap();
900 assert_eq!(&result, expected_range);
901 }
902 }
903 check_compression_ranges(
904 &[0, 10, 20, 30],
905 30,
906 &[0..10, 10..20, 20..30, 30..30],
907 COMPRESSED_BLOB_CHUNK_SIZE,
908 COMPRESSED_BLOB_CHUNK_SIZE,
909 );
910 check_compression_ranges(
911 &[0, 10, 20, 30],
912 30,
913 &[0..20, 20..30],
914 COMPRESSED_BLOB_CHUNK_SIZE,
915 COMPRESSED_BLOB_CHUNK_SIZE * 2,
916 );
917 check_compression_ranges(
918 &[0, 10, 20, 30],
919 30,
920 &[0..30],
921 COMPRESSED_BLOB_CHUNK_SIZE,
922 COMPRESSED_BLOB_CHUNK_SIZE * 4,
923 );
924 check_compression_ranges(
925 &[0, 10, 20, 30, MAX_SMALL_OFFSET + 10],
926 MAX_SMALL_OFFSET + 10,
927 &[0..MAX_SMALL_OFFSET + 10, MAX_SMALL_OFFSET + 10..MAX_SMALL_OFFSET + 10],
928 COMPRESSED_BLOB_CHUNK_SIZE,
929 COMPRESSED_BLOB_CHUNK_SIZE * 4,
930 );
931 check_compression_ranges(
932 &[
933 0,
934 10,
935 20,
936 30,
937 MAX_SMALL_OFFSET + 10,
938 MAX_SMALL_OFFSET + 20,
939 MAX_SMALL_OFFSET + 30,
940 MAX_SMALL_OFFSET + 40,
941 MAX_SMALL_OFFSET + 50,
942 ],
943 MAX_SMALL_OFFSET + 50,
944 &[
945 0..20,
946 20..MAX_SMALL_OFFSET + 10,
947 MAX_SMALL_OFFSET + 10..MAX_SMALL_OFFSET + 30,
948 MAX_SMALL_OFFSET + 30..MAX_SMALL_OFFSET + 50,
949 ],
950 COMPRESSED_BLOB_CHUNK_SIZE,
951 COMPRESSED_BLOB_CHUNK_SIZE * 2,
952 );
953 }
954
955 #[fuchsia::test]
956 fn test_compression_info_compressed_range_for_uncompressed_range_errors() {
957 let compression_info = CompressionInfo::new(
958 COMPRESSED_BLOB_CHUNK_SIZE,
959 MAX_SMALL_OFFSET + 50,
960 &[
961 0,
962 10,
963 20,
964 30,
965 MAX_SMALL_OFFSET + 10,
966 MAX_SMALL_OFFSET + 20,
967 MAX_SMALL_OFFSET + 30,
968 MAX_SMALL_OFFSET + 40,
969 MAX_SMALL_OFFSET + 50,
970 ],
971 ZSTD,
972 )
973 .unwrap();
974
975 assert!(
977 compression_info
978 .compressed_range_for_uncompressed_range(&(1..COMPRESSED_BLOB_CHUNK_SIZE),)
979 .is_err()
980 );
981
982 assert!(
984 compression_info
985 .compressed_range_for_uncompressed_range(
986 &(COMPRESSED_BLOB_CHUNK_SIZE * 9..COMPRESSED_BLOB_CHUNK_SIZE * 12),
987 )
988 .is_err()
989 );
990
991 assert!(
993 compression_info
994 .compressed_range_for_uncompressed_range(&(0..COMPRESSED_BLOB_CHUNK_SIZE + 1),)
995 .is_err()
996 );
997 assert!(
998 compression_info
999 .compressed_range_for_uncompressed_range(&(0..COMPRESSED_BLOB_CHUNK_SIZE - 1),)
1000 .is_err()
1001 );
1002 assert!(
1003 compression_info
1004 .compressed_range_for_uncompressed_range(
1005 &(COMPRESSED_BLOB_CHUNK_SIZE..COMPRESSED_BLOB_CHUNK_SIZE * 2 + 1),
1006 )
1007 .is_err()
1008 );
1009 assert!(
1010 compression_info
1011 .compressed_range_for_uncompressed_range(
1012 &(COMPRESSED_BLOB_CHUNK_SIZE..COMPRESSED_BLOB_CHUNK_SIZE * 2 - 1),
1013 )
1014 .is_err()
1015 );
1016
1017 assert!(
1019 compression_info
1020 .compressed_range_for_uncompressed_range(
1021 &(COMPRESSED_BLOB_CHUNK_SIZE * 8..COMPRESSED_BLOB_CHUNK_SIZE * 8 + 4096),
1022 )
1023 .is_ok()
1024 );
1025 }
1026
1027 #[fuchsia::test]
1028 fn test_read_ahead_size_for_chunk_size() {
1029 assert_eq!(read_ahead_size_for_chunk_size(32 * 1024, 32 * 1024), 32 * 1024);
1030 assert_eq!(read_ahead_size_for_chunk_size(48 * 1024, 32 * 1024), 48 * 1024);
1031 assert_eq!(read_ahead_size_for_chunk_size(64 * 1024, 32 * 1024), 64 * 1024);
1032
1033 assert_eq!(read_ahead_size_for_chunk_size(32 * 1024, 64 * 1024), 64 * 1024);
1034 assert_eq!(read_ahead_size_for_chunk_size(48 * 1024, 64 * 1024), 48 * 1024);
1035 assert_eq!(read_ahead_size_for_chunk_size(64 * 1024, 64 * 1024), 64 * 1024);
1036 assert_eq!(read_ahead_size_for_chunk_size(96 * 1024, 64 * 1024), 96 * 1024);
1037
1038 assert_eq!(read_ahead_size_for_chunk_size(32 * 1024, 128 * 1024), 128 * 1024);
1039 assert_eq!(read_ahead_size_for_chunk_size(48 * 1024, 128 * 1024), 96 * 1024);
1040 assert_eq!(read_ahead_size_for_chunk_size(64 * 1024, 128 * 1024), 128 * 1024);
1041 assert_eq!(read_ahead_size_for_chunk_size(96 * 1024, 128 * 1024), 96 * 1024);
1042 }
1043
1044 fn build_compression_info(size: usize) -> (CompressionInfo, Vec<u8>, Vec<u8>) {
1045 let options =
1046 ChunkedArchiveOptions::V3 { compression_algorithm: CompressionAlgorithm::Lz4 };
1047 let mut compressor = options.compressor();
1048 let mut uncompressed_data = Vec::with_capacity(size);
1049 {
1050 let mut run_length = 1;
1051 let mut run_value: u8 = 0;
1052 while uncompressed_data.len() < size {
1053 uncompressed_data
1054 .resize(std::cmp::min(uncompressed_data.len() + run_length, size), run_value);
1055 run_length = (run_length + 1) % 19 + 1;
1056 run_value = (run_value + 1) % 17;
1057 }
1058 }
1059 let mut compressed_offsets = vec![0];
1060 let mut compressed_data = vec![];
1061 for chunk in uncompressed_data.chunks(CHUNK_SIZE) {
1062 let mut compressed_chunk = compressor.compress(chunk, 0).unwrap();
1063 compressed_data.append(&mut compressed_chunk);
1064 compressed_offsets.push(compressed_data.len() as u64);
1065 }
1066 compressed_offsets.pop();
1067 let compressed_size = compressed_data.len() as u64;
1068 (
1069 CompressionInfo::new(
1070 CHUNK_SIZE as u64,
1071 compressed_size,
1072 &compressed_offsets,
1073 CompressionAlgorithm::Lz4,
1074 )
1075 .unwrap(),
1076 compressed_data,
1077 uncompressed_data,
1078 )
1079 }
1080
1081 #[fuchsia::test]
1082 fn test_compression_info_decompress_single_chunk() {
1083 let (compression_info, compressed_data, uncompressed_data) =
1084 build_compression_info(CHUNK_SIZE);
1085 let mut decompressed_data = vec![0u8; CHUNK_SIZE + 1];
1086
1087 compression_info
1088 .decompress(&compressed_data[..], &mut decompressed_data[0..CHUNK_SIZE], 0)
1089 .expect("failed to decompress");
1090 assert_eq!(uncompressed_data, decompressed_data[0..CHUNK_SIZE]);
1091
1092 compression_info
1094 .decompress(&compressed_data[..], &mut decompressed_data[0..CHUNK_SIZE - 1], 0)
1095 .expect_err("decompression should fail");
1096
1097 compression_info
1099 .decompress(&compressed_data[..], &mut decompressed_data[0..CHUNK_SIZE - 1], 0)
1100 .expect_err("decompression should fail");
1101 }
1102
1103 #[fuchsia::test]
1104 fn test_compression_info_decompress_multiple_chunks() {
1105 fn slice_for_chunks<'a>(
1106 compressed_data: &'a [u8],
1107 compression_info: &CompressionInfo,
1108 chunks: Range<u64>,
1109 ) -> &'a [u8] {
1110 let range = compression_info
1111 .compressed_range_for_uncompressed_range(
1112 &(chunks.start * CHUNK_SIZE as u64..chunks.end * CHUNK_SIZE as u64),
1113 )
1114 .unwrap();
1115 &compressed_data[range.start as usize..range.end as usize]
1116 }
1117
1118 const BLOB_SIZE: usize = CHUNK_SIZE * 4 + 4096;
1119 let (compression_info, compressed_data, uncompressed_data) =
1120 build_compression_info(BLOB_SIZE);
1121 let mut decompressed_data = vec![0u8; BLOB_SIZE];
1122
1123 compression_info
1125 .decompress(&compressed_data[..], &mut decompressed_data[..], 0)
1126 .expect("failed to decompress");
1127 assert_eq!(uncompressed_data, decompressed_data);
1128
1129 compression_info
1131 .decompress(
1132 slice_for_chunks(&compressed_data, &compression_info, 0..4),
1133 &mut decompressed_data[0..CHUNK_SIZE * 4],
1134 0,
1135 )
1136 .expect("failed to decompress");
1137 assert_eq!(&uncompressed_data[0..CHUNK_SIZE], &decompressed_data[0..CHUNK_SIZE]);
1138
1139 compression_info
1141 .decompress(
1142 slice_for_chunks(&compressed_data, &compression_info, 0..4),
1143 &mut decompressed_data[0..CHUNK_SIZE * 4 - 1],
1144 0,
1145 )
1146 .expect_err("decompression should fail");
1147
1148 compression_info
1150 .decompress(
1151 slice_for_chunks(&compressed_data, &compression_info, 0..4),
1152 &mut decompressed_data[0..CHUNK_SIZE * 4 + 1],
1153 0,
1154 )
1155 .expect_err("decompression should fail");
1156
1157 let partial_chunk = slice_for_chunks(&compressed_data, &compression_info, 4..5);
1159 compression_info
1160 .decompress(partial_chunk, &mut decompressed_data[0..4096], CHUNK_SIZE as u64 * 4)
1161 .expect("failed to decompress");
1162 assert_eq!(&uncompressed_data[CHUNK_SIZE * 4..], &decompressed_data[0..4096]);
1163
1164 compression_info
1166 .decompress(partial_chunk, &mut decompressed_data[0..4095], CHUNK_SIZE as u64 * 4)
1167 .expect_err("decompression should fail");
1168
1169 compression_info
1171 .decompress(partial_chunk, &mut decompressed_data[0..4097], CHUNK_SIZE as u64 * 4)
1172 .expect_err("decompression should fail");
1173 }
1174
1175 #[fuchsia::test(threads = 10)]
1176 async fn test_refault_metric() {
1177 let fixture = new_blob_fixture().await;
1178 {
1179 let volume = fixture.volume().volume().clone();
1180 const FILE_SIZE: u64 = READ_AHEAD_SIZE * 4 - 4096;
1181 let data = vec![0xffu8; FILE_SIZE as usize];
1182 let hash = fixture.write_blob(&data, CompressionMode::Never).await;
1183
1184 let blob = fixture.get_opened_blob(hash).await.unwrap();
1185 assert_eq!(blob.chunks_supplied.len(), 4);
1186 assert_eq!(&blob.chunks_supplied.get(), &[false, false, false, false]);
1188
1189 blob.vmo.read_to_vec::<u8>(4096, 4096).unwrap();
1190
1191 assert_eq!(&blob.chunks_supplied.get(), &[true, false, false, false]);
1192
1193 blob.vmo.read_to_vec::<u8>(READ_AHEAD_SIZE * 2 + 4096, READ_AHEAD_SIZE).unwrap();
1194 assert_eq!(&blob.chunks_supplied.get(), &[true, false, true, true]);
1195
1196 assert_eq!(volume.blob_resupplied_count().read(Ordering::SeqCst), 0);
1198
1199 blob.clone().page_in(PageInRange::new(
1204 FILE_SIZE - READ_AHEAD_SIZE..FILE_SIZE,
1205 blob.dup(),
1206 Epoch::global().guard(),
1207 ));
1208 Epoch::global().barrier().await;
1209
1210 let pages_per_chunk = READ_AHEAD_SIZE / zx::system_get_page_size() as u64;
1212 assert_eq!(volume.blob_resupplied_count().read(Ordering::SeqCst), 2 * pages_per_chunk);
1213 }
1214
1215 fixture.close().await;
1216 }
1217}