driver_tools/subcommands/show/
mod.rs1pub 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 let empty: [String; 0] = [];
35 let device_info = fuchsia_driver_dev::get_device_info(
36 &driver_development_proxy,
37 &empty,
38 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 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 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 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}