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 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            // try again in case this is a fully escaped moniker or selector
69            Err(Error::SearchParameterNotFound(_)) => {
70                // In case they included a tree-selector segment, attempt to parse but don't bail
71                // on failure
72                if let Ok(selector) = selectors::parse_verbose(&value) {
73                    results.push(selector);
74                } else {
75                    // Note the lack of `sanitize_moniker_for_selectors`. `value` is assumed to
76                    // either
77                    //   A) Be a component that isn't running; therefore the selector being
78                    //      right or wrong is irrelevant
79                    //   B) Already be sanitized by the caller
80                    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
104/// Returns the selectors for a component whose url, manifest, or moniker contains the
105/// `component` string.
106pub 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
161/// Expand selectors with a tree name. If a tree name is given, the selectors will be guaranteed to
162/// include the tree name given unless they already have a tree name set. If no tree name is given
163/// and the selectors carry no tree name, then they'll be updated to target all tree names
164/// associated with the component.
165pub 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        // Safety: "**:*" is a valid selector
175        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
192/// Get all the exposed `ArchiveAccessor` from any child component which
193/// directly exposes them or places them in its outgoing directory.
194pub 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                        // This skips .host accessors intentionally.
238                        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            // fallback is to assume that moniker2 is a valid moniker
428            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}