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