Skip to main content

driver_tools/subcommands/show/
mod.rs

1// Copyright 2026 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
5pub mod args;
6
7use anyhow::Result;
8use args::ShowCommand;
9use bind::debugger::debug_dump::dump_bind_rules;
10use flex_fuchsia_driver_development as fdd;
11use fuchsia_driver_dev::GetSingleDriverError;
12#[cfg(feature = "fdomain")]
13use fuchsia_driver_dev_fdomain as fuchsia_driver_dev;
14use std::collections::HashMap;
15use std::io::Write;
16
17pub async fn show(
18    cmd: ShowCommand,
19    writer: &mut dyn Write,
20    driver_development_proxy: fdd::ManagerProxy,
21) -> Result<()> {
22    let driver_info = match fuchsia_driver_dev::get_single_driver_from_query(
23        &cmd.query,
24        &driver_development_proxy,
25    )
26    .await
27    {
28        Ok(info) => info,
29        Err(GetSingleDriverError::NotFound(query)) => {
30            return Err(anyhow::anyhow!("No drivers found matching {:?}.", query));
31        }
32        Err(e) => return Err(anyhow::anyhow!(e)),
33    };
34
35    // Query devices and create a map of drivers to devices.
36    let empty: [String; 0] = [];
37    let device_info = fuchsia_driver_dev::get_device_info(
38        &driver_development_proxy,
39        &empty,
40        /* exact_match= */ false,
41    )
42    .await?;
43
44    let mut driver_to_devices = HashMap::<String, Vec<String>>::new();
45    for device in device_info.into_iter() {
46        let driver = device.bound_driver_url.clone();
47        let device_name = device.moniker.as_ref();
48        if let (Some(driver), Some(device_name)) = (driver, device_name) {
49            driver_to_devices
50                .entry(driver.to_string())
51                .and_modify(|v| v.push(device_name.to_string()))
52                .or_insert_with(|| vec![device_name.to_string()]);
53        }
54    }
55
56    if let Some(name) = driver_info.name {
57        writeln!(writer, "{0: <10}: {1}", "Name", name)?;
58    }
59    if let Some(url) = driver_info.url.as_ref() {
60        writeln!(writer, "{0: <10}: {1}", "URL", url)?;
61    }
62
63    // If the version isn't set, the value is assumed to be 1.
64    writeln!(
65        writer,
66        "{0: <10}: {1}",
67        "DF Version",
68        driver_info.driver_framework_version.unwrap_or(1)
69    )?;
70
71    if let Some(node_categories) = driver_info.device_categories {
72        write!(writer, "Node Categories: [")?;
73
74        for (i, category_table) in node_categories.iter().enumerate() {
75            if let Some(category) = &category_table.category {
76                if let Some(subcategory) = &category_table.subcategory {
77                    if !subcategory.is_empty() {
78                        write!(writer, "{}::{}", category, subcategory)?;
79                    } else {
80                        write!(writer, "{}", category,)?;
81                    }
82                } else {
83                    write!(writer, "{}", category,)?;
84                }
85            }
86
87            if i != node_categories.len() - 1 {
88                write!(writer, ", ")?;
89            }
90        }
91        writeln!(writer, "]")?;
92    }
93
94    if let Some(url) = driver_info.url {
95        if let Some(nodes) = driver_to_devices.get(&url) {
96            writeln!(writer, "{0: <10}:\n  {1}", "Nodes", nodes.join("\n  "))?;
97        }
98    }
99    let bind_rules =
100        driver_info.bind_rules_bytecode.map(|bytecode| dump_bind_rules(bytecode).ok()).flatten();
101    match bind_rules {
102        Some(bind_rules) => {
103            writeln!(writer, "{0: <10}: ", "Bind rules bytecode")?;
104            writeln!(writer, "{}", bind_rules)?;
105        }
106        _ => writeln!(writer, "Issue parsing the bind rules bytecode")?,
107    }
108    writeln!(writer)?;
109
110    Ok(())
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116    use anyhow::Context;
117    use argh::FromArgs;
118    use flex_client::fidl::ServerEnd;
119    use flex_fuchsia_driver_framework as fdf;
120    use fuchsia_async as fasync;
121    use futures::future::{Future, FutureExt};
122    use futures::stream::StreamExt;
123
124    /// Invokes `show` with `cmd` and runs a mock driver development server that
125    /// invokes `on_driver_development_request` whenever it receives a request.
126    /// The output of `show` that is normally written to its `writer` parameter
127    /// is returned.
128    async fn run_test_show<F, Fut>(
129        #[cfg(feature = "fdomain")] client: std::sync::Arc<flex_client::Client>,
130        cmd: ShowCommand,
131        on_driver_development_request: F,
132    ) -> Result<String>
133    where
134        F: Fn(fdd::ManagerRequest) -> Fut + Send + Sync + 'static,
135        Fut: Future<Output = Result<()>> + Send + Sync,
136    {
137        #[cfg(not(feature = "fdomain"))]
138        let client = flex_client::fidl::ZirconClient;
139        let (driver_development_proxy, mut driver_development_requests) =
140            client.create_proxy_and_stream::<fdd::ManagerMarker>();
141
142        // Run the command and mock driver development server.
143        let mut writer = Vec::new();
144        let request_handler_task = fasync::Task::spawn(async move {
145            while let Some(res) = driver_development_requests.next().await {
146                let request = res.context("Failed to get next request")?;
147                on_driver_development_request(request).await.context("Failed to handle request")?;
148            }
149            anyhow::bail!("Driver development request stream unexpectedly closed");
150        });
151        futures::select! {
152            res = request_handler_task.fuse() => {
153                res?;
154                anyhow::bail!("Request handler task unexpectedly finished");
155            }
156            res = show(cmd, &mut writer, driver_development_proxy).fuse() => res.context("Show command failed")?,
157        }
158
159        String::from_utf8(writer).context("Failed to convert show output to a string")
160    }
161
162    async fn run_driver_info_iterator_server(
163        mut driver_infos: Vec<fdf::DriverInfo>,
164        iterator: ServerEnd<fdd::DriverInfoIteratorMarker>,
165    ) -> Result<()> {
166        let mut iterator = iterator.into_stream();
167        while let Some(res) = iterator.next().await {
168            let request = res.context("Failed to get request")?;
169            match request {
170                fdd::DriverInfoIteratorRequest::GetNext { responder } => {
171                    responder
172                        .send(&driver_infos)
173                        .context("Failed to send driver infos to responder")?;
174                    driver_infos.clear();
175                }
176            }
177        }
178        Ok(())
179    }
180
181    async fn run_device_info_iterator_server(
182        mut device_infos: Vec<fdd::NodeInfo>,
183        iterator: ServerEnd<fdd::NodeInfoIteratorMarker>,
184    ) -> Result<()> {
185        let mut iterator = iterator.into_stream();
186        while let Some(res) = iterator.next().await {
187            let request = res.context("Failed to get request")?;
188            match request {
189                fdd::NodeInfoIteratorRequest::GetNext { responder } => {
190                    responder
191                        .send(&device_infos)
192                        .context("Failed to send device infos to responder")?;
193                    device_infos.clear();
194                }
195            }
196        }
197        Ok(())
198    }
199
200    #[fuchsia::test]
201    async fn test_show() {
202        #[cfg(feature = "fdomain")]
203        let client = fdomain_local::local_client_empty();
204        let cmd = ShowCommand::from_args(&["show"], &["foo"]).unwrap();
205
206        let output = run_test_show(
207            #[cfg(feature = "fdomain")]
208            client,
209            cmd,
210            |request: fdd::ManagerRequest| async move {
211                match request {
212                    fdd::ManagerRequest::GetDriverInfo {
213                        driver_filter,
214                        iterator,
215                        control_handle: _,
216                    } => {
217                        let mut infos = vec![fdf::DriverInfo {
218                            name: Some("foo".to_owned()),
219                            url: Some(
220                                "fuchsia-pkg://fuchsia.com/foo-package#meta/foo.cm".to_owned(),
221                            ),
222                            package_type: Some(fdf::DriverPackageType::Base),
223                            device_categories: Some(vec![
224                                fdf::DeviceCategory {
225                                    category: Some("connectivity".to_string()),
226                                    subcategory: Some("ethernet".to_string()),
227                                    ..Default::default()
228                                },
229                                fdf::DeviceCategory {
230                                    category: Some("usb".to_string()),
231                                    subcategory: None,
232                                    ..Default::default()
233                                },
234                            ]),
235                            ..Default::default()
236                        }];
237                        if !driver_filter.is_empty()
238                            && !infos[0].url.as_ref().unwrap().contains(&driver_filter[0])
239                            && !infos[0].name.as_ref().unwrap().contains(&driver_filter[0])
240                        {
241                            infos.clear();
242                        }
243
244                        run_driver_info_iterator_server(infos, iterator)
245                            .await
246                            .context("Failed to run driver info iterator server")?
247                    }
248                    fdd::ManagerRequest::GetNodeInfo {
249                        node_filter: _,
250                        iterator,
251                        control_handle: _,
252                        exact_match: _,
253                    } => run_device_info_iterator_server(
254                        vec![fdd::NodeInfo {
255                            bound_driver_url: Some(
256                                "fuchsia-pkg://fuchsia.com/foo-package#meta/foo.cm".to_owned(),
257                            ),
258                            moniker: Some("dev.sys.foo".to_owned()),
259                            ..Default::default()
260                        }],
261                        iterator,
262                    )
263                    .await
264                    .context("Failed to run device info iterator server")?,
265                    _ => {}
266                }
267                Ok(())
268            },
269        )
270        .await
271        .unwrap();
272
273        assert_eq!(
274            output,
275            r#"Name      : foo
276URL       : fuchsia-pkg://fuchsia.com/foo-package#meta/foo.cm
277DF Version: 1
278Node Categories: [connectivity::ethernet, usb]
279Nodes     :
280  dev.sys.foo
281Issue parsing the bind rules bytecode
282
283"#
284        );
285    }
286
287    #[fuchsia::test]
288    async fn test_show_no_match() {
289        #[cfg(feature = "fdomain")]
290        let client = fdomain_local::local_client_empty();
291        let cmd = ShowCommand::from_args(&["show"], &["nonexistent"]).unwrap();
292
293        let result = run_test_show(
294            #[cfg(feature = "fdomain")]
295            client,
296            cmd,
297            |request: fdd::ManagerRequest| async move {
298                match request {
299                    fdd::ManagerRequest::GetDriverInfo { iterator, .. } => {
300                        run_driver_info_iterator_server(vec![], iterator).await
301                    }
302                    _ => Ok(()),
303                }
304            },
305        )
306        .await;
307
308        assert!(result.is_err());
309        assert_eq!(
310            format!("{:?}", result.unwrap_err()),
311            "Show command failed\n\nCaused by:\n    No drivers found matching \"nonexistent\"."
312        );
313    }
314
315    #[fuchsia::test]
316    async fn test_show_ambiguous() {
317        #[cfg(feature = "fdomain")]
318        let client = fdomain_local::local_client_empty();
319        let cmd = ShowCommand::from_args(&["show"], &["foo"]).unwrap();
320
321        let result = run_test_show(
322            #[cfg(feature = "fdomain")]
323            client,
324            cmd,
325            |request: fdd::ManagerRequest| async move {
326                match request {
327                    fdd::ManagerRequest::GetDriverInfo { iterator, .. } => {
328                        run_driver_info_iterator_server(
329                            vec![
330                                fdf::DriverInfo {
331                                    name: Some("foo1".to_owned()),
332                                    url: Some(
333                                        "fuchsia-pkg://fuchsia.com/foo1#meta/foo1.cm".to_owned(),
334                                    ),
335                                    ..Default::default()
336                                },
337                                fdf::DriverInfo {
338                                    name: Some("foo2".to_owned()),
339                                    url: Some(
340                                        "fuchsia-pkg://fuchsia.com/foo2#meta/foo2.cm".to_owned(),
341                                    ),
342                                    ..Default::default()
343                                },
344                            ],
345                            iterator,
346                        )
347                        .await
348                    }
349                    _ => Ok(()),
350                }
351            },
352        )
353        .await;
354
355        assert!(result.is_err());
356        let err_msg = format!("{:?}", result.unwrap_err());
357        assert!(err_msg.contains("matches more than one driver"));
358        assert!(err_msg.contains("foo1"));
359        assert!(err_msg.contains("foo2"));
360    }
361}