1use crate::inspect::container::InspectHandle;
6use diagnostics_data::InspectHandleName;
7use fidl::endpoints::{DiscoverableProtocolMarker, Proxy};
8use fidl_fuchsia_inspect::{TreeMarker, TreeProxy};
9use fidl_fuchsia_inspect_deprecated::{InspectMarker, InspectProxy};
10use fidl_fuchsia_io as fio;
11use futures::stream::StreamExt;
12use log::error;
13use std::pin::pin;
14use std::sync::{Arc, Weak};
15
16pub type InspectHandleDeque = std::collections::VecDeque<(Option<InspectHandleName>, InspectData)>;
18
19#[derive(Debug)]
22pub enum InspectData {
23 Vmo { data: Arc<zx::Vmo>, escrowed: bool },
25
26 File(Vec<u8>),
31
32 Tree(TreeProxy),
34
35 DeprecatedFidl(InspectProxy),
37}
38
39fn maybe_load_service<P: DiscoverableProtocolMarker>(
40 dir_proxy: &fio::DirectoryProxy,
41 entry: &fuchsia_fs::directory::DirEntry,
42) -> Result<Option<P::Proxy>, anyhow::Error> {
43 if entry.name.ends_with(P::PROTOCOL_NAME) {
44 let (proxy, server) = fidl::endpoints::create_proxy::<P>();
45 fdio::service_connect_at(
46 dir_proxy.as_channel().as_ref(),
47 &entry.name,
48 server.into_channel(),
49 )?;
50 return Ok(Some(proxy));
51 }
52 Ok(None)
53}
54
55pub async fn populate_data_map(inspect_handles: &[Weak<InspectHandle>]) -> InspectHandleDeque {
56 let mut data_map = InspectHandleDeque::new();
57 for inspect_handle in inspect_handles {
58 let Some(handle) = inspect_handle.upgrade() else {
59 continue;
60 };
61 match handle.as_ref() {
62 InspectHandle::Directory { proxy: ref dir } => {
63 return populate_data_map_from_dir(dir).await;
64 }
65 InspectHandle::Tree { proxy, name } => {
66 data_map.push_back((
67 name.as_ref().map(|name| InspectHandleName::name(name.clone())),
68 InspectData::Tree(proxy.clone()),
69 ));
70 }
71 InspectHandle::Escrow { vmo, name, .. } => {
72 data_map.push_back((
73 name.as_ref().map(|name| InspectHandleName::name(name.clone())),
74 InspectData::Vmo { data: Arc::clone(vmo), escrowed: true },
75 ));
76 }
77 }
78 }
79
80 data_map
81}
82
83async fn populate_data_map_from_dir(inspect_proxy: &fio::DirectoryProxy) -> InspectHandleDeque {
86 let mut entries = pin!(
89 fuchsia_fs::directory::readdir_recursive(inspect_proxy, None).filter_map(
90 |result| {
91 async move {
92 result.ok()
95 }
96 }
97 )
98 );
99 let mut data_map = InspectHandleDeque::new();
100 while let Some(entry) = entries.next().await {
102 if let Ok(Some(proxy)) = maybe_load_service::<TreeMarker>(inspect_proxy, &entry) {
105 data_map.push_back((
106 Some(InspectHandleName::filename(entry.name)),
107 InspectData::Tree(proxy),
108 ));
109 continue;
110 }
111
112 if let Ok(Some(proxy)) = maybe_load_service::<InspectMarker>(inspect_proxy, &entry) {
113 data_map.push_back((
114 Some(InspectHandleName::filename(entry.name)),
115 InspectData::DeprecatedFidl(proxy),
116 ));
117 continue;
118 }
119
120 if !entry.name.ends_with(".inspect")
121 || entry.kind != fuchsia_fs::directory::DirentKind::File
122 {
123 continue;
124 }
125
126 let file_proxy = match fuchsia_fs::directory::open_file_async(
127 inspect_proxy,
128 &entry.name,
129 fio::PERM_READABLE,
130 ) {
131 Ok(proxy) => proxy,
132 Err(_) => {
133 continue;
136 }
137 };
138
139 let vmo = match file_proxy.get_backing_memory(fio::VmoFlags::READ).await {
141 Ok(vmo) => vmo,
142 Err(_) => {
143 continue;
146 }
147 };
148
149 let data = match vmo.map_err(zx::Status::from_raw) {
150 Ok(vmo) => InspectData::Vmo { data: Arc::new(vmo), escrowed: false },
151 Err(err) => {
152 match err {
153 zx::Status::NOT_SUPPORTED => {}
154 err => {
155 error!(
156 file:% = entry.name, err:?;
157 "unexpected error from GetBackingMemory",
158 )
159 }
160 }
161 match fuchsia_fs::file::read(&file_proxy).await {
162 Ok(contents) => InspectData::File(contents),
163 Err(_) => {
164 continue;
167 }
168 }
169 }
170 };
171 data_map.push_back((Some(InspectHandleName::filename(entry.name)), data));
172 }
173
174 data_map
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180 use assert_matches::assert_matches;
181 use diagnostics_assertions::assert_data_tree;
182 use fidl::endpoints::create_request_stream;
183 use fuchsia_async as fasync;
184 use fuchsia_component::server::ServiceFs;
185 use fuchsia_inspect::{Inspector, reader};
186 use inspect_runtime::TreeServerSendPreference;
187 use inspect_runtime::service::spawn_tree_server_with_stream;
188 use zx::Peered;
189
190 fn get_vmo(text: &[u8]) -> zx::Vmo {
191 let vmo = zx::Vmo::create(4096).unwrap();
192 vmo.write(text, 0).unwrap();
193 vmo
194 }
195
196 #[fuchsia::test]
197 async fn populate_data_map_with_trees() {
198 let insp1 = Inspector::default();
199 let insp2 = Inspector::default();
200 let insp3 = Inspector::default();
201
202 insp1.root().record_int("one", 1);
203 insp2.root().record_int("two", 2);
204 insp3.root().record_int("three", 3);
205
206 let scope = fasync::Scope::new();
207
208 let (tree1, request_stream) = create_request_stream::<TreeMarker>();
209 spawn_tree_server_with_stream(
210 insp1,
211 TreeServerSendPreference::default(),
212 request_stream,
213 &scope,
214 );
215 let (tree2, request_stream) = create_request_stream::<TreeMarker>();
216 spawn_tree_server_with_stream(
217 insp2,
218 TreeServerSendPreference::default(),
219 request_stream,
220 &scope,
221 );
222 let (tree3, request_stream) = create_request_stream::<TreeMarker>();
223 spawn_tree_server_with_stream(
224 insp3,
225 TreeServerSendPreference::default(),
226 request_stream,
227 &scope,
228 );
229
230 let name1 = Some(InspectHandleName::name("tree1"));
231 let name2 = Some(InspectHandleName::name("tree2"));
232 let name3 = None;
233
234 let handles = [
235 Arc::new(InspectHandle::Tree { proxy: tree1.into_proxy(), name: Some("tree1".into()) }),
236 Arc::new(InspectHandle::Tree { proxy: tree2.into_proxy(), name: Some("tree2".into()) }),
237 Arc::new(InspectHandle::Tree { proxy: tree3.into_proxy(), name: None }),
238 ];
239 let data = populate_data_map(&handles.iter().map(Arc::downgrade).collect::<Vec<_>>()).await;
240 assert_eq!(data.len(), 3);
241
242 let (_, tree1) = data.iter().find(|(n, _)| *n == name1).unwrap();
243
244 assert_matches!(tree1, InspectData::Tree(t) => {
245 let h = reader::read(t).await.unwrap();
246 assert_data_tree!(h, root: {
247 one: 1i64,
248 });
249 });
250
251 let (_, tree2) = data.iter().find(|(n, _)| *n == name2).unwrap();
252
253 assert_matches!(tree2, InspectData::Tree(t) => {
254 let h = reader::read(t).await.unwrap();
255 assert_data_tree!(h, root: {
256 two: 2i64,
257 });
258 });
259
260 let (_, tree3) = data.iter().find(|(n, _)| *n == name3).unwrap();
261
262 assert_matches!(tree3, InspectData::Tree(t) => {
263 let h = reader::read(t).await.unwrap();
264 assert_data_tree!(h, root: {
265 three: 3i64,
266 });
267 });
268 }
269
270 #[fuchsia::test]
271 async fn inspect_data_collector() {
272 let path = "/test-bindings/out";
273 let mut fs = ServiceFs::new();
276 let vmo = get_vmo(b"test1");
277 let vmo2 = get_vmo(b"test2");
278 let vmo3 = get_vmo(b"test3");
279 let vmo4 = get_vmo(b"test4");
280 fs.dir("diagnostics").add_vmo_file_at("root.inspect", vmo);
281 fs.dir("diagnostics").add_vmo_file_at("root_not_inspect", vmo2);
282 fs.dir("diagnostics").dir("a").add_vmo_file_at("root.inspect", vmo3);
283 fs.dir("diagnostics").dir("b").add_vmo_file_at("root.inspect", vmo4);
284 let (h0, h1) = fidl::endpoints::create_endpoints();
286 fs.serve_connection(h1).unwrap();
287
288 let ns = fdio::Namespace::installed().unwrap();
289 ns.bind(path, h0).unwrap();
290
291 fasync::Task::spawn(fs.collect()).detach();
292
293 let (done0, done1) = zx::Channel::create();
294
295 std::thread::spawn(move || {
298 let done = done1;
299 let mut executor = fasync::LocalExecutor::default();
300
301 executor.run_singlethreaded(async {
302 let inspect_proxy = Arc::new(InspectHandle::directory(
303 fuchsia_fs::directory::open_in_namespace(
304 &format!("{path}/diagnostics"),
305 fio::PERM_READABLE,
306 )
307 .expect("Failed to open directory"),
308 ));
309 let extra_data = populate_data_map(&[Arc::downgrade(&inspect_proxy)]).await;
310 assert_eq!(3, extra_data.len());
311
312 let assert_extra_data = |path: &str, content: &[u8]| {
313 let (_, extra) = extra_data
314 .iter()
315 .find(|(n, _)| *n == Some(InspectHandleName::filename(path)))
316 .unwrap();
317
318 match extra {
319 InspectData::Vmo { data: vmo, escrowed: _ } => {
320 let mut buf = [0u8; 5];
321 vmo.read(&mut buf, 0).expect("reading vmo");
322 assert_eq!(content, &buf);
323 }
324 v => {
325 panic!("Expected Vmo, got {v:?}");
326 }
327 }
328 };
329
330 assert_extra_data("root.inspect", b"test1");
331 assert_extra_data("a/root.inspect", b"test3");
332 assert_extra_data("b/root.inspect", b"test4");
333
334 done.signal_peer(zx::Signals::NONE, zx::Signals::USER_0).expect("signalling peer");
335 });
336 });
337
338 fasync::OnSignals::new(&done0, zx::Signals::USER_0).await.unwrap();
339 ns.unbind(path).unwrap();
340 }
341}