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 futures::future::{Future, FutureExt};
120    use futures::stream::StreamExt;
121    use {flex_fuchsia_driver_framework as fdf, fuchsia_async as fasync};
122
123    /// Invokes `show` with `cmd` and runs a mock driver development server that
124    /// invokes `on_driver_development_request` whenever it receives a request.
125    /// The output of `show` that is normally written to its `writer` parameter
126    /// is returned.
127    async fn run_test_show<F, Fut>(
128        #[cfg(feature = "fdomain")] client: std::sync::Arc<flex_client::Client>,
129        cmd: ShowCommand,
130        on_driver_development_request: F,
131    ) -> Result<String>
132    where
133        F: Fn(fdd::ManagerRequest) -> Fut + Send + Sync + 'static,
134        Fut: Future<Output = Result<()>> + Send + Sync,
135    {
136        #[cfg(not(feature = "fdomain"))]
137        let client = flex_client::fidl::ZirconClient;
138        let (driver_development_proxy, mut driver_development_requests) =
139            client.create_proxy_and_stream::<fdd::ManagerMarker>();
140
141        // Run the command and mock driver development server.
142        let mut writer = Vec::new();
143        let request_handler_task = fasync::Task::spawn(async move {
144            while let Some(res) = driver_development_requests.next().await {
145                let request = res.context("Failed to get next request")?;
146                on_driver_development_request(request).await.context("Failed to handle request")?;
147            }
148            anyhow::bail!("Driver development request stream unexpectedly closed");
149        });
150        futures::select! {
151            res = request_handler_task.fuse() => {
152                res?;
153                anyhow::bail!("Request handler task unexpectedly finished");
154            }
155            res = show(cmd, &mut writer, driver_development_proxy).fuse() => res.context("Show command failed")?,
156        }
157
158        String::from_utf8(writer).context("Failed to convert show output to a string")
159    }
160
161    async fn run_driver_info_iterator_server(
162        mut driver_infos: Vec<fdf::DriverInfo>,
163        iterator: ServerEnd<fdd::DriverInfoIteratorMarker>,
164    ) -> Result<()> {
165        let mut iterator = iterator.into_stream();
166        while let Some(res) = iterator.next().await {
167            let request = res.context("Failed to get request")?;
168            match request {
169                fdd::DriverInfoIteratorRequest::GetNext { responder } => {
170                    responder
171                        .send(&driver_infos)
172                        .context("Failed to send driver infos to responder")?;
173                    driver_infos.clear();
174                }
175            }
176        }
177        Ok(())
178    }
179
180    async fn run_device_info_iterator_server(
181        mut device_infos: Vec<fdd::NodeInfo>,
182        iterator: ServerEnd<fdd::NodeInfoIteratorMarker>,
183    ) -> Result<()> {
184        let mut iterator = iterator.into_stream();
185        while let Some(res) = iterator.next().await {
186            let request = res.context("Failed to get request")?;
187            match request {
188                fdd::NodeInfoIteratorRequest::GetNext { responder } => {
189                    responder
190                        .send(&device_infos)
191                        .context("Failed to send device infos to responder")?;
192                    device_infos.clear();
193                }
194            }
195        }
196        Ok(())
197    }
198
199    #[fasync::run_singlethreaded(test)]
200    async fn test_show() {
201        #[cfg(feature = "fdomain")]
202        let client = fdomain_local::local_client_empty();
203        let cmd = ShowCommand::from_args(&["show"], &["foo"]).unwrap();
204
205        let output = run_test_show(
206            #[cfg(feature = "fdomain")]
207            client,
208            cmd,
209            |request: fdd::ManagerRequest| async move {
210                match request {
211                    fdd::ManagerRequest::GetDriverInfo {
212                        driver_filter,
213                        iterator,
214                        control_handle: _,
215                    } => {
216                        let mut infos = vec![fdf::DriverInfo {
217                            name: Some("foo".to_owned()),
218                            url: Some(
219                                "fuchsia-pkg://fuchsia.com/foo-package#meta/foo.cm".to_owned(),
220                            ),
221                            package_type: Some(fdf::DriverPackageType::Base),
222                            device_categories: Some(vec![
223                                fdf::DeviceCategory {
224                                    category: Some("connectivity".to_string()),
225                                    subcategory: Some("ethernet".to_string()),
226                                    ..Default::default()
227                                },
228                                fdf::DeviceCategory {
229                                    category: Some("usb".to_string()),
230                                    subcategory: None,
231                                    ..Default::default()
232                                },
233                            ]),
234                            ..Default::default()
235                        }];
236                        if !driver_filter.is_empty()
237                            && !infos[0].url.as_ref().unwrap().contains(&driver_filter[0])
238                            && !infos[0].name.as_ref().unwrap().contains(&driver_filter[0])
239                        {
240                            infos.clear();
241                        }
242
243                        run_driver_info_iterator_server(infos, iterator)
244                            .await
245                            .context("Failed to run driver info iterator server")?
246                    }
247                    fdd::ManagerRequest::GetNodeInfo {
248                        node_filter: _,
249                        iterator,
250                        control_handle: _,
251                        exact_match: _,
252                    } => run_device_info_iterator_server(
253                        vec![fdd::NodeInfo {
254                            bound_driver_url: Some(
255                                "fuchsia-pkg://fuchsia.com/foo-package#meta/foo.cm".to_owned(),
256                            ),
257                            moniker: Some("dev.sys.foo".to_owned()),
258                            ..Default::default()
259                        }],
260                        iterator,
261                    )
262                    .await
263                    .context("Failed to run device info iterator server")?,
264                    _ => {}
265                }
266                Ok(())
267            },
268        )
269        .await
270        .unwrap();
271
272        assert_eq!(
273            output,
274            r#"Name      : foo
275URL       : fuchsia-pkg://fuchsia.com/foo-package#meta/foo.cm
276DF Version: 1
277Node Categories: [connectivity::ethernet, usb]
278Nodes     :
279  dev.sys.foo
280Issue parsing the bind rules bytecode
281
282"#
283        );
284    }
285
286    #[fasync::run_singlethreaded(test)]
287    async fn test_show_no_match() {
288        #[cfg(feature = "fdomain")]
289        let client = fdomain_local::local_client_empty();
290        let cmd = ShowCommand::from_args(&["show"], &["nonexistent"]).unwrap();
291
292        let result = run_test_show(
293            #[cfg(feature = "fdomain")]
294            client,
295            cmd,
296            |request: fdd::ManagerRequest| async move {
297                match request {
298                    fdd::ManagerRequest::GetDriverInfo { iterator, .. } => {
299                        run_driver_info_iterator_server(vec![], iterator).await
300                    }
301                    _ => Ok(()),
302                }
303            },
304        )
305        .await;
306
307        assert!(result.is_err());
308        assert_eq!(
309            format!("{:?}", result.unwrap_err()),
310            "Show command failed\n\nCaused by:\n    No drivers found matching \"nonexistent\"."
311        );
312    }
313
314    #[fasync::run_singlethreaded(test)]
315    async fn test_show_ambiguous() {
316        #[cfg(feature = "fdomain")]
317        let client = fdomain_local::local_client_empty();
318        let cmd = ShowCommand::from_args(&["show"], &["foo"]).unwrap();
319
320        let result = run_test_show(
321            #[cfg(feature = "fdomain")]
322            client,
323            cmd,
324            |request: fdd::ManagerRequest| async move {
325                match request {
326                    fdd::ManagerRequest::GetDriverInfo { iterator, .. } => {
327                        run_driver_info_iterator_server(
328                            vec![
329                                fdf::DriverInfo {
330                                    name: Some("foo1".to_owned()),
331                                    url: Some(
332                                        "fuchsia-pkg://fuchsia.com/foo1#meta/foo1.cm".to_owned(),
333                                    ),
334                                    ..Default::default()
335                                },
336                                fdf::DriverInfo {
337                                    name: Some("foo2".to_owned()),
338                                    url: Some(
339                                        "fuchsia-pkg://fuchsia.com/foo2#meta/foo2.cm".to_owned(),
340                                    ),
341                                    ..Default::default()
342                                },
343                            ],
344                            iterator,
345                        )
346                        .await
347                    }
348                    _ => Ok(()),
349                }
350            },
351        )
352        .await;
353
354        assert!(result.is_err());
355        let err_msg = format!("{:?}", result.unwrap_err());
356        assert!(err_msg.contains("matches more than one driver"));
357        assert!(err_msg.contains("foo1"));
358        assert!(err_msg.contains("foo2"));
359    }
360}