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