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