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