Skip to main content

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};
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
33/// Attempt to connect to the `fuchsia.diagnostics.*ArchiveAccessor` with the selector
34/// specified.
35pub 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            // try again in case this is a fully escaped moniker or selector
105            Err(Error::SearchParameterNotFound(_)) => {
106                // In case they included a tree-selector segment, attempt to parse but don't bail
107                // on failure
108                if let Ok(selector) = selectors::parse_verbose(&value) {
109                    results.push(selector);
110                } else {
111                    // Note the lack of `sanitize_moniker_for_selectors`. `value` is assumed to
112                    // either
113                    //   A) Be a component that isn't running; therefore the selector being
114                    //      right or wrong is irrelevant
115                    //   B) Already be sanitized by the caller
116                    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
140/// Returns the selectors for a component whose url, manifest, or moniker contains the
141/// `component` string.
142pub 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
196/// Expand selectors with a tree name. If a tree name is given, the selectors will be guaranteed to
197/// include the tree name given unless they already have a tree name set. If no tree name is given
198/// and the selectors carry no tree name, then they'll be updated to target all tree names
199/// associated with the component.
200pub 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        // Safety: "**:*" is a valid selector
210        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
227/// Get all the exposed `ArchiveAccessor` from any child component which
228/// directly exposes them or places them in its outgoing directory.
229pub 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                        // This skips .host accessors intentionally.
262                        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            // fallback is to assume that moniker2 is a valid moniker
452            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}