iquery/commands/
utils.rs

1// Copyright 2020 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use 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
19/// Attempt to connect to the `fuchsia.diagnostics.*ArchiveAccessor` with the selector
20/// specified.
21pub 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            // try again in case this is a fully escaped moniker or selector
91            Err(Error::SearchParameterNotFound(_)) => {
92                // In case they included a tree-selector segment, attempt to parse but don't bail
93                // on failure
94                if let Ok(selector) = selectors::parse_verbose(&value) {
95                    results.push(selector);
96                } else {
97                    // Note the lack of `sanitize_moniker_for_selectors`. `value` is assumed to
98                    // either
99                    //   A) Be a component that isn't running; therefore the selector being
100                    //      right or wrong is irrelevant
101                    //   B) Already be sanitized by the caller
102                    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
126/// Returns the selectors for a component whose url, manifest, or moniker contains the
127/// `component` string.
128pub 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
182/// Expand selectors with a tree name. If a tree name is given, the selectors will be guaranteed to
183/// include the tree name given unless they already have a tree name set. If no tree name is given
184/// and the selectors carry no tree name, then they'll be updated to target all tree names
185/// associated with the component.
186pub 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        // Safety: "**:*" is a valid selector
196        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
213/// Get all the exposed `ArchiveAccessor` from any child component which
214/// directly exposes them or places them in its outgoing directory.
215pub 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                        // This skips .host accessors intentionally.
248                        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            // fallback is to assume that moniker2 is a valid moniker
438            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}