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 =
89 pin!(fuchsia_fs::directory::readdir_recursive(inspect_proxy, None)
90 .filter_map(|result| {
91 async move {
92 result.ok()
95 }
96 }));
97 let mut data_map = InspectHandleDeque::new();
98 while let Some(entry) = entries.next().await {
100 if let Ok(Some(proxy)) = maybe_load_service::<TreeMarker>(inspect_proxy, &entry) {
103 data_map.push_back((
104 Some(InspectHandleName::filename(entry.name)),
105 InspectData::Tree(proxy),
106 ));
107 continue;
108 }
109
110 if let Ok(Some(proxy)) = maybe_load_service::<InspectMarker>(inspect_proxy, &entry) {
111 data_map.push_back((
112 Some(InspectHandleName::filename(entry.name)),
113 InspectData::DeprecatedFidl(proxy),
114 ));
115 continue;
116 }
117
118 if !entry.name.ends_with(".inspect")
119 || entry.kind != fuchsia_fs::directory::DirentKind::File
120 {
121 continue;
122 }
123
124 let file_proxy = match fuchsia_fs::directory::open_file_async(
125 inspect_proxy,
126 &entry.name,
127 fio::PERM_READABLE,
128 ) {
129 Ok(proxy) => proxy,
130 Err(_) => {
131 continue;
134 }
135 };
136
137 let vmo = match file_proxy.get_backing_memory(fio::VmoFlags::READ).await {
139 Ok(vmo) => vmo,
140 Err(_) => {
141 continue;
144 }
145 };
146
147 let data = match vmo.map_err(zx::Status::from_raw) {
148 Ok(vmo) => InspectData::Vmo { data: Arc::new(vmo), escrowed: false },
149 Err(err) => {
150 match err {
151 zx::Status::NOT_SUPPORTED => {}
152 err => {
153 error!(
154 file:% = entry.name, err:?;
155 "unexpected error from GetBackingMemory",
156 )
157 }
158 }
159 match fuchsia_fs::file::read(&file_proxy).await {
160 Ok(contents) => InspectData::File(contents),
161 Err(_) => {
162 continue;
165 }
166 }
167 }
168 };
169 data_map.push_back((Some(InspectHandleName::filename(entry.name)), data));
170 }
171
172 data_map
173}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178 use assert_matches::assert_matches;
179 use diagnostics_assertions::assert_data_tree;
180 use fidl::endpoints::create_request_stream;
181 use fuchsia_async as fasync;
182 use fuchsia_component::server::ServiceFs;
183 use fuchsia_inspect::{reader, Inspector};
184 use inspect_runtime::service::spawn_tree_server_with_stream;
185 use inspect_runtime::TreeServerSendPreference;
186 use zx::Peered;
187
188 fn get_vmo(text: &[u8]) -> zx::Vmo {
189 let vmo = zx::Vmo::create(4096).unwrap();
190 vmo.write(text, 0).unwrap();
191 vmo
192 }
193
194 #[fuchsia::test]
195 async fn populate_data_map_with_trees() {
196 let insp1 = Inspector::default();
197 let insp2 = Inspector::default();
198 let insp3 = Inspector::default();
199
200 insp1.root().record_int("one", 1);
201 insp2.root().record_int("two", 2);
202 insp3.root().record_int("three", 3);
203
204 let scope = fasync::Scope::new();
205
206 let (tree1, request_stream) = create_request_stream::<TreeMarker>();
207 spawn_tree_server_with_stream(
208 insp1,
209 TreeServerSendPreference::default(),
210 request_stream,
211 &scope,
212 );
213 let (tree2, request_stream) = create_request_stream::<TreeMarker>();
214 spawn_tree_server_with_stream(
215 insp2,
216 TreeServerSendPreference::default(),
217 request_stream,
218 &scope,
219 );
220 let (tree3, request_stream) = create_request_stream::<TreeMarker>();
221 spawn_tree_server_with_stream(
222 insp3,
223 TreeServerSendPreference::default(),
224 request_stream,
225 &scope,
226 );
227
228 let name1 = Some(InspectHandleName::name("tree1"));
229 let name2 = Some(InspectHandleName::name("tree2"));
230 let name3 = None;
231
232 let handles = [
233 Arc::new(InspectHandle::Tree { proxy: tree1.into_proxy(), name: Some("tree1".into()) }),
234 Arc::new(InspectHandle::Tree { proxy: tree2.into_proxy(), name: Some("tree2".into()) }),
235 Arc::new(InspectHandle::Tree { proxy: tree3.into_proxy(), name: None }),
236 ];
237 let data = populate_data_map(&handles.iter().map(Arc::downgrade).collect::<Vec<_>>()).await;
238 assert_eq!(data.len(), 3);
239
240 let (_, tree1) = data.iter().find(|(n, _)| *n == name1).unwrap();
241
242 assert_matches!(tree1, InspectData::Tree(t) => {
243 let h = reader::read(t).await.unwrap();
244 assert_data_tree!(h, root: {
245 one: 1i64,
246 });
247 });
248
249 let (_, tree2) = data.iter().find(|(n, _)| *n == name2).unwrap();
250
251 assert_matches!(tree2, InspectData::Tree(t) => {
252 let h = reader::read(t).await.unwrap();
253 assert_data_tree!(h, root: {
254 two: 2i64,
255 });
256 });
257
258 let (_, tree3) = data.iter().find(|(n, _)| *n == name3).unwrap();
259
260 assert_matches!(tree3, InspectData::Tree(t) => {
261 let h = reader::read(t).await.unwrap();
262 assert_data_tree!(h, root: {
263 three: 3i64,
264 });
265 });
266 }
267
268 #[fuchsia::test]
269 async fn inspect_data_collector() {
270 let path = "/test-bindings/out";
271 let mut fs = ServiceFs::new();
274 let vmo = get_vmo(b"test1");
275 let vmo2 = get_vmo(b"test2");
276 let vmo3 = get_vmo(b"test3");
277 let vmo4 = get_vmo(b"test4");
278 fs.dir("diagnostics").add_vmo_file_at("root.inspect", vmo);
279 fs.dir("diagnostics").add_vmo_file_at("root_not_inspect", vmo2);
280 fs.dir("diagnostics").dir("a").add_vmo_file_at("root.inspect", vmo3);
281 fs.dir("diagnostics").dir("b").add_vmo_file_at("root.inspect", vmo4);
282 let (h0, h1) = fidl::endpoints::create_endpoints();
284 fs.serve_connection(h1).unwrap();
285
286 let ns = fdio::Namespace::installed().unwrap();
287 ns.bind(path, h0).unwrap();
288
289 fasync::Task::spawn(fs.collect()).detach();
290
291 let (done0, done1) = zx::Channel::create();
292
293 std::thread::spawn(move || {
296 let done = done1;
297 let mut executor = fasync::LocalExecutor::new();
298
299 executor.run_singlethreaded(async {
300 let inspect_proxy = Arc::new(InspectHandle::directory(
301 fuchsia_fs::directory::open_in_namespace(
302 &format!("{path}/diagnostics"),
303 fio::PERM_READABLE,
304 )
305 .expect("Failed to open directory"),
306 ));
307 let extra_data = populate_data_map(&[Arc::downgrade(&inspect_proxy)]).await;
308 assert_eq!(3, extra_data.len());
309
310 let assert_extra_data = |path: &str, content: &[u8]| {
311 let (_, extra) = extra_data
312 .iter()
313 .find(|(n, _)| *n == Some(InspectHandleName::filename(path)))
314 .unwrap();
315
316 match extra {
317 InspectData::Vmo { data: vmo, escrowed: _ } => {
318 let mut buf = [0u8; 5];
319 vmo.read(&mut buf, 0).expect("reading vmo");
320 assert_eq!(content, &buf);
321 }
322 v => {
323 panic!("Expected Vmo, got {v:?}");
324 }
325 }
326 };
327
328 assert_extra_data("root.inspect", b"test1");
329 assert_extra_data("a/root.inspect", b"test3");
330 assert_extra_data("b/root.inspect", b"test4");
331
332 done.signal_peer(zx::Signals::NONE, zx::Signals::USER_0).expect("signalling peer");
333 });
334 });
335
336 fasync::OnSignals::new(&done0, zx::Signals::USER_0).await.unwrap();
337 ns.unbind(path).unwrap();
338 }
339}