Skip to main content

fxfs_platform/fuchsia/fxblob/
blob.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! This module contains the [`FxBlob`] node type used to represent an immutable blob persisted to
6//! disk which can be read back.
7
8use 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
40// When the top bit of the open count is set, it means the file has been deleted and when the count
41// drops to zero, it will be tombstoned.  Once it has dropped to zero, it cannot be opened again
42// (assertions will fire).
43const PURGED: usize = 1 << (usize::BITS - 1);
44
45/// Represents an immutable blob stored on Fxfs with associated an merkle tree.
46#[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, // always set.
55    pager_packet_receiver_registration: Arc<PagerPacketReceiverRegistration<Self>>,
56    chunks_supplied: AtomicBitVec,
57}
58
59// Fuchsia can have many open blobs at once. The size of FxBlob is important.
60static_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    /// Returns the new blob.
120    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        // The chunk size may have changed between the old blob and the new blob. Preserving the
131        // chunks supplied bits isn't important.
132        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        // We have tests that rely on the cache being purged and there are races where the
148        // `FxBlob::drop` isn't called early enough, which can make the test flaky.
149        self.handle.owner().cache().remove(self.as_ref());
150
151        // Lock must be held until the open counts is incremented to prevent concurrent handling of
152        // zero children signals.
153        let receiver_lock =
154            self.pager_packet_receiver_registration.receiver().set_receiver(&new_blob);
155        if receiver_lock.is_strong() {
156            // If there was a strong moved between them, then the counts exchange as well. It is
157            // only important that the increment happen under the lock as it may handle the next
158            // zero children signal, no new requests can now go to the old blob, and because
159            // existing requests hold an open count reference using `try_keep_open` for the duration
160            // of the request, we can immediately decrement the open count of the old blob.
161            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    /// Returns the extents mapping logical offsets to device offsets for both merkle and data
172    /// attributes. Returns `(merkle_extents, data_extents)`, where each `MappingExtent` maps a
173    /// logical byte range to a physical device offset. Extents are sorted by logical offset and
174    /// non-overlapping.
175    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        // Query data extents
182        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        // Query Merkle extents
192        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        // The end of the range may not be chunk aligned if it's the last chunk.
209        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    /// Creates a read-only child VMO for this blob backed by the pager. The blob cannot be purged
248    /// until all child VMOs have been destroyed.
249    ///
250    /// *WARNING*: We need to ensure the open count is non-zero before invoking this function, so
251    /// it is only implemented for [`OpenedNode<FxBlob>`]. This prevents the blob from being purged
252    /// before we get a chance to register it with the pager for [`zx::Signals::VMO_ZERO_CHILDREN`].
253    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            // Take an open count so that we keep this object alive if it is otherwise closed. This
262            // is only valid since we know the current open count is non-zero, otherwise we might
263            // increment the open count after the blob has been purged.
264            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!(); // Add a parent back-reference if needed.
277    }
278
279    fn set_parent(&self, _parent: Arc<FxDirectory>) {
280        // NOP
281    }
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        // Delegate to the generic page handling code.
360        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        // The vmo shouldn't have full pages beyond the end of the blob so we shouldn't be getting
377        // page faults for ranges beyond the end of the blob.
378        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                } // loop
444                if decompression_errors > 0 {
445                    info!("Read succeeded on second attempt");
446                }
447            }
448        };
449        {
450            // TODO(https://fxbug.dev/42073035): This should be offloaded to the kernel at which
451            // point we can delete this.
452            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        // Zero the tail.
457        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            // The overwritten extents should each be exactly 8192 bytes.
611            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            // Ensure the tail is zeroed
659            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            // Overwrite the second read-ahead window.  The first window should successfully verify.
675            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(/*read_only=*/ 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        // The VMO shouldn't be resizable.
728        assert_matches!(blob_vmo.set_size(20), Err(_));
729
730        // The VMO shouldn't be writable.
731        assert_matches!(blob_vmo.write(b"overwrite", 0), Err(_));
732
733        // The VMO's content size shouldn't be modifiable.
734        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        // The start of reads must be chunk aligned.
851        assert!(
852            compression_info
853                .compressed_range_for_uncompressed_range(&(1..COMPRESSED_BLOB_CHUNK_SIZE),)
854                .is_err()
855        );
856
857        // Reading entirely past the last offset isn't allowed.
858        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        // Reading a different amount than the read-ahead size isn't allowed for middle offsets.
867        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        // Reading less than the read-ahead size for the last offset is allowed.
893        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        // Too small of destination buffer.
968        compression_info
969            .decompress(&compressed_data, &mut decompressed_data[0..CHUNK_SIZE - 1], 0)
970            .expect_err("decompression should fail");
971
972        // Too large of destination buffer.
973        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        // Decompress the entire blob.
999        compression_info
1000            .decompress(&compressed_data, &mut decompressed_data, 0)
1001            .expect("failed to decompress");
1002        assert_eq!(uncompressed_data, decompressed_data);
1003
1004        // Decompress just the whole chunks.
1005        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        // Too small of destination buffer for whole chunks.
1015        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        // Too large of destination buffer for whole chunks.
1024        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        // Decompress just the tail.
1033        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        // Too small of destination buffer for the tail.
1040        compression_info
1041            .decompress(partial_chunk, &mut decompressed_data[0..4095], CHUNK_SIZE as u64 * 4)
1042            .expect_err("decompression should fail");
1043
1044        // Too large of destination buffer for the tail.
1045        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            // Nothing has been read yet.
1062            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            // We have loaded pages, but only once each.
1072            assert_eq!(volume.blob_resupplied_count().read(Ordering::SeqCst), 0);
1073
1074            // Re-read some pages.
1075
1076            // We can't evict pages from the VMO to get the kernel to resupply them but we can call
1077            // page_in directly and wait for the counters to change.
1078            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}