driver_tools/subcommands/list/
mod.rs

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