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