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