1use 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#[derive(Debug)]
24pub struct Snapshot {
25 buffer: BackingBuffer,
27}
28
29pub type ScannedBlock<'a, K> = Block<&'a BackingBuffer, K>;
31
32const SNAPSHOT_TRIES: u64 = 1024;
33
34impl Snapshot {
35 pub fn scan(&self) -> BlockIterator<'_> {
37 BlockIterator::from(&self.buffer)
38 }
39
40 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 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 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 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 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 total_length <= data.len() {
151 return Ok(String::from_utf8_lossy(&data[..total_length]).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 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 .map(|i| block.get(i).ok_or(ReaderError::InvalidVmo))
193 .collect::<Result<Vec<i64>, _>>()?;
194 Property::IntArray(
195 name,
196 ArrayContent::new(values, format)?,
199 )
200 }
201 Some(BlockType::UintValue) => {
202 let block = block.cast_array_unchecked::<Uint>();
203 let values = value_indexes
204 .map(|i| block.get(i).ok_or(ReaderError::InvalidVmo))
205 .collect::<Result<Vec<u64>, _>>()?;
206 Property::UintArray(
207 name,
208 ArrayContent::new(values, format)?,
211 )
212 }
213 Some(BlockType::DoubleValue) => {
214 let block = block.cast_array_unchecked::<Double>();
215 let values = value_indexes
216 .map(|i| block.get(i).ok_or(ReaderError::InvalidVmo))
217 .collect::<Result<Vec<f64>, _>>()?;
218 Property::DoubleArray(
219 name,
220 ArrayContent::new(values, format)?,
223 )
224 }
225 Some(BlockType::StringReference) => {
226 let block = block.cast_array_unchecked::<StringRef>();
227 let values = value_indexes
228 .map(|value_index| {
229 let string_idx = block
230 .get_string_index_at(value_index)
231 .ok_or(ReaderError::InvalidVmo)?;
232 if string_idx == BlockIndex::EMPTY {
235 return Ok(String::new());
236 }
237
238 let ref_block = self
239 .get_block(string_idx)?
240 .cast::<StringRef>()
241 .ok_or(ReaderError::InvalidVmo)?;
242 self.load_string_reference(ref_block)
243 })
244 .collect::<Result<Vec<String>, _>>()?;
245 Property::StringList(name, values)
246 }
247 _ => return Err(ReaderError::UnexpectedArrayEntryFormat(block.entry_type_raw())),
248 };
249 Ok(parsed_property)
250 }
251
252 pub(crate) fn parse_property(
253 &self,
254 block: ScannedBlock<'_, Buffer>,
255 ) -> Result<Property, ReaderError> {
256 let name_index = block.name_index();
257 let name = self.get_name(name_index).ok_or(ReaderError::ParseName(name_index))?;
258 let data_index = block.extent_index();
259 match block.format().ok_or(ReaderError::InvalidVmo)? {
260 PropertyFormat::String => {
261 let total_length = block.total_length();
262 let buffer = self.read_extents(total_length, data_index)?;
263 Ok(Property::String(name, String::from_utf8_lossy(&buffer).to_string()))
264 }
265 PropertyFormat::Bytes => {
266 let total_length = block.total_length();
267 let buffer = self.read_extents(total_length, data_index)?;
268 Ok(Property::Bytes(name, buffer))
269 }
270 PropertyFormat::StringReference => {
271 let data_head = self
272 .get_block(data_index)?
273 .cast::<StringRef>()
274 .ok_or(ReaderError::InvalidVmo)?;
275 Ok(Property::String(name, self.load_string_reference(data_head)?))
276 }
277 }
278 }
279
280 pub(crate) fn parse_link(
281 &self,
282 block: ScannedBlock<'_, Link>,
283 ) -> Result<LinkValue, ReaderError> {
284 let name_index = block.name_index();
285 let name = self.get_name(name_index).ok_or(ReaderError::ParseName(name_index))?;
286 let link_content_index = block.content_index();
287 let content =
288 self.get_name(link_content_index).ok_or(ReaderError::ParseName(link_content_index))?;
289 let disposition = block.link_node_disposition().ok_or(ReaderError::InvalidVmo)?;
290 Ok(LinkValue { name, content, disposition })
291 }
292
293 pub(crate) fn read_extents(
296 &self,
297 total_length: usize,
298 first_extent: BlockIndex,
299 ) -> Result<Vec<u8>, ReaderError> {
300 if total_length > self.buffer.len() {
301 return Err(ReaderError::InvalidVmo);
302 }
303 let mut buffer = vec![0u8; total_length];
304 let mut offset = 0;
305 let mut extent_index = first_extent;
306 while extent_index != BlockIndex::EMPTY && offset < total_length {
307 let extent = self
308 .get_block(extent_index)
309 .and_then(|b| b.cast::<Extent>().ok_or(ReaderError::InvalidVmo))?;
310 let content = extent.contents()?;
311 if content.is_empty() {
312 break;
313 }
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 #[cfg(test)]
325 pub fn build(bytes: &[u8]) -> Self {
326 Snapshot { buffer: BackingBuffer::from(bytes.to_vec()) }
327 }
328}
329
330fn 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
347impl 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
388pub struct BlockIterator<'a> {
391 offset: usize,
393
394 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 test_string_reference_short_total_length() -> 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
536 let mut str_block = Block::free(&mut container, 2.into(), 0, BlockIndex::EMPTY)?
537 .become_reserved()
538 .become_string_reference();
539 str_block.write_inline(b"hello");
540 str_block.set_total_length(0);
541
542 let snapshot = get_snapshot!(container, storage, || {})?;
543 let name = snapshot.get_name(2.into());
544 assert_eq!(name, Some("".to_string()));
545
546 Ok(())
547 }
548
549 #[fuchsia::test]
550 fn scan() -> Result<(), Error> {
551 let size = 4096;
552 let (mut container, storage) = Container::read_and_write(size).unwrap();
553 let _ = Block::free(
554 &mut container,
555 BlockIndex::HEADER,
556 constants::HEADER_ORDER,
557 BlockIndex::EMPTY,
558 )?
559 .become_reserved()
560 .become_header(size)?;
561 let _ = Block::free(&mut container, 2.into(), 2, BlockIndex::EMPTY)?
562 .become_reserved()
563 .become_extent(6.into());
564 let _ = Block::free(&mut container, 6.into(), 0, BlockIndex::EMPTY)?
565 .become_reserved()
566 .become_int_value(1, 3.into(), 4.into());
567
568 let snapshot = get_snapshot!(container, storage, || {})?;
569
570 let mut blocks = snapshot.scan();
572
573 let block = blocks.next().unwrap().cast::<Header>().unwrap();
574 assert_eq!(block.block_type(), Some(BlockType::Header));
575 assert_eq!(*block.index(), 0);
576 assert_eq!(block.order(), constants::HEADER_ORDER);
577 assert_eq!(block.magic_number(), constants::HEADER_MAGIC_NUMBER);
578 assert_eq!(block.version(), constants::HEADER_VERSION_NUMBER);
579
580 let block = blocks.next().unwrap().cast::<Extent>().unwrap();
581 assert_eq!(block.block_type(), Some(BlockType::Extent));
582 assert_eq!(*block.index(), 2);
583 assert_eq!(block.order(), 2);
584 assert_eq!(*block.next_extent(), 6);
585
586 let block = blocks.next().unwrap().cast::<Int>().unwrap();
587 assert_eq!(block.block_type(), Some(BlockType::IntValue));
588 assert_eq!(*block.index(), 6);
589 assert_eq!(block.order(), 0);
590 assert_eq!(*block.name_index(), 3);
591 assert_eq!(*block.parent_index(), 4);
592 assert_eq!(block.value(), 1);
593
594 assert!(blocks.all(|b| b.block_type() == Some(BlockType::Free)));
595
596 assert_eq!(snapshot.get_block(0.into()).unwrap().block_type(), Some(BlockType::Header));
598 assert_eq!(snapshot.get_block(2.into()).unwrap().block_type(), Some(BlockType::Extent));
599 assert_eq!(snapshot.get_block(6.into()).unwrap().block_type(), Some(BlockType::IntValue));
600 assert_eq!(snapshot.get_block(7.into()).unwrap().block_type(), Some(BlockType::Free));
601 let bad_index = BlockIndex::from(4096);
602 assert_matches!(
603 snapshot.get_block(bad_index),
604 Err(ReaderError::GetBlock(index)) if index == bad_index
605 );
606
607 Ok(())
608 }
609
610 #[fuchsia::test]
611 fn scan_bad_header() -> Result<(), Error> {
612 let (mut container, storage) = Container::read_and_write(4096).unwrap();
613
614 container.copy_from_slice(&[
616 0x00, 0x02, 0xff, b'I', b'N', b'S', b'P',
620 ]);
621 assert!(get_snapshot!(container, storage, || {}).is_err());
622 Ok(())
623 }
624
625 #[fuchsia::test]
626 fn invalid_type() -> Result<(), Error> {
627 let (mut container, storage) = Container::read_and_write(4096).unwrap();
628 container.copy_from_slice(&[0x00, 0xff, 0x01]);
629 assert!(get_snapshot!(container, storage, || {}).is_err());
630 Ok(())
631 }
632
633 #[fuchsia::test]
634 fn invalid_order() -> Result<(), Error> {
635 let (mut container, storage) = Container::read_and_write(4096).unwrap();
636 container.copy_from_slice(&[0xff, 0xff]);
637 assert!(get_snapshot!(container, storage, || {}).is_err());
638 Ok(())
639 }
640
641 #[fuchsia::test]
642 fn invalid_pending_write() -> Result<(), Error> {
643 let size = 4096;
644 let (mut container, storage) = Container::read_and_write(size).unwrap();
645 let mut header = Block::free(
646 &mut container,
647 BlockIndex::HEADER,
648 constants::HEADER_ORDER,
649 BlockIndex::EMPTY,
650 )?
651 .become_reserved()
652 .become_header(size)?;
653 header.lock();
654 assert!(get_snapshot!(container, storage, || {}).is_err());
655 Ok(())
656 }
657
658 #[fuchsia::test]
659 fn invalid_magic_number() -> Result<(), Error> {
660 let size = 4096;
661 let (mut container, storage) = Container::read_and_write(size).unwrap();
662 let mut header = Block::free(
663 &mut container,
664 BlockIndex::HEADER,
665 constants::HEADER_ORDER,
666 BlockIndex::EMPTY,
667 )?
668 .become_reserved()
669 .become_header(size)?;
670 header.set_magic(3);
671 assert!(get_snapshot!(container, storage, || {}).is_err());
672 Ok(())
673 }
674
675 #[fuchsia::test]
676 fn invalid_generation_count() -> Result<(), Error> {
677 let size = 4096;
678 let (mut container, storage) = Container::read_and_write(size).unwrap();
679 let _ = Block::free(
680 &mut container,
681 BlockIndex::HEADER,
682 constants::HEADER_ORDER,
683 BlockIndex::EMPTY,
684 )?
685 .become_reserved()
686 .become_header(size)?;
687 let result = get_snapshot!(container, storage, || {
688 let mut header = container.block_at_unchecked_mut::<Header>(BlockIndex::HEADER);
689 header.lock();
690 header.unlock();
691 });
692 #[cfg(target_os = "fuchsia")]
693 assert!(result.is_err());
694 #[cfg(not(target_os = "fuchsia"))]
697 assert!(result.is_ok());
698 Ok(())
699 }
700
701 #[fuchsia::test]
702 fn snapshot_from_few_bytes() {
703 let values = (0u8..16).collect::<Vec<u8>>();
704 assert!(Snapshot::try_from(values.clone()).is_err());
705 assert!(Snapshot::try_from(values).is_err());
706 assert!(Snapshot::try_from(vec![]).is_err());
707 assert!(Snapshot::try_from(vec![0u8, 1, 2, 3, 4]).is_err());
708 }
709
710 #[fuchsia::test]
711 fn snapshot_frozen_vmo() -> Result<(), Error> {
712 let size = 4096;
713 let (mut container, parent_storage) = Container::read_and_write(size).unwrap();
714 let _ = Block::free(
715 &mut container,
716 BlockIndex::HEADER,
717 constants::HEADER_ORDER,
718 BlockIndex::EMPTY,
719 )?
720 .become_reserved()
721 .become_header(size)?;
722 container.copy_from_slice_at(8, &[0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]);
723
724 let snapshot;
725 #[cfg(target_os = "fuchsia")]
726 {
727 let storage = parent_storage.create_child(
728 zx::VmoChildOptions::SNAPSHOT | zx::VmoChildOptions::NO_WRITE,
729 0,
730 size as u64,
731 )?;
732 snapshot = get_snapshot!(container, storage, || {})?;
733 };
734 #[cfg(not(target_os = "fuchsia"))]
735 {
736 #[allow(clippy::let_unit_value)]
738 let _ = parent_storage;
739 snapshot = get_snapshot!(container, (), || {})?
740 };
741 assert!(matches!(snapshot.buffer, BackingBuffer::Container(_)));
742
743 let (mut container2, storage2) = Container::read_and_write(size).unwrap();
744 let _ = Block::free(
745 &mut container2,
746 BlockIndex::HEADER,
747 constants::HEADER_ORDER,
748 BlockIndex::EMPTY,
749 )?
750 .become_reserved()
751 .become_header(size)?;
752 container2.copy_from_slice_at(8, &[2u8; 8]);
753 let snapshot = get_snapshot!(container2, storage2, || {})?;
754 assert!(matches!(snapshot.buffer, BackingBuffer::Bytes(_)));
755
756 Ok(())
757 }
758
759 #[cfg(target_os = "fuchsia")]
762 #[fuchsia::test]
763 fn snapshot_frozen_mutable_vmo_fails() -> Result<(), Error> {
764 let size = 4096;
765 let (mut container, storage) = Container::read_and_write(size).unwrap();
766 let _ = Block::free(
767 &mut container,
768 BlockIndex::HEADER,
769 constants::HEADER_ORDER,
770 BlockIndex::EMPTY,
771 )?
772 .become_reserved()
773 .become_header(size)?;
774 container.copy_from_slice_at(8, &[0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]);
775
776 assert_matches!(
777 get_snapshot!(container, storage, || {}),
778 Err(ReaderError::Vmo(zx::Status::BAD_STATE))
779 );
780
781 Ok(())
782 }
783
784 #[fuchsia::test]
785 fn snapshot_vmo_with_unused_space() -> Result<(), Error> {
786 let size = 4 * constants::PAGE_SIZE_BYTES;
787 let (mut container, storage) = Container::read_and_write(size).unwrap();
788 let _ = Block::free(
789 &mut container,
790 BlockIndex::HEADER,
791 constants::HEADER_ORDER,
792 BlockIndex::EMPTY,
793 )?
794 .become_reserved()
795 .become_header(constants::PAGE_SIZE_BYTES)?;
796
797 let snapshot = get_snapshot!(container, storage, || {})?;
798 assert_eq!(snapshot.buffer.len(), constants::PAGE_SIZE_BYTES);
799
800 Ok(())
801 }
802
803 #[fuchsia::test]
804 fn snapshot_vmo_with_very_large_vmo() -> Result<(), Error> {
805 let size = 2 * constants::MAX_VMO_SIZE;
806 let (mut container, storage) = Container::read_and_write(size).unwrap();
807 let _ = Block::free(
808 &mut container,
809 BlockIndex::HEADER,
810 constants::HEADER_ORDER,
811 BlockIndex::EMPTY,
812 )?
813 .become_reserved()
814 .become_header(size)?;
815
816 let snapshot = get_snapshot!(container, storage, || {})?;
817 assert_eq!(snapshot.buffer.len(), constants::MAX_VMO_SIZE);
818
819 Ok(())
820 }
821
822 #[fuchsia::test]
823 fn snapshot_vmo_with_header_without_size_info() -> Result<(), Error> {
824 let size = 2 * constants::PAGE_SIZE_BYTES;
825 let (mut container, storage) = Container::read_and_write(size).unwrap();
826 let mut header = Block::free(&mut container, BlockIndex::HEADER, 0, BlockIndex::EMPTY)?
827 .become_reserved()
828 .become_header(constants::PAGE_SIZE_BYTES)?;
829 header.set_order(0)?;
830
831 let snapshot = get_snapshot!(container, storage, || {})?;
832 assert_eq!(snapshot.buffer.len(), size);
833
834 Ok(())
835 }
836}