Skip to main content

fuchsia_inspect/writer/types/
inspector.rs

1// Copyright 2021 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
5use crate::writer::private::InspectTypeInternal;
6use crate::writer::state::Stats;
7use crate::writer::{Error, Heap, Node, State};
8use diagnostics_hierarchy::{DiagnosticsHierarchy, DiagnosticsHierarchyGetter};
9use inspect_format::{BlockContainer, Container, constants};
10use log::error;
11use std::borrow::Cow;
12use std::cmp::max;
13use std::sync::Arc;
14
15#[cfg(target_os = "fuchsia")]
16use inspect_format::{BlockAccessorExt, Header};
17#[cfg(target_os = "fuchsia")]
18use zx::HandleBased;
19
20/// Root of the Inspect API. Through this API, further nodes can be created and inspect can be
21/// served.
22#[derive(Clone)]
23pub struct Inspector {
24    /// The root node.
25    root_node: Arc<Node>,
26
27    /// The storage backing the inspector. This is a VMO when working on Fuchsia.
28    #[allow(dead_code)] // unused and meaningless in the host build.
29    storage: Option<Arc<<Container as BlockContainer>::ShareableData>>,
30}
31
32impl DiagnosticsHierarchyGetter<String> for Inspector {
33    async fn get_diagnostics_hierarchy<'a>(&'a self) -> Cow<'_, DiagnosticsHierarchy>
34    where
35        String: 'a,
36    {
37        let hierarchy = crate::reader::read(self).await.expect("failed to get hierarchy");
38        Cow::Owned(hierarchy)
39    }
40}
41
42pub trait InspectorIntrospectionExt {
43    fn stats(&self) -> Option<Stats>;
44}
45
46impl InspectorIntrospectionExt for Inspector {
47    fn stats(&self) -> Option<Stats> {
48        self.state().and_then(|outer| outer.try_lock().ok().map(|state| state.stats()))
49    }
50}
51
52#[cfg(target_os = "fuchsia")]
53impl Inspector {
54    /// Returns a duplicate of the underlying VMO for this Inspector.
55    ///
56    /// The duplicated VMO will be read-only, and is suitable to send to clients over FIDL.
57    pub fn duplicate_vmo(&self) -> Option<zx::Vmo> {
58        self.storage.as_ref().and_then(|vmo| {
59            vmo.duplicate_handle(
60                zx::Rights::BASIC | zx::Rights::READ | zx::Rights::MAP | zx::Rights::GET_PROPERTY,
61            )
62            .ok()
63        })
64    }
65
66    /// Returns a duplicate of the underlying VMO for this Inspector with the given rights.
67    ///
68    /// The duplicated VMO will be read-only, and is suitable to send to clients over FIDL.
69    pub fn duplicate_vmo_with_rights(&self, rights: zx::Rights) -> Option<zx::Vmo> {
70        self.storage.as_ref().and_then(|vmo| vmo.duplicate_handle(rights).ok())
71    }
72
73    /// This produces a copy-on-write VMO with a generation count marked as
74    /// VMO_FROZEN. The resulting VMO is read-only.
75    ///
76    /// Failure
77    /// This function returns `None` for failure. That can happen for
78    /// a few reasons.
79    ///   1) It is a semantic error to freeze a VMO while an atomic transaction
80    ///      is in progress, because that transaction is supposed to be atomic.
81    ///   2) VMO errors. This can include running out of space or debug assertions.
82    ///
83    /// Note: the generation count for the original VMO is updated immediately. Since
84    /// the new VMO is page-by-page copy-on-write, at least the first page of the
85    /// VMO will immediately do a true copy. The practical implications of this
86    /// depend on implementation details like how large a VMO is versus page size.
87    pub fn frozen_vmo_copy(&self) -> Result<zx::Vmo, Error> {
88        if let Some(state) = self.state() {
89            let mut guard = state.try_lock()?;
90            guard.frozen_vmo_copy()
91        } else if let Some(vmo) = self.storage.as_ref() {
92            if is_vmo_frozen(vmo) {
93                // No-op Inspector was created with a frozen VMO, duplicate it.
94                vmo.duplicate_handle(
95                    // TODO(https://github.com/rust-lang/rust/issues/143802): Make this a const
96                    // and share with duplicate_vmo().
97                    zx::Rights::BASIC
98                        | zx::Rights::READ
99                        | zx::Rights::MAP
100                        | zx::Rights::GET_PROPERTY,
101                )
102                .map_err(Error::DuplicateVmoFailed)
103            } else {
104                // No-op Inspector was created with a non-frozen VMO; we can't
105                // freeze it since we don't have write rights to the VMO.
106                Err(Error::VmoNotFrozen)
107            }
108        } else {
109            Err(Error::VmoMissing)
110        }
111    }
112
113    /// Returns a VMO holding a copy of the data in this inspector.
114    ///
115    /// The copied VMO will be read-only.
116    pub fn copy_vmo(&self) -> Option<zx::Vmo> {
117        self.copy_vmo_data().and_then(|data| {
118            if let Ok(vmo) = zx::Vmo::create(data.len() as u64) {
119                vmo.write(&data, 0).ok().map(|_| vmo)
120            } else {
121                None
122            }
123        })
124    }
125
126    pub(crate) fn get_storage_handle(&self) -> Option<Arc<zx::Vmo>> {
127        // We can'just share a reference to the underlying vec<u8> storage, so we copy the data
128        self.storage.clone()
129    }
130
131    #[cfg(test)]
132    pub(crate) fn is_frozen(&self) -> bool {
133        if let Some(vmo) = self.storage.as_ref() {
134            return is_vmo_frozen(vmo);
135        }
136        false
137    }
138}
139
140#[cfg(target_os = "fuchsia")]
141fn is_vmo_frozen(vmo: impl AsRef<zx::Vmo>) -> bool {
142    let mut buffer: [u8; 32] = [0; 32];
143    if vmo.as_ref().read(&mut buffer, 0).is_ok() {
144        let block = buffer.block_at_unchecked::<Header>(inspect_format::BlockIndex::EMPTY);
145        return block.generation_count() == inspect_format::constants::VMO_FROZEN;
146    }
147    false
148}
149
150#[cfg(not(target_os = "fuchsia"))]
151impl Inspector {
152    pub(crate) fn duplicate_vmo(&self) -> Option<<Container as BlockContainer>::Data> {
153        // We don't support getting a duplicate handle to the data on the host so we lock and copy
154        // the udnerlying data.
155        self.copy_vmo_data()
156    }
157
158    pub(crate) fn get_storage_handle(&self) -> Option<Vec<u8>> {
159        // We can'just share a reference to the underlying vec<u8> storage, so we copy the data
160        self.copy_vmo_data()
161    }
162}
163
164impl Default for Inspector {
165    fn default() -> Self {
166        Inspector::new(InspectorConfig::default())
167    }
168}
169
170impl Inspector {
171    /// Initializes a new Inspect VMO object with the
172    /// [`default maximum size`][constants::DEFAULT_VMO_SIZE_BYTES].
173    pub fn new(conf: InspectorConfig) -> Self {
174        conf.build()
175    }
176
177    /// Returns a copy of the bytes stored in the VMO for this inspector.
178    ///
179    /// The output will be truncated to only those bytes that are needed to accurately read the
180    /// stored data.
181    pub fn copy_vmo_data(&self) -> Option<Vec<u8>> {
182        self.root_node.inner.inner_ref().and_then(|inner_ref| inner_ref.state.copy_vmo_bytes())
183    }
184
185    pub fn max_size(&self) -> Option<usize> {
186        self.state()?.try_lock().ok().map(|state| state.stats().maximum_size)
187    }
188
189    /// True if the Inspector was created successfully (it's not No-Op)
190    pub fn is_valid(&self) -> bool {
191        // It is only necessary to check the root_node, because:
192        //   1) If the Inspector was created as a no-op, the root node is not valid.
193        //   2) If the creation of the Inspector failed, then the root_node is invalid. This
194        //      is because `Inspector::new_root` returns the VMO and root node as a pair.
195        self.root_node.is_valid()
196    }
197
198    /// Returns the root node of the inspect hierarchy.
199    pub fn root(&self) -> &Node {
200        &self.root_node
201    }
202
203    /// Takes a function to execute as under a single lock of the Inspect VMO. This function
204    /// receives a reference to the root of the inspect hierarchy.
205    pub fn atomic_update<F, R>(&self, update_fn: F) -> R
206    where
207        F: FnOnce(&Node) -> R,
208    {
209        self.root().atomic_update(update_fn)
210    }
211
212    pub(crate) fn state(&self) -> Option<State> {
213        self.root().inner.inner_ref().map(|inner_ref| inner_ref.state.clone())
214    }
215}
216
217/// Classic builder pattern object for constructing an `Inspector`.
218pub struct InspectorConfig {
219    is_no_op: bool,
220    size: usize,
221    storage: Option<Arc<<Container as BlockContainer>::ShareableData>>,
222}
223
224impl Default for InspectorConfig {
225    /// A default Inspector:
226    ///     * Fully functional
227    ///     * Size: `constants::DEFAULT_VMO_SIZE_BYTES`
228    ///
229    /// Because the default is so cheap to construct, there is
230    /// no "empty" `InspectorConfig`.
231    fn default() -> Self {
232        Self { is_no_op: false, size: constants::DEFAULT_VMO_SIZE_BYTES, storage: None }
233    }
234}
235
236impl InspectorConfig {
237    /// A read-only Inspector.
238    pub fn no_op(mut self) -> Self {
239        self.is_no_op = true;
240        self
241    }
242
243    /// Size of the VMO.
244    pub fn size(mut self, max_size: usize) -> Self {
245        self.size = max_size;
246        self
247    }
248
249    fn create_no_op(self) -> Inspector {
250        Inspector { storage: self.storage, root_node: Arc::new(Node::new_no_op()) }
251    }
252
253    fn adjusted_buffer_size(max_size: usize) -> usize {
254        let mut size = max(constants::MINIMUM_VMO_SIZE_BYTES, max_size);
255        // If the size is not a multiple of 4096, round up.
256        if !size.is_multiple_of(constants::MINIMUM_VMO_SIZE_BYTES) {
257            size =
258                (1 + size / constants::MINIMUM_VMO_SIZE_BYTES) * constants::MINIMUM_VMO_SIZE_BYTES;
259        }
260
261        size
262    }
263}
264
265#[cfg(target_os = "fuchsia")]
266impl InspectorConfig {
267    /// An Inspector with a readable VMO.
268    /// Implicitly no-op.
269    pub fn vmo(mut self, vmo: zx::Vmo) -> Self {
270        self.storage = Some(Arc::new(vmo));
271        self.no_op()
272    }
273
274    fn build(self) -> Inspector {
275        if self.is_no_op {
276            return self.create_no_op();
277        }
278
279        match Self::new_root(self.size) {
280            Ok((storage, root_node)) => {
281                Inspector { storage: Some(storage), root_node: Arc::new(root_node) }
282            }
283            Err(e) => {
284                error!("Failed to create root node. Error: {:?}", e);
285                self.create_no_op()
286            }
287        }
288    }
289
290    /// Allocates a new VMO and initializes it.
291    fn new_root(
292        max_size: usize,
293    ) -> Result<(Arc<<Container as BlockContainer>::ShareableData>, Node), Error> {
294        let size = Self::adjusted_buffer_size(max_size);
295        let (container, vmo) = Container::read_and_write(size).map_err(Error::AllocateVmo)?;
296        let name = zx::Name::new("InspectHeap").unwrap();
297        vmo.set_name(&name).map_err(Error::AllocateVmo)?;
298        let vmo = Arc::new(vmo);
299        let heap = Heap::new(container).map_err(|e| Error::CreateHeap(Box::new(e)))?;
300        let state =
301            State::create(heap, vmo.clone()).map_err(|e| Error::CreateState(Box::new(e)))?;
302        Ok((vmo, Node::new_root(state)))
303    }
304}
305
306#[cfg(not(target_os = "fuchsia"))]
307impl InspectorConfig {
308    fn build(self) -> Inspector {
309        if self.is_no_op {
310            return self.create_no_op();
311        }
312
313        match Self::new_root(self.size) {
314            Ok((root_node, storage)) => {
315                Inspector { storage: Some(storage), root_node: Arc::new(root_node) }
316            }
317            Err(e) => {
318                error!("Failed to create root node. Error: {:?}", e);
319                self.create_no_op()
320            }
321        }
322    }
323
324    fn new_root(
325        max_size: usize,
326    ) -> Result<(Node, Arc<<Container as BlockContainer>::ShareableData>), Error> {
327        let size = Self::adjusted_buffer_size(max_size);
328        let (container, storage) = Container::read_and_write(size).unwrap();
329        let heap = Heap::new(container).map_err(|e| Error::CreateHeap(Box::new(e)))?;
330        let state =
331            State::create(heap, Arc::new(storage)).map_err(|e| Error::CreateState(Box::new(e)))?;
332        Ok((Node::new_root(state), Arc::new(storage)))
333    }
334}
335
336#[cfg(test)]
337mod tests {
338    use super::*;
339    use crate::assert_update_is_atomic;
340
341    #[fuchsia::test]
342    fn inspector_new() {
343        let test_object = Inspector::default();
344        assert_eq!(test_object.max_size().unwrap(), constants::DEFAULT_VMO_SIZE_BYTES);
345    }
346
347    #[fuchsia::test]
348    fn inspector_copy_data() {
349        let test_object = Inspector::default();
350
351        assert_eq!(test_object.max_size().unwrap(), constants::DEFAULT_VMO_SIZE_BYTES);
352
353        // The copy will be a single page, since that is all that is used.
354        assert_eq!(test_object.copy_vmo_data().unwrap().len(), 4096);
355    }
356
357    #[fuchsia::test]
358    fn no_op() {
359        let inspector = Inspector::new(InspectorConfig::default().size(4096));
360        // Make the VMO full.
361        let nodes = (0..84)
362            .map(|i| inspector.root().create_child(format!("test-{i}")))
363            .collect::<Vec<Node>>();
364
365        assert!(nodes.iter().all(|node| node.is_valid()));
366        let no_op_node = inspector.root().create_child("no-op-child");
367        assert!(!no_op_node.is_valid());
368    }
369
370    #[fuchsia::test]
371    fn inspector_new_with_size() {
372        let test_object = Inspector::new(InspectorConfig::default().size(8192));
373        assert_eq!(test_object.max_size().unwrap(), 8192);
374
375        // If size is not a multiple of 4096, it'll be rounded up.
376        let test_object = Inspector::new(InspectorConfig::default().size(10000));
377        assert_eq!(test_object.max_size().unwrap(), 12288);
378
379        // If size is less than the minimum size, the minimum will be set.
380        let test_object = Inspector::new(InspectorConfig::default().size(2000));
381        assert_eq!(test_object.max_size().unwrap(), 4096);
382    }
383
384    #[fuchsia::test]
385    async fn atomic_update() {
386        let insp = Inspector::default();
387        assert_update_is_atomic!(insp, |n| {
388            n.record_int("", 1);
389            n.record_int("", 2);
390            n.record_uint("", 3);
391            n.record_string("", "abcd");
392        });
393    }
394}
395
396// These tests exercise Fuchsia-specific APIs for Inspector.
397#[cfg(all(test, target_os = "fuchsia"))]
398mod fuchsia_tests {
399    use super::*;
400    use assert_matches::assert_matches;
401
402    #[fuchsia::test]
403    fn inspector_duplicate_vmo() {
404        let test_object = Inspector::default();
405        assert_eq!(
406            test_object.storage.as_ref().unwrap().get_size().unwrap(),
407            constants::DEFAULT_VMO_SIZE_BYTES as u64
408        );
409        assert_eq!(
410            test_object.duplicate_vmo().unwrap().get_size().unwrap(),
411            constants::DEFAULT_VMO_SIZE_BYTES as u64
412        );
413    }
414
415    #[fuchsia::test]
416    fn inspector_new_root() {
417        // Note, the small size we request should be rounded up to a full 4kB page.
418        let (vmo, root_node) = InspectorConfig::new_root(100).unwrap();
419        assert_eq!(vmo.get_size().unwrap(), 4096);
420        let inner = root_node.inner.inner_ref().unwrap();
421        assert_eq!(*inner.block_index, 0);
422        assert_eq!("InspectHeap", vmo.get_name().expect("Has name"));
423    }
424
425    #[fuchsia::test]
426    fn freeze_vmo_works() {
427        let inspector = Inspector::default();
428        let initial =
429            inspector.state().unwrap().with_current_header(|header| header.generation_count());
430        let vmo = inspector.frozen_vmo_copy();
431
432        assert!(!inspector.is_frozen());
433
434        let mut buffer: [u8; 32] = [0; 32];
435        inspector.storage.as_ref().unwrap().read(&mut buffer, 0).unwrap();
436        let block = buffer.block_at_unchecked::<Header>(inspect_format::BlockIndex::EMPTY);
437        let gen_count = block.generation_count();
438
439        assert_eq!(initial + 2, gen_count);
440        assert!(gen_count.is_multiple_of(2));
441
442        let frozen_insp = Inspector::new(InspectorConfig::default().no_op().vmo(vmo.unwrap()));
443        assert!(frozen_insp.is_frozen());
444    }
445
446    #[fuchsia::test]
447    fn no_op_inspector_with_frozen_vmo_can_be_frozen() {
448        const VMO_SIZE: u64 = 4096;
449
450        let original_inspector = Inspector::new(InspectorConfig::default().size(VMO_SIZE as usize));
451        let frozen_vmo = original_inspector.frozen_vmo_copy().unwrap();
452        let inspector = Inspector::new(InspectorConfig::default().no_op().vmo(frozen_vmo));
453
454        assert_matches!(inspector.state(), None);
455        assert_matches!(inspector.frozen_vmo_copy(), Ok(copy) => {
456            assert_eq!(copy.get_size().unwrap(), VMO_SIZE);
457        });
458    }
459
460    #[fuchsia::test]
461    fn no_op_inspector_with_non_frozen_vmo_cannot_be_frozen() {
462        let inspect_vmo = zx::Vmo::create(4096).unwrap();
463        let inspector = Inspector::new(InspectorConfig::default().no_op().vmo(inspect_vmo));
464
465        assert_matches!(inspector.state(), None);
466        assert_matches!(inspector.frozen_vmo_copy(), Err(Error::VmoNotFrozen));
467    }
468
469    #[fuchsia::test]
470    fn transactions_block_freezing() {
471        let inspector = Inspector::default();
472        inspector.atomic_update(|_| assert!(inspector.frozen_vmo_copy().is_err()));
473    }
474
475    #[fuchsia::test]
476    fn transactions_block_copying() {
477        let inspector = Inspector::default();
478        inspector.atomic_update(|_| assert!(inspector.copy_vmo().is_none()));
479        inspector.atomic_update(|_| assert!(inspector.copy_vmo_data().is_none()));
480    }
481
482    #[fuchsia::test]
483    fn inspector_new_with_size() {
484        let test_object = Inspector::new(InspectorConfig::default().size(8192));
485        assert_eq!(test_object.max_size().unwrap(), 8192);
486
487        assert_eq!(
488            "InspectHeap",
489            test_object.storage.as_ref().unwrap().get_name().expect("Has name")
490        );
491
492        // If size is not a multiple of 4096, it'll be rounded up.
493        let test_object = Inspector::new(InspectorConfig::default().size(10000));
494        assert_eq!(test_object.max_size().unwrap(), 12288);
495
496        // If size is less than the minimum size, the minimum will be set.
497        let test_object = Inspector::new(InspectorConfig::default().size(2000));
498        assert_eq!(test_object.max_size().unwrap(), 4096);
499    }
500}