Skip to main content

fuchsia_inspect/reader/
snapshot.rs

1// Copyright 2019 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//! A snapshot represents all the loaded blocks of the VMO in a way that we can reconstruct the
6//! implicit tree.
7
8use crate::Inspector;
9use crate::reader::LinkValue;
10use crate::reader::error::ReaderError;
11use crate::reader::readable_tree::SnapshotSource;
12use diagnostics_hierarchy::{ArrayContent, Property};
13use inspect_format::{
14    Array, Block, BlockAccessorExt, BlockContainer, BlockIndex, BlockType, Bool, Buffer, Container,
15    CopyBytes, Double, Extent, Header, Int, Link, Name, PropertyFormat, ReadBytes, StringRef, Uint,
16    Unknown, ValueBlockKind, constants, utils,
17};
18use std::cmp;
19
20pub use crate::reader::tree_reader::SnapshotTree;
21
22/// Enables to scan all the blocks in a given buffer.
23#[derive(Debug)]
24pub struct Snapshot {
25    /// The buffer read from an Inspect VMO.
26    buffer: BackingBuffer,
27}
28
29/// A scanned block.
30pub type ScannedBlock<'a, K> = Block<&'a BackingBuffer, K>;
31
32const SNAPSHOT_TRIES: u64 = 1024;
33
34impl Snapshot {
35    /// Returns an iterator that returns all the Blocks in the buffer.
36    pub fn scan(&self) -> BlockIterator<'_> {
37        BlockIterator::from(&self.buffer)
38    }
39
40    /// Gets the block at the given |index|.
41    pub fn get_block(&self, index: BlockIndex) -> Result<ScannedBlock<'_, Unknown>, ReaderError> {
42        if index.offset() < self.buffer.len() {
43            Ok(self.buffer.block_at(index))
44        } else {
45            Err(ReaderError::GetBlock(index))
46        }
47    }
48
49    /// Try to take a consistent snapshot of the given VMO once.
50    ///
51    /// Returns a Snapshot on success or an Error if a consistent snapshot could not be taken.
52    pub fn try_once_from_vmo(source: &SnapshotSource) -> Result<Snapshot, ReaderError> {
53        Snapshot::try_once_with_callback(source, &mut || {})
54    }
55
56    fn try_once_with_callback<F>(
57        source: &SnapshotSource,
58        read_callback: &mut F,
59    ) -> Result<Snapshot, ReaderError>
60    where
61        F: FnMut(),
62    {
63        // Read the generation count one time
64        let mut header_bytes: [u8; 32] = [0; 32];
65        source.copy_bytes(&mut header_bytes);
66        let Some(header_block) = header_bytes.maybe_block_at::<Header>(BlockIndex::HEADER) else {
67            return Err(ReaderError::InvalidVmo);
68        };
69        let generation = header_block.generation_count();
70        if generation == constants::VMO_FROZEN {
71            #[cfg(target_os = "fuchsia")]
72            {
73                let info = source.info().map_err(ReaderError::Vmo)?;
74                if !info.flags.contains(zx::VmoInfoFlags::IMMUTABLE) {
75                    return Err(ReaderError::Vmo(zx::Status::BAD_STATE));
76                }
77            }
78            if let Ok(buffer) = BackingBuffer::try_from(source) {
79                return Ok(Snapshot { buffer });
80            }
81        }
82
83        // Read the buffer
84        let vmo_size = if let Some(vmo_size) = header_block.vmo_size()? {
85            cmp::min(vmo_size as usize, constants::MAX_VMO_SIZE)
86        } else {
87            cmp::min(source.len(), constants::MAX_VMO_SIZE)
88        };
89        let mut buffer = vec![0u8; vmo_size];
90        source.copy_bytes(&mut buffer);
91        if cfg!(test) {
92            read_callback();
93        }
94
95        // Read the generation count one more time to ensure the previous buffer read is
96        // consistent. It's safe to unwrap this time, we already checked we can read 32 bytes from
97        // the slice.
98        source.copy_bytes(&mut header_bytes);
99        match header_generation_count(&header_bytes) {
100            None => Err(ReaderError::InconsistentSnapshot),
101            Some(new_generation) if new_generation != generation => {
102                Err(ReaderError::InconsistentSnapshot)
103            }
104            Some(_) => Ok(Snapshot { buffer: BackingBuffer::from(buffer) }),
105        }
106    }
107
108    fn try_from_with_callback<F>(
109        source: &SnapshotSource,
110        mut read_callback: F,
111    ) -> Result<Snapshot, ReaderError>
112    where
113        F: FnMut(),
114    {
115        let mut i = 0;
116        loop {
117            match Snapshot::try_once_with_callback(source, &mut read_callback) {
118                Ok(snapshot) => return Ok(snapshot),
119                Err(e) => {
120                    if i >= SNAPSHOT_TRIES {
121                        return Err(e);
122                    }
123                }
124            };
125            i += 1;
126        }
127    }
128
129    pub(crate) fn get_name(&self, index: BlockIndex) -> Option<String> {
130        let block = self.get_block(index).ok()?;
131        match block.block_type()? {
132            BlockType::Name => self.load_name(block.cast::<Name>().unwrap()),
133            BlockType::StringReference => {
134                self.load_string_reference(block.cast::<StringRef>().unwrap()).ok()
135            }
136            _ => None,
137        }
138    }
139
140    pub(crate) fn load_name(&self, block: ScannedBlock<'_, Name>) -> Option<String> {
141        block.contents().ok().map(|s| s.to_string())
142    }
143
144    pub(crate) fn load_string_reference(
145        &self,
146        block: ScannedBlock<'_, StringRef>,
147    ) -> Result<String, ReaderError> {
148        let mut data = block.inline_data()?.to_vec();
149        let total_length = block.total_length();
150        if data.len() == total_length {
151            return Ok(String::from_utf8_lossy(&data).to_string());
152        }
153
154        let extent_index = block.next_extent();
155        let still_to_read_length = total_length - data.len();
156        data.append(&mut self.read_extents(still_to_read_length, extent_index)?);
157
158        Ok(String::from_utf8_lossy(&data).to_string())
159    }
160
161    pub(crate) fn parse_primitive_property<'a, K>(
162        &self,
163        block: ScannedBlock<'a, K>,
164    ) -> Result<Property, ReaderError>
165    where
166        ScannedBlock<'a, K>: MakePrimitiveProperty,
167        K: ValueBlockKind,
168    {
169        let name_index = block.name_index();
170        let name = self.get_name(name_index).ok_or(ReaderError::ParseName(name_index))?;
171        Ok(block.make_property(name))
172    }
173
174    pub(crate) fn parse_array_property(
175        &self,
176        block: ScannedBlock<'_, Array<Unknown>>,
177    ) -> Result<Property, ReaderError> {
178        let name_index = block.name_index();
179        let name = self.get_name(name_index).ok_or(ReaderError::ParseName(name_index))?;
180        let array_slots = block.slots();
181        // Safety: So long as the array is valid, array_capacity will return a valid value.
182        let capacity = block.capacity().ok_or(ReaderError::InvalidVmo)?;
183        if capacity < array_slots {
184            return Err(ReaderError::AttemptedToReadTooManyArraySlots(block.index()));
185        }
186        let value_indexes = 0..array_slots;
187        let format = block.format().ok_or(ReaderError::InvalidVmo)?;
188        let parsed_property = match block.entry_type() {
189            Some(BlockType::IntValue) => {
190                let block = block.cast_array_unchecked::<Int>();
191                let values = value_indexes
192                    // Safety: in release mode, this can only error for index-out-of-bounds.
193                    // We check above that indexes are in-bounds.
194                    .map(|i| block.get(i).unwrap())
195                    .collect::<Vec<i64>>();
196                Property::IntArray(
197                    name,
198                    // Safety: if the block is an array, it must have an array format.
199                    // We have already verified it is an array.
200                    ArrayContent::new(values, format)?,
201                )
202            }
203            Some(BlockType::UintValue) => {
204                let block = block.cast_array_unchecked::<Uint>();
205                let values = value_indexes
206                    // Safety: in release mode, this can only error for index-out-of-bounds.
207                    // We check above that indexes are in-bounds.
208                    .map(|i| block.get(i).unwrap())
209                    .collect::<Vec<u64>>();
210                Property::UintArray(
211                    name,
212                    // Safety: if the block is an array, it must have an array format.
213                    // We have already verified it is an array.
214                    ArrayContent::new(values, format)?,
215                )
216            }
217            Some(BlockType::DoubleValue) => {
218                let block = block.cast_array_unchecked::<Double>();
219                let values = value_indexes
220                    // Safety: in release mode, this can only error for index-out-of-bounds.
221                    // We check above that indexes are in-bounds.
222                    .map(|i| block.get(i).unwrap())
223                    .collect::<Vec<f64>>();
224                Property::DoubleArray(
225                    name,
226                    // Safety: if the block is an array, it must have an array format.
227                    // We have already verified it is an array.
228                    ArrayContent::new(values, format)?,
229                )
230            }
231            Some(BlockType::StringReference) => {
232                let block = block.cast_array_unchecked::<StringRef>();
233                let values = value_indexes
234                    .map(|value_index| {
235                        let string_idx = block
236                            .get_string_index_at(value_index)
237                            .ok_or(ReaderError::InvalidVmo)?;
238                        // default initialize unset values -- 0 index is never a string, it is always
239                        // the header block
240                        if string_idx == BlockIndex::EMPTY {
241                            return Ok(String::new());
242                        }
243
244                        let ref_block = self
245                            .get_block(string_idx)?
246                            .cast::<StringRef>()
247                            .ok_or(ReaderError::InvalidVmo)?;
248                        self.load_string_reference(ref_block)
249                    })
250                    .collect::<Result<Vec<String>, _>>()?;
251                Property::StringList(name, values)
252            }
253            _ => return Err(ReaderError::UnexpectedArrayEntryFormat(block.entry_type_raw())),
254        };
255        Ok(parsed_property)
256    }
257
258    pub(crate) fn parse_property(
259        &self,
260        block: ScannedBlock<'_, Buffer>,
261    ) -> Result<Property, ReaderError> {
262        let name_index = block.name_index();
263        let name = self.get_name(name_index).ok_or(ReaderError::ParseName(name_index))?;
264        let data_index = block.extent_index();
265        match block.format().ok_or(ReaderError::InvalidVmo)? {
266            PropertyFormat::String => {
267                let total_length = block.total_length();
268                let buffer = self.read_extents(total_length, data_index)?;
269                Ok(Property::String(name, String::from_utf8_lossy(&buffer).to_string()))
270            }
271            PropertyFormat::Bytes => {
272                let total_length = block.total_length();
273                let buffer = self.read_extents(total_length, data_index)?;
274                Ok(Property::Bytes(name, buffer))
275            }
276            PropertyFormat::StringReference => {
277                let data_head = self
278                    .get_block(data_index)?
279                    .cast::<StringRef>()
280                    .ok_or(ReaderError::InvalidVmo)?;
281                Ok(Property::String(name, self.load_string_reference(data_head)?))
282            }
283        }
284    }
285
286    pub(crate) fn parse_link(
287        &self,
288        block: ScannedBlock<'_, Link>,
289    ) -> Result<LinkValue, ReaderError> {
290        let name_index = block.name_index();
291        let name = self.get_name(name_index).ok_or(ReaderError::ParseName(name_index))?;
292        let link_content_index = block.content_index();
293        let content =
294            self.get_name(link_content_index).ok_or(ReaderError::ParseName(link_content_index))?;
295        let disposition = block.link_node_disposition().ok_or(ReaderError::InvalidVmo)?;
296        Ok(LinkValue { name, content, disposition })
297    }
298
299    // Incrementally add the contents of each extent in the extent linked list
300    // until we reach the last extent or the maximum expected length.
301    pub(crate) fn read_extents(
302        &self,
303        total_length: usize,
304        first_extent: BlockIndex,
305    ) -> Result<Vec<u8>, ReaderError> {
306        let mut buffer = vec![0u8; total_length];
307        let mut offset = 0;
308        let mut extent_index = first_extent;
309        while extent_index != BlockIndex::EMPTY && offset < total_length {
310            let extent = self
311                .get_block(extent_index)
312                .and_then(|b| b.cast::<Extent>().ok_or(ReaderError::InvalidVmo))?;
313            let content = extent.contents()?;
314            let extent_length = cmp::min(total_length - offset, content.len());
315            buffer[offset..offset + extent_length].copy_from_slice(&content[..extent_length]);
316            offset += extent_length;
317            extent_index = extent.next_extent();
318        }
319
320        Ok(buffer)
321    }
322
323    // Used for snapshot tests.
324    #[cfg(test)]
325    pub fn build(bytes: &[u8]) -> Self {
326        Snapshot { buffer: BackingBuffer::from(bytes.to_vec()) }
327    }
328}
329
330/// Reads the given 16 bytes as an Inspect Block Header and returns the
331/// generation count if the header is valid: correct magic number, version number
332/// and nobody is writing to it.
333fn header_generation_count<T: ReadBytes>(bytes: &T) -> Option<u64> {
334    if bytes.len() < 16 {
335        return None;
336    }
337    let block = bytes.maybe_block_at::<Header>(BlockIndex::HEADER)?;
338    if block.magic_number() == constants::HEADER_MAGIC_NUMBER
339        && block.version() <= constants::HEADER_VERSION_NUMBER
340        && !block.is_locked()
341    {
342        return Some(block.generation_count());
343    }
344    None
345}
346
347/// Construct a snapshot from a byte vector.
348impl TryFrom<Vec<u8>> for Snapshot {
349    type Error = ReaderError;
350
351    fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
352        if header_generation_count(&bytes).is_some() {
353            Ok(Snapshot { buffer: BackingBuffer::from(bytes) })
354        } else {
355            Err(ReaderError::MissingHeaderOrLocked)
356        }
357    }
358}
359
360impl TryFrom<&Inspector> for Snapshot {
361    type Error = ReaderError;
362
363    fn try_from(inspector: &Inspector) -> Result<Self, Self::Error> {
364        let handle = inspector.get_storage_handle();
365        let storage = handle.as_ref().ok_or(ReaderError::NoOpInspector)?;
366        Snapshot::try_from_with_callback(storage, || {})
367    }
368}
369
370#[cfg(target_os = "fuchsia")]
371impl TryFrom<&zx::Vmo> for Snapshot {
372    type Error = ReaderError;
373
374    fn try_from(vmo: &zx::Vmo) -> Result<Self, Self::Error> {
375        Snapshot::try_from_with_callback(vmo, || {})
376    }
377}
378
379#[cfg(not(target_os = "fuchsia"))]
380impl TryFrom<&Vec<u8>> for Snapshot {
381    type Error = ReaderError;
382
383    fn try_from(buffer: &Vec<u8>) -> Result<Self, Self::Error> {
384        Snapshot::try_from_with_callback(buffer, || {})
385    }
386}
387
388/// Iterates over a byte array containing Inspect API blocks and returns the
389/// blocks in order.
390pub struct BlockIterator<'a> {
391    /// Current offset at which the iterator is reading.
392    offset: usize,
393
394    /// The bytes being read.
395    container: &'a BackingBuffer,
396}
397
398impl<'a> From<&'a BackingBuffer> for BlockIterator<'a> {
399    fn from(container: &'a BackingBuffer) -> Self {
400        BlockIterator { offset: 0, container }
401    }
402}
403
404impl<'a> Iterator for BlockIterator<'a> {
405    type Item = ScannedBlock<'a, Unknown>;
406
407    fn next(&mut self) -> Option<Self::Item> {
408        if self.offset >= self.container.len() {
409            return None;
410        }
411        let index = BlockIndex::from_offset(self.offset);
412        let block = self.container.block_at(index);
413        if self.container.len() - self.offset < utils::order_to_size(block.order()) {
414            return None;
415        }
416        self.offset += utils::order_to_size(block.order());
417        Some(block)
418    }
419}
420
421#[derive(Debug)]
422pub enum BackingBuffer {
423    Bytes(Vec<u8>),
424    Container(Container),
425}
426
427#[cfg(target_os = "fuchsia")]
428impl TryFrom<&zx::Vmo> for BackingBuffer {
429    type Error = ReaderError;
430    fn try_from(source: &zx::Vmo) -> Result<Self, Self::Error> {
431        let container = Container::read_only(source)?;
432        Ok(BackingBuffer::Container(container))
433    }
434}
435
436#[cfg(not(target_os = "fuchsia"))]
437impl TryFrom<&Vec<u8>> for BackingBuffer {
438    type Error = ReaderError;
439    fn try_from(source: &Vec<u8>) -> Result<Self, Self::Error> {
440        let container = Container::read_only(source);
441        Ok(BackingBuffer::Container(container))
442    }
443}
444
445impl From<Vec<u8>> for BackingBuffer {
446    fn from(v: Vec<u8>) -> Self {
447        BackingBuffer::Bytes(v)
448    }
449}
450
451impl ReadBytes for BackingBuffer {
452    fn get_slice_at(&self, offset: usize, size: usize) -> Option<&[u8]> {
453        match &self {
454            BackingBuffer::Container(m) => m.get_slice_at(offset, size),
455            BackingBuffer::Bytes(b) => b.get_slice_at(offset, size),
456        }
457    }
458}
459
460impl BlockContainer for BackingBuffer {
461    type Data = Self;
462    type ShareableData = ();
463
464    fn len(&self) -> usize {
465        match &self {
466            BackingBuffer::Container(m) => m.len(),
467            BackingBuffer::Bytes(v) => v.len(),
468        }
469    }
470}
471
472pub(crate) trait MakePrimitiveProperty {
473    fn make_property(&self, name: String) -> Property;
474}
475
476impl MakePrimitiveProperty for ScannedBlock<'_, Int> {
477    fn make_property(&self, name: String) -> Property {
478        Property::Int(name, self.value())
479    }
480}
481
482impl MakePrimitiveProperty for ScannedBlock<'_, Uint> {
483    fn make_property(&self, name: String) -> Property {
484        Property::Uint(name, self.value())
485    }
486}
487
488impl MakePrimitiveProperty for ScannedBlock<'_, Double> {
489    fn make_property(&self, name: String) -> Property {
490        Property::Double(name, self.value())
491    }
492}
493
494impl MakePrimitiveProperty for ScannedBlock<'_, Bool> {
495    fn make_property(&self, name: String) -> Property {
496        Property::Bool(name, self.value())
497    }
498}
499
500#[cfg(test)]
501mod tests {
502    use super::*;
503    use anyhow::Error;
504    use assert_matches::assert_matches;
505    use inspect_format::{BlockAccessorMutExt, WriteBytes};
506
507    #[cfg(target_os = "fuchsia")]
508    macro_rules! get_snapshot {
509        ($container:ident, $storage:expr, $callback:expr) => {
510            Snapshot::try_from_with_callback(&$storage, $callback)
511        };
512    }
513
514    #[cfg(not(target_os = "fuchsia"))]
515    macro_rules! get_snapshot {
516        ($container:ident, $storage:expr, $callback:expr) => {{
517            let _storage = $storage;
518            let slice = $container.get_slice($container.len()).unwrap().to_vec();
519            Snapshot::try_from_with_callback(&slice, $callback)
520        }};
521    }
522
523    #[fuchsia::test]
524    fn scan() -> Result<(), Error> {
525        let size = 4096;
526        let (mut container, storage) = Container::read_and_write(size).unwrap();
527        let _ = Block::free(
528            &mut container,
529            BlockIndex::HEADER,
530            constants::HEADER_ORDER,
531            BlockIndex::EMPTY,
532        )?
533        .become_reserved()
534        .become_header(size)?;
535        let _ = Block::free(&mut container, 2.into(), 2, BlockIndex::EMPTY)?
536            .become_reserved()
537            .become_extent(6.into());
538        let _ = Block::free(&mut container, 6.into(), 0, BlockIndex::EMPTY)?
539            .become_reserved()
540            .become_int_value(1, 3.into(), 4.into());
541
542        let snapshot = get_snapshot!(container, storage, || {})?;
543
544        // Scan blocks
545        let mut blocks = snapshot.scan();
546
547        let block = blocks.next().unwrap().cast::<Header>().unwrap();
548        assert_eq!(block.block_type(), Some(BlockType::Header));
549        assert_eq!(*block.index(), 0);
550        assert_eq!(block.order(), constants::HEADER_ORDER);
551        assert_eq!(block.magic_number(), constants::HEADER_MAGIC_NUMBER);
552        assert_eq!(block.version(), constants::HEADER_VERSION_NUMBER);
553
554        let block = blocks.next().unwrap().cast::<Extent>().unwrap();
555        assert_eq!(block.block_type(), Some(BlockType::Extent));
556        assert_eq!(*block.index(), 2);
557        assert_eq!(block.order(), 2);
558        assert_eq!(*block.next_extent(), 6);
559
560        let block = blocks.next().unwrap().cast::<Int>().unwrap();
561        assert_eq!(block.block_type(), Some(BlockType::IntValue));
562        assert_eq!(*block.index(), 6);
563        assert_eq!(block.order(), 0);
564        assert_eq!(*block.name_index(), 3);
565        assert_eq!(*block.parent_index(), 4);
566        assert_eq!(block.value(), 1);
567
568        assert!(blocks.all(|b| b.block_type() == Some(BlockType::Free)));
569
570        // Verify get_block
571        assert_eq!(snapshot.get_block(0.into()).unwrap().block_type(), Some(BlockType::Header));
572        assert_eq!(snapshot.get_block(2.into()).unwrap().block_type(), Some(BlockType::Extent));
573        assert_eq!(snapshot.get_block(6.into()).unwrap().block_type(), Some(BlockType::IntValue));
574        assert_eq!(snapshot.get_block(7.into()).unwrap().block_type(), Some(BlockType::Free));
575        let bad_index = BlockIndex::from(4096);
576        assert_matches!(
577            snapshot.get_block(bad_index),
578            Err(ReaderError::GetBlock(index)) if index == bad_index
579        );
580
581        Ok(())
582    }
583
584    #[fuchsia::test]
585    fn scan_bad_header() -> Result<(), Error> {
586        let (mut container, storage) = Container::read_and_write(4096).unwrap();
587
588        // create a header block with an invalid version number
589        container.copy_from_slice(&[
590            0x00, /* order/reserved */
591            0x02, /* type */
592            0xff, /* invalid version number */
593            b'I', b'N', b'S', b'P',
594        ]);
595        assert!(get_snapshot!(container, storage, || {}).is_err());
596        Ok(())
597    }
598
599    #[fuchsia::test]
600    fn invalid_type() -> Result<(), Error> {
601        let (mut container, storage) = Container::read_and_write(4096).unwrap();
602        container.copy_from_slice(&[0x00, 0xff, 0x01]);
603        assert!(get_snapshot!(container, storage, || {}).is_err());
604        Ok(())
605    }
606
607    #[fuchsia::test]
608    fn invalid_order() -> Result<(), Error> {
609        let (mut container, storage) = Container::read_and_write(4096).unwrap();
610        container.copy_from_slice(&[0xff, 0xff]);
611        assert!(get_snapshot!(container, storage, || {}).is_err());
612        Ok(())
613    }
614
615    #[fuchsia::test]
616    fn invalid_pending_write() -> Result<(), Error> {
617        let size = 4096;
618        let (mut container, storage) = Container::read_and_write(size).unwrap();
619        let mut header = Block::free(
620            &mut container,
621            BlockIndex::HEADER,
622            constants::HEADER_ORDER,
623            BlockIndex::EMPTY,
624        )?
625        .become_reserved()
626        .become_header(size)?;
627        header.lock();
628        assert!(get_snapshot!(container, storage, || {}).is_err());
629        Ok(())
630    }
631
632    #[fuchsia::test]
633    fn invalid_magic_number() -> Result<(), Error> {
634        let size = 4096;
635        let (mut container, storage) = Container::read_and_write(size).unwrap();
636        let mut header = Block::free(
637            &mut container,
638            BlockIndex::HEADER,
639            constants::HEADER_ORDER,
640            BlockIndex::EMPTY,
641        )?
642        .become_reserved()
643        .become_header(size)?;
644        header.set_magic(3);
645        assert!(get_snapshot!(container, storage, || {}).is_err());
646        Ok(())
647    }
648
649    #[fuchsia::test]
650    fn invalid_generation_count() -> Result<(), Error> {
651        let size = 4096;
652        let (mut container, storage) = Container::read_and_write(size).unwrap();
653        let _ = Block::free(
654            &mut container,
655            BlockIndex::HEADER,
656            constants::HEADER_ORDER,
657            BlockIndex::EMPTY,
658        )?
659        .become_reserved()
660        .become_header(size)?;
661        let result = get_snapshot!(container, storage, || {
662            let mut header = container.block_at_unchecked_mut::<Header>(BlockIndex::HEADER);
663            header.lock();
664            header.unlock();
665        });
666        #[cfg(target_os = "fuchsia")]
667        assert!(result.is_err());
668        // When in the host, we don't have underlying shared memory, so this can't fail as we
669        // had already cloned the underlying vector.
670        #[cfg(not(target_os = "fuchsia"))]
671        assert!(result.is_ok());
672        Ok(())
673    }
674
675    #[fuchsia::test]
676    fn snapshot_from_few_bytes() {
677        let values = (0u8..16).collect::<Vec<u8>>();
678        assert!(Snapshot::try_from(values.clone()).is_err());
679        assert!(Snapshot::try_from(values).is_err());
680        assert!(Snapshot::try_from(vec![]).is_err());
681        assert!(Snapshot::try_from(vec![0u8, 1, 2, 3, 4]).is_err());
682    }
683
684    #[fuchsia::test]
685    fn snapshot_frozen_vmo() -> Result<(), Error> {
686        let size = 4096;
687        let (mut container, parent_storage) = Container::read_and_write(size).unwrap();
688        let _ = Block::free(
689            &mut container,
690            BlockIndex::HEADER,
691            constants::HEADER_ORDER,
692            BlockIndex::EMPTY,
693        )?
694        .become_reserved()
695        .become_header(size)?;
696        container.copy_from_slice_at(8, &[0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]);
697
698        let snapshot;
699        #[cfg(target_os = "fuchsia")]
700        {
701            let storage = parent_storage.create_child(
702                zx::VmoChildOptions::SNAPSHOT | zx::VmoChildOptions::NO_WRITE,
703                0,
704                size as u64,
705            )?;
706            snapshot = get_snapshot!(container, storage, || {})?;
707        };
708        #[cfg(not(target_os = "fuchsia"))]
709        {
710            // Silence unused variable warning, which happens to be set to a unit value.
711            #[allow(clippy::let_unit_value)]
712            let _ = parent_storage;
713            snapshot = get_snapshot!(container, (), || {})?
714        };
715        assert!(matches!(snapshot.buffer, BackingBuffer::Container(_)));
716
717        let (mut container2, storage2) = Container::read_and_write(size).unwrap();
718        let _ = Block::free(
719            &mut container2,
720            BlockIndex::HEADER,
721            constants::HEADER_ORDER,
722            BlockIndex::EMPTY,
723        )?
724        .become_reserved()
725        .become_header(size)?;
726        container2.copy_from_slice_at(8, &[2u8; 8]);
727        let snapshot = get_snapshot!(container2, storage2, || {})?;
728        assert!(matches!(snapshot.buffer, BackingBuffer::Bytes(_)));
729
730        Ok(())
731    }
732
733    // Check that snapshot fails if the VMO is frozen but not immutable.
734    // This test is only valid on Fuchsia, where VMOs can be used.
735    #[cfg(target_os = "fuchsia")]
736    #[fuchsia::test]
737    fn snapshot_frozen_mutable_vmo_fails() -> Result<(), Error> {
738        let size = 4096;
739        let (mut container, storage) = Container::read_and_write(size).unwrap();
740        let _ = Block::free(
741            &mut container,
742            BlockIndex::HEADER,
743            constants::HEADER_ORDER,
744            BlockIndex::EMPTY,
745        )?
746        .become_reserved()
747        .become_header(size)?;
748        container.copy_from_slice_at(8, &[0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]);
749
750        assert_matches!(
751            get_snapshot!(container, storage, || {}),
752            Err(ReaderError::Vmo(zx::Status::BAD_STATE))
753        );
754
755        Ok(())
756    }
757
758    #[fuchsia::test]
759    fn snapshot_vmo_with_unused_space() -> Result<(), Error> {
760        let size = 4 * constants::PAGE_SIZE_BYTES;
761        let (mut container, storage) = Container::read_and_write(size).unwrap();
762        let _ = Block::free(
763            &mut container,
764            BlockIndex::HEADER,
765            constants::HEADER_ORDER,
766            BlockIndex::EMPTY,
767        )?
768        .become_reserved()
769        .become_header(constants::PAGE_SIZE_BYTES)?;
770
771        let snapshot = get_snapshot!(container, storage, || {})?;
772        assert_eq!(snapshot.buffer.len(), constants::PAGE_SIZE_BYTES);
773
774        Ok(())
775    }
776
777    #[fuchsia::test]
778    fn snapshot_vmo_with_very_large_vmo() -> Result<(), Error> {
779        let size = 2 * constants::MAX_VMO_SIZE;
780        let (mut container, storage) = Container::read_and_write(size).unwrap();
781        let _ = Block::free(
782            &mut container,
783            BlockIndex::HEADER,
784            constants::HEADER_ORDER,
785            BlockIndex::EMPTY,
786        )?
787        .become_reserved()
788        .become_header(size)?;
789
790        let snapshot = get_snapshot!(container, storage, || {})?;
791        assert_eq!(snapshot.buffer.len(), constants::MAX_VMO_SIZE);
792
793        Ok(())
794    }
795
796    #[fuchsia::test]
797    fn snapshot_vmo_with_header_without_size_info() -> Result<(), Error> {
798        let size = 2 * constants::PAGE_SIZE_BYTES;
799        let (mut container, storage) = Container::read_and_write(size).unwrap();
800        let mut header = Block::free(&mut container, BlockIndex::HEADER, 0, BlockIndex::EMPTY)?
801            .become_reserved()
802            .become_header(constants::PAGE_SIZE_BYTES)?;
803        header.set_order(0)?;
804
805        let snapshot = get_snapshot!(container, storage, || {})?;
806        assert_eq!(snapshot.buffer.len(), size);
807
808        Ok(())
809    }
810}