1use crate::commands::types::DiagnosticsProvider;
6use crate::types::Error;
7use anyhow::anyhow;
8use cm_rust::{ExposeDeclCommon, ExposeSource, SourceName};
9use component_debug::dirs::*;
10use component_debug::realm::*;
11use fidl::endpoints::DiscoverableProtocolMarker;
12use fidl_fuchsia_diagnostics::{All, ArchiveAccessorMarker, Selector, TreeNames};
13use fuchsia_fs::directory;
14use moniker::Moniker;
15use {fidl_fuchsia_io as fio, fidl_fuchsia_sys2 as fsys2};
16
17const ACCESSORS_DICTIONARY: &str = "diagnostics-accessors";
18
19pub async fn connect_accessor<P: DiscoverableProtocolMarker>(
22 moniker: &Moniker,
23 accessor_name: &str,
24 proxy: &fsys2::RealmQueryProxy,
25) -> Result<P::Proxy, Error> {
26 let proxy = connect_to_instance_protocol_at_path::<P>(
27 moniker,
28 OpenDirType::Exposed,
29 &format!("{ACCESSORS_DICTIONARY}/{accessor_name}"),
30 proxy,
31 )
32 .await
33 .map_err(|e| Error::ConnectToProtocol(accessor_name.to_string(), anyhow!("{:?}", e)))?;
34 Ok(proxy)
35}
36
37async fn list_accessors(
38 realm: &fsys2::RealmQueryProxy,
39 moniker: &Moniker,
40) -> Option<Vec<fuchsia_fs::directory::DirEntry>> {
41 let Ok(exposed_dir) = open_instance_directory(moniker, OpenDirType::Exposed, realm).await
42 else {
43 return None;
44 };
45
46 let (dir_proxy, server_end) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
47 exposed_dir
48 .open(
49 ACCESSORS_DICTIONARY,
50 fio::PERM_READABLE,
51 &Default::default(),
52 server_end.into_channel(),
53 )
54 .expect("Open call failed!");
55
56 directory::readdir(&dir_proxy).await.ok()
57}
58
59async fn fuzzy_search(
60 query: &str,
61 realm_query: &fsys2::RealmQueryProxy,
62) -> Result<Instance, Error> {
63 let mut instances = component_debug::query::get_instances_from_query(query, realm_query)
64 .await
65 .map_err(Error::FuzzyMatchRealmQuery)?;
66 if instances.is_empty() {
67 return Err(Error::SearchParameterNotFound(query.to_string()));
68 } else if instances.len() > 1 {
69 return Err(Error::FuzzyMatchTooManyMatches(
70 instances.into_iter().map(|i| i.moniker.to_string()).collect(),
71 ));
72 }
73
74 Ok(instances.pop().unwrap())
75}
76
77pub async fn process_fuzzy_inputs<P: DiagnosticsProvider>(
78 queries: impl IntoIterator<Item = String>,
79 provider: &P,
80) -> Result<Vec<Selector>, Error> {
81 let mut queries = queries.into_iter().peekable();
82 if queries.peek().is_none() {
83 return Ok(vec![]);
84 }
85
86 let realm_query = provider.realm_query();
87 let mut results = vec![];
88 for value in queries {
89 match fuzzy_search(&value, realm_query).await {
90 Err(Error::SearchParameterNotFound(_)) => {
92 if let Ok(selector) = selectors::parse_verbose(&value) {
95 results.push(selector);
96 } else {
97 let selector_string = format!("{value}:root");
103 results.push(
104 selectors::parse_verbose(&selector_string)
105 .map_err(|e| Error::ParseSelector(selector_string, e.into()))?,
106 )
107 }
108 }
109 Err(e) => return Err(e),
110 Ok(instance) => {
111 let selector_string = format!(
112 "{}:root",
113 selectors::sanitize_moniker_for_selectors(instance.moniker.to_string()),
114 );
115 results.push(
116 selectors::parse_verbose(&selector_string)
117 .map_err(|e| Error::ParseSelector(selector_string, e.into()))?,
118 )
119 }
120 }
121 }
122
123 Ok(results)
124}
125
126pub async fn process_component_query_with_partial_selectors<P: DiagnosticsProvider>(
129 component: &str,
130 tree_selectors: impl Iterator<Item = String>,
131 provider: &P,
132) -> Result<Vec<Selector>, Error> {
133 let mut tree_selectors = tree_selectors.into_iter().peekable();
134 let realm_query = provider.realm_query();
135 let instance = fuzzy_search(component, realm_query).await?;
136
137 let mut results = vec![];
138 if tree_selectors.peek().is_none() {
139 let selector_string = format!(
140 "{}:root",
141 selectors::sanitize_moniker_for_selectors(instance.moniker.to_string())
142 );
143 results
144 .push(selectors::parse_verbose(&selector_string).map_err(Error::PartialSelectorHint)?);
145 } else {
146 for s in tree_selectors {
147 let selector_string = format!(
148 "{}:{}",
149 selectors::sanitize_moniker_for_selectors(instance.moniker.to_string()),
150 s
151 );
152 results.push(
153 selectors::parse_verbose(&selector_string).map_err(Error::PartialSelectorHint)?,
154 )
155 }
156 }
157
158 Ok(results)
159}
160
161fn add_tree_name(selector: &mut Selector, tree_name: String) -> Result<(), Error> {
162 match selector.tree_names {
163 None => selector.tree_names = Some(TreeNames::Some(vec![tree_name])),
164 Some(ref mut names) => match names {
165 TreeNames::Some(ref mut names) => {
166 if !names.iter().any(|n| n == &tree_name) {
167 names.push(tree_name)
168 }
169 }
170 TreeNames::All(_) => {}
171 TreeNames::__SourceBreaking { unknown_ordinal } => {
172 let unknown_ordinal = *unknown_ordinal;
173 return Err(Error::InvalidSelector(format!(
174 "selector had invalid TreeNames variant {unknown_ordinal}: {selector:?}",
175 )));
176 }
177 },
178 }
179 Ok(())
180}
181
182pub fn ensure_tree_field_is_set(
187 selectors: &mut Vec<Selector>,
188 tree_name: Option<String>,
189) -> Result<(), Error> {
190 if selectors.is_empty() {
191 let Some(tree_name) = tree_name else {
192 return Ok(());
193 };
194
195 let mut selector = selectors::parse_verbose("**:*").unwrap();
197 selector.tree_names = Some(TreeNames::Some(vec![tree_name]));
198 selectors.push(selector);
199 return Ok(());
200 }
201
202 for selector in selectors.iter_mut() {
203 if let Some(tree_name) = &tree_name {
204 add_tree_name(selector, tree_name.clone())?;
205 } else if selector.tree_names.is_none() {
206 selector.tree_names = Some(TreeNames::All(All {}))
207 }
208 }
209
210 Ok(())
211}
212
213pub async fn get_accessor_selectors(
216 realm_query: &fsys2::RealmQueryProxy,
217) -> Result<Vec<String>, Error> {
218 let mut result = vec![];
219 let instances = get_all_instances(realm_query).await?;
220 for instance in instances {
221 match get_resolved_declaration(&instance.moniker, realm_query).await {
222 Err(GetDeclarationError::InstanceNotFound(_))
223 | Err(GetDeclarationError::InstanceNotResolved(_)) => continue,
224 Err(err) => return Err(err.into()),
225 Ok(decl) => {
226 for capability in decl.capabilities {
227 let capability_name = capability.name().to_string();
228 if capability_name != ACCESSORS_DICTIONARY {
229 continue;
230 }
231 if !decl.exposes.iter().any(|expose| {
232 expose.source_name() == capability.name()
233 && *expose.source() == ExposeSource::Self_
234 }) {
235 continue;
236 }
237
238 let Some(entries) = list_accessors(realm_query, &instance.moniker).await else {
239 continue;
240 };
241
242 for entry in entries {
243 let directory::DirEntry { name, kind: fio::DirentType::Service } = entry
244 else {
245 continue;
246 };
247 if !name.starts_with(ArchiveAccessorMarker::PROTOCOL_NAME) {
249 continue;
250 }
251 result.push(format!("{}:{name}", instance.moniker));
252 }
253 }
254 }
255 }
256 }
257 result.sort();
258 Ok(result)
259}
260
261#[cfg(test)]
262mod test {
263 use super::*;
264 use assert_matches::assert_matches;
265 use iquery_test_support::{MockRealmQuery, MockRealmQueryBuilder};
266 use selectors::parse_verbose;
267 use std::rc::Rc;
268
269 #[fuchsia::test]
270 async fn test_get_accessors() {
271 let fake_realm_query = Rc::new(MockRealmQuery::default());
272 let realm_query = Rc::clone(&fake_realm_query).get_proxy().await;
273
274 let res = get_accessor_selectors(&realm_query).await;
275
276 assert_matches!(res, Ok(_));
277
278 assert_eq!(
279 res.unwrap(),
280 vec![
281 String::from("example/component:fuchsia.diagnostics.ArchiveAccessor"),
282 String::from("foo/bar/thing:instance:fuchsia.diagnostics.ArchiveAccessor.feedback"),
283 String::from("foo/component:fuchsia.diagnostics.ArchiveAccessor.feedback"),
284 ]
285 );
286 }
287
288 #[fuchsia::test]
289 fn test_ensure_tree_field_is_set() {
290 let name = Some("abc".to_string());
291 let expected = vec![
292 parse_verbose("core/one:[name=abc]root").unwrap(),
293 parse_verbose("core/one:[name=xyz, name=abc]root").unwrap(),
294 ];
295
296 let mut actual = vec![
297 parse_verbose("core/one:root").unwrap(),
298 parse_verbose("core/one:[name=xyz]root").unwrap(),
299 ];
300 ensure_tree_field_is_set(&mut actual, name.clone()).unwrap();
301 assert_eq!(actual, expected);
302 }
303
304 #[fuchsia::test]
305 fn test_ensure_tree_field_is_set_noop_when_tree_names_set() {
306 let expected = vec![
307 parse_verbose("core/one:[...]root").unwrap(),
308 parse_verbose("core/one:[name=xyz]root").unwrap(),
309 ];
310 let mut actual = vec![
311 parse_verbose("core/one:root").unwrap(),
312 parse_verbose("core/one:[name=xyz]root").unwrap(),
313 ];
314 ensure_tree_field_is_set(&mut actual, None).unwrap();
315 assert_eq!(actual, expected);
316 }
317
318 #[fuchsia::test]
319 fn test_ensure_tree_field_is_set_noop_on_empty_vec_no_name() {
320 let mut actual = vec![];
321 ensure_tree_field_is_set(&mut actual, None).unwrap();
322 assert_eq!(actual, vec![]);
323 }
324
325 #[fuchsia::test]
326 fn test_ensure_tree_field_is_set_all_components_when_empty_and_name() {
327 let expected = vec![parse_verbose("**:[name=abc]*").unwrap()];
328 let mut actual = vec![];
329 let name = Some("abc".to_string());
330 ensure_tree_field_is_set(&mut actual, name).unwrap();
331 assert_eq!(actual, expected);
332 }
333
334 struct FakeProvider {
335 realm_query: fsys2::RealmQueryProxy,
336 }
337
338 impl FakeProvider {
339 async fn new(monikers: &'static [&'static str]) -> Self {
340 let mut builder = MockRealmQueryBuilder::default();
341 for name in monikers {
342 builder = builder.when(name).moniker(name).add();
343 }
344 let realm_query_proxy = Rc::new(builder.build()).get_proxy().await;
345 Self { realm_query: realm_query_proxy }
346 }
347 }
348
349 impl DiagnosticsProvider for FakeProvider {
350 async fn snapshot(
351 &self,
352 _: Option<&str>,
353 _: impl IntoIterator<Item = Selector>,
354 ) -> Result<Vec<diagnostics_data::Data<diagnostics_data::Inspect>>, Error> {
355 unreachable!("unimplemented");
356 }
357
358 async fn get_accessor_paths(&self) -> Result<Vec<String>, Error> {
359 unreachable!("unimplemented");
360 }
361
362 fn realm_query(&self) -> &fsys2::RealmQueryProxy {
363 &self.realm_query
364 }
365 }
366
367 #[fuchsia::test]
368 async fn test_process_fuzzy_inputs_success() {
369 let actual = process_fuzzy_inputs(
370 ["moniker1".to_string()],
371 &FakeProvider::new(&["core/moniker1", "core/moniker2"]).await,
372 )
373 .await
374 .unwrap();
375
376 let expected = vec![parse_verbose("core/moniker1:root").unwrap()];
377
378 assert_eq!(actual, expected);
379
380 let actual = process_fuzzy_inputs(
381 ["moniker1:collection".to_string()],
382 &FakeProvider::new(&["core/moniker1:collection", "core/moniker1", "core/moniker2"])
383 .await,
384 )
385 .await
386 .unwrap();
387
388 let expected = vec![parse_verbose(r"core/moniker1\:collection:root").unwrap()];
389
390 assert_eq!(actual, expected);
391
392 let actual = process_fuzzy_inputs(
393 [r"core/moniker1\:collection".to_string()],
394 &FakeProvider::new(&["core/moniker1:collection"]).await,
395 )
396 .await
397 .unwrap();
398
399 let expected = vec![parse_verbose(r"core/moniker1\:collection:root").unwrap()];
400
401 assert_eq!(actual, expected);
402
403 let actual = process_fuzzy_inputs(
404 ["core/moniker1:root:prop".to_string()],
405 &FakeProvider::new(&["core/moniker1:collection", "core/moniker1"]).await,
406 )
407 .await
408 .unwrap();
409
410 let expected = vec![parse_verbose(r"core/moniker1:root:prop").unwrap()];
411
412 assert_eq!(actual, expected);
413
414 let actual = process_fuzzy_inputs(
415 ["core/moniker1".to_string(), "core/moniker2".to_string()],
416 &FakeProvider::new(&["core/moniker1", "core/moniker2"]).await,
417 )
418 .await
419 .unwrap();
420
421 let expected = vec![
422 parse_verbose(r"core/moniker1:root").unwrap(),
423 parse_verbose(r"core/moniker2:root").unwrap(),
424 ];
425
426 assert_eq!(actual, expected);
427
428 let actual = process_fuzzy_inputs(
429 ["moniker1".to_string(), "moniker2".to_string()],
430 &FakeProvider::new(&["core/moniker1"]).await,
431 )
432 .await
433 .unwrap();
434
435 let expected = vec![
436 parse_verbose(r"core/moniker1:root").unwrap(),
437 parse_verbose("moniker2:root").unwrap(),
439 ];
440
441 assert_eq!(actual, expected);
442
443 let actual = process_fuzzy_inputs(
444 ["core/moniker1:root:prop".to_string(), "core/moniker2".to_string()],
445 &FakeProvider::new(&["core/moniker1", "core/moniker2"]).await,
446 )
447 .await
448 .unwrap();
449
450 let expected = vec![
451 parse_verbose(r"core/moniker1:root:prop").unwrap(),
452 parse_verbose(r"core/moniker2:root").unwrap(),
453 ];
454
455 assert_eq!(actual, expected);
456 }
457
458 #[fuchsia::test]
459 async fn test_process_fuzzy_inputs_failures() {
460 let actual =
461 process_fuzzy_inputs(["moniker ".to_string()], &FakeProvider::new(&["moniker"]).await)
462 .await;
463
464 assert_matches!(actual, Err(Error::ParseSelector(_, _)));
465
466 let actual = process_fuzzy_inputs(
467 ["moniker".to_string()],
468 &FakeProvider::new(&["core/moniker1", "core/moniker2"]).await,
469 )
470 .await;
471
472 assert_matches!(actual, Err(Error::FuzzyMatchTooManyMatches(_)));
473 }
474
475 #[fuchsia::test]
476 async fn test_fuzzy_component_search() {
477 let actual = process_component_query_with_partial_selectors(
478 "moniker1",
479 [].into_iter(),
480 &FakeProvider::new(&["core/moniker1", "core/moniker2"]).await,
481 )
482 .await
483 .unwrap();
484
485 let expected = vec![parse_verbose(r"core/moniker1:root").unwrap()];
486
487 assert_eq!(actual, expected);
488
489 let actual = process_component_query_with_partial_selectors(
490 "moniker1",
491 ["root/foo:bar".to_string()].into_iter(),
492 &FakeProvider::new(&["core/moniker1", "core/moniker2"]).await,
493 )
494 .await
495 .unwrap();
496
497 let expected = vec![parse_verbose(r"core/moniker1:root/foo:bar").unwrap()];
498
499 assert_eq!(actual, expected);
500
501 let actual = process_component_query_with_partial_selectors(
502 "moniker1",
503 ["root/foo:bar".to_string()].into_iter(),
504 &FakeProvider::new(&["core/moniker2", "core/moniker3"]).await,
505 )
506 .await;
507
508 assert_matches!(actual, Err(Error::SearchParameterNotFound(_)));
509 }
510}