zx/
info.rs

1// Copyright 2018 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//! Type-safe bindings for Zircon object information.
6
7use crate::{ok, sys, HandleRef, Status};
8use std::mem::MaybeUninit;
9use std::ops::Deref;
10use zerocopy::{FromBytes, Immutable};
11
12// Tuning constants for get_info_vec(). pub(crate) to support unit tests.
13pub(crate) const INFO_VEC_SIZE_INITIAL: usize = 16;
14const INFO_VEC_SIZE_PAD: usize = 2;
15
16#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
17#[repr(transparent)]
18pub struct Topic(sys::zx_object_info_topic_t);
19
20impl Deref for Topic {
21    type Target = sys::zx_object_info_topic_t;
22
23    fn deref(&self) -> &Self::Target {
24        &self.0
25    }
26}
27
28/// A query to get info about a zircon object.
29///
30/// # Safety
31///
32/// `InfoTy` must be the same size as what the kernel expects to return for the provided topic.
33pub(crate) unsafe trait ObjectQuery {
34    /// A `Topic` identifying this query.
35    const TOPIC: Topic;
36    /// The datatype returned by querying for Self::TOPIC.
37    type InfoTy: FromBytes + Immutable;
38}
39
40assoc_values!(Topic, [
41    NONE = sys::ZX_INFO_NONE;
42    HANDLE_VALID = sys::ZX_INFO_HANDLE_VALID;
43    HANDLE_BASIC = sys::ZX_INFO_HANDLE_BASIC;
44    PROCESS = sys::ZX_INFO_PROCESS;
45    PROCESS_THREADS = sys::ZX_INFO_PROCESS_THREADS;
46    VMAR = sys::ZX_INFO_VMAR;
47    VMAR_MAPS = sys::ZX_INFO_VMAR_MAPS;
48    JOB_CHILDREN = sys::ZX_INFO_JOB_CHILDREN;
49    JOB_PROCESSES = sys::ZX_INFO_JOB_PROCESSES;
50    THREAD = sys::ZX_INFO_THREAD;
51    THREAD_EXCEPTION_REPORT = sys::ZX_INFO_THREAD_EXCEPTION_REPORT;
52    TASK_STATS = sys::ZX_INFO_TASK_STATS;
53    TASK_RUNTIME = sys::ZX_INFO_TASK_RUNTIME;
54    PROCESS_MAPS = sys::ZX_INFO_PROCESS_MAPS;
55    PROCESS_VMOS = sys::ZX_INFO_PROCESS_VMOS;
56    THREAD_STATS = sys::ZX_INFO_THREAD_STATS;
57    CPU_STATS = sys::ZX_INFO_CPU_STATS;
58    KMEM_STATS = sys::ZX_INFO_KMEM_STATS;
59    KMEM_STATS_EXTENDED = sys::ZX_INFO_KMEM_STATS_EXTENDED;
60    KMEM_STATS_COMPRESSION = sys::ZX_INFO_KMEM_STATS_COMPRESSION;
61    RESOURCE = sys::ZX_INFO_RESOURCE;
62    HANDLE_COUNT = sys::ZX_INFO_HANDLE_COUNT;
63    BTI = sys::ZX_INFO_BTI;
64    PROCESS_HANDLE_STATS = sys::ZX_INFO_PROCESS_HANDLE_STATS;
65    SOCKET = sys::ZX_INFO_SOCKET;
66    TIMER = sys::ZX_INFO_TIMER;
67    VMO = sys::ZX_INFO_VMO;
68    JOB = sys::ZX_INFO_JOB;
69    IOB = sys::ZX_INFO_IOB;
70    IOB_REGIONS = sys::ZX_INFO_IOB_REGIONS;
71    MEMORY_STALL = sys::ZX_INFO_MEMORY_STALL;
72    CLOCK_MAPPED_SIZE = sys::ZX_INFO_CLOCK_MAPPED_SIZE;
73]);
74
75/// Query information about a zircon object. Returns a valid slice and any remaining capacity on
76/// success, along with a count of how many infos the kernel had available.
77pub(crate) fn object_get_info<'a, Q: ObjectQuery>(
78    handle: HandleRef<'_>,
79    out: &'a mut [MaybeUninit<Q::InfoTy>],
80) -> Result<(&'a mut [Q::InfoTy], &'a mut [MaybeUninit<Q::InfoTy>], usize), Status>
81where
82    Q::InfoTy: FromBytes + Immutable,
83{
84    let mut actual = 0;
85    let mut avail = 0;
86
87    // SAFETY: The slice pointer is known valid to write to for `size_of_val` because it came from
88    // a mutable reference.
89    let status = unsafe {
90        sys::zx_object_get_info(
91            handle.raw_handle(),
92            *Q::TOPIC,
93            out.as_mut_ptr().cast::<u8>(),
94            std::mem::size_of_val(out),
95            &mut actual,
96            &mut avail,
97        )
98    };
99    ok(status)?;
100
101    let (initialized, uninit) = out.split_at_mut(actual);
102
103    // TODO(https://fxbug.dev/352398385) switch to MaybeUninit::slice_assume_init_mut
104    // SAFETY: these values have been initialized by the kernel and implement the right zerocopy
105    // traits to be instantiated from arbitrary bytes.
106    let initialized: &mut [Q::InfoTy] = unsafe {
107        std::slice::from_raw_parts_mut(
108            initialized.as_mut_ptr().cast::<Q::InfoTy>(),
109            initialized.len(),
110        )
111    };
112
113    Ok((initialized, uninit, avail))
114}
115
116/// Query information about a zircon object, expecting only a single info in the return.
117pub(crate) fn object_get_info_single<Q: ObjectQuery>(
118    handle: HandleRef<'_>,
119) -> Result<Q::InfoTy, Status>
120where
121    Q::InfoTy: Copy + FromBytes + Immutable,
122{
123    let mut info = MaybeUninit::<Q::InfoTy>::uninit();
124    let (info, _, _) = object_get_info::<Q>(handle, std::slice::from_mut(&mut info))?;
125    Ok(info[0])
126}
127
128/// Query multiple records of information about a zircon object.
129/// Returns a vec of Q::InfoTy on success.
130/// Intended for calls that return multiple small objects.
131pub(crate) fn object_get_info_vec<Q: ObjectQuery>(
132    handle: HandleRef<'_>,
133) -> Result<Vec<Q::InfoTy>, Status> {
134    // Start with a few slots
135    let mut out = Vec::<Q::InfoTy>::with_capacity(INFO_VEC_SIZE_INITIAL);
136    loop {
137        let (init, _uninit, avail) =
138            object_get_info::<Q>(handle.clone(), out.spare_capacity_mut())?;
139        let num_initialized = init.len();
140        if num_initialized == avail {
141            // SAFETY: the kernel has initialized all of these values.
142            unsafe { out.set_len(num_initialized) };
143            return Ok(out);
144        } else {
145            // The number of records may increase between retries; reserve space for that.
146            // TODO(https://fxbug.dev/384531846) grow more conservatively
147            let needed_space = avail * INFO_VEC_SIZE_PAD;
148            if let Some(to_grow) = needed_space.checked_sub(out.capacity()) {
149                out.reserve_exact(to_grow);
150            }
151
152            // We may ask the kernel to copy more than a page in the next iteration of this loop, so
153            // prefault each of the pages in the region.
154            //
155            // Currently Zircon has a very slow path when performing large usercopies into freshly
156            // allocated buffers. In practice our large heap allocated buffers are typically newly
157            // mapped and do not yet have any pages faulted in. Unlike a copy performed by
158            // userspace, Zircon itself has to handle any page faults and in order to avoid
159            // deadlocking with user pagers (among other issues), it will drop locks it holds,
160            // service the page fault, and then reacquire the locks (see linked bug below).
161            //
162            // In order to avoid hitting this slow path, we want to ensure that all of the pages of
163            // our Vec have already been faulted in before we hand off to the kernel to write data
164            // into them. Ideally the kernel would handle this for us but in the meantime we can
165            // shave a lot of time from the heaviest zx_object_get_info calls by faulting the pages
166            // ourselves.
167            //
168            // We only want to do this when the bindings themselves are responsible for creating the
169            // vector, because callers who manage their own buffer for the syscall may be doing
170            // their own page management for the buffer and we don't want to penalize them.
171            // TODO(https://fxbug.dev/383401884) remove once zircon prefaults get_info usercopies
172            // TODO(https://fxbug.dev/384941113) consider zx_vmar_op_range on root vmar instead
173            let maybe_unfaulted = out.spare_capacity_mut();
174
175            // SAFETY: zerocopy::FromBytes means it's OK to write arbitrary bytes to the slice.
176            // TODO(https://github.com/rust-lang/rust/issues/93092) use MaybeUninit::slice_as_bytes_mut
177            let maybe_unfaulted_bytes: &mut [MaybeUninit<u8>] = unsafe {
178                std::slice::from_raw_parts_mut(
179                    maybe_unfaulted.as_mut_ptr().cast::<MaybeUninit<u8>>(),
180                    std::mem::size_of_val(maybe_unfaulted),
181                )
182            };
183
184            // chunks_mut doesn't give us page alignment but that doesn't matter for pre-faulting.
185            for page in maybe_unfaulted_bytes.chunks_mut(crate::system_get_page_size() as usize) {
186                // This writes a single byte to each page to avoid unnecessary memory traffic. A
187                // non-zero byte is written so that these pages won't get picked up by Zircon's
188                // zero page scanner.
189                page[0] = MaybeUninit::new(1u8);
190            }
191        }
192    }
193}