Skip to main content

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 flex_fuchsia_driver_development as fdd;
10#[cfg(feature = "fdomain")]
11use fuchsia_driver_dev_fdomain as fuchsia_driver_dev;
12use futures::join;
13use std::collections::HashSet;
14use std::io::Write;
15
16pub async fn list(
17    cmd: ListCommand,
18    writer: &mut dyn Write,
19    driver_development_proxy: fdd::ManagerProxy,
20) -> Result<()> {
21    if cmd.verbose {
22        writeln!(
23            writer,
24            "WARNING: The verbose flag is deprecated. Use `ffx driver show` instead."
25        )?;
26        return Ok(());
27    }
28
29    let empty: [String; 0] = [];
30    let driver_info = fuchsia_driver_dev::get_driver_info(&driver_development_proxy, &empty);
31
32    let driver_info = if cmd.loaded || cmd.running {
33        // Query devices and create a hash set of loaded drivers.
34        let device_info = fuchsia_driver_dev::get_device_info(
35            &driver_development_proxy,
36            &empty,
37            /* exact_match= */ false,
38        );
39
40        // Await the futures concurrently.
41        let (driver_info, device_info) = join!(driver_info, device_info);
42
43        let loaded_driver_set: HashSet<String> = HashSet::from_iter(
44            device_info?
45                .into_iter()
46                .filter(|device_info| {
47                    if cmd.running {
48                        device_info.is_running.unwrap_or(false)
49                            && device_info.driver_host_koid.is_some()
50                    } else {
51                        true
52                    }
53                })
54                .filter_map(|device_info| device_info.bound_driver_url),
55        );
56
57        // Filter the driver list by the hash set.
58        driver_info?
59            .into_iter()
60            .filter(|driver| {
61                let mut loaded = false;
62                if let Some(ref url) = driver.url {
63                    if loaded_driver_set.contains(url) {
64                        loaded = true
65                    }
66                }
67                loaded
68            })
69            .collect()
70    } else {
71        driver_info.await.context("Failed to get driver info")?
72    };
73
74    for driver in driver_info {
75        if let Some(name) = driver.name {
76            let url = driver.url.unwrap_or_default();
77            writeln!(writer, "{:<20}: {}", name, url)?;
78        } else {
79            let url = driver.url.unwrap_or_default();
80            writeln!(writer, "{}", url)?;
81        }
82    }
83    Ok(())
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89    use argh::FromArgs;
90    use flex_client::fidl::ServerEnd;
91    use flex_fuchsia_driver_framework as fdf;
92    use fuchsia_async as fasync;
93    use futures::future::{Future, FutureExt};
94    use futures::stream::StreamExt;
95    #[cfg(feature = "fdomain")]
96    use std::sync::Arc;
97
98    /// Invokes `list` with `cmd` and runs a mock driver development server that
99    /// invokes `on_driver_development_request` whenever it receives a request.
100    /// The output of `list` that is normally written to its `writer` parameter
101    /// is returned.
102    async fn test_list<F, Fut>(
103        #[cfg(feature = "fdomain")] client: Arc<flex_client::Client>,
104        cmd: ListCommand,
105        on_driver_development_request: F,
106    ) -> Result<String>
107    where
108        F: Fn(fdd::ManagerRequest) -> Fut + Send + Sync + 'static,
109        Fut: Future<Output = Result<()>> + Send + Sync,
110    {
111        #[cfg(not(feature = "fdomain"))]
112        let client = flex_client::fidl::ZirconClient;
113        let (driver_development_proxy, mut driver_development_requests) =
114            client.create_proxy_and_stream::<fdd::ManagerMarker>();
115
116        // Run the command and mock driver development server.
117        let mut writer = Vec::new();
118        let request_handler_task = fasync::Task::spawn(async move {
119            while let Some(res) = driver_development_requests.next().await {
120                let request = res.unwrap();
121                on_driver_development_request(request).await.context("Failed to handle request")?;
122            }
123            anyhow::bail!("Driver development request stream unexpectedly closed");
124        });
125        futures::select! {
126            res = request_handler_task.fuse() => {
127                res?;
128                anyhow::bail!("Request handler task unexpectedly finished");
129            }
130            res = list(cmd, &mut writer, driver_development_proxy).fuse() => res.context("List command failed")?,
131        }
132
133        String::from_utf8(writer).context("Failed to convert list output to a string")
134    }
135
136    async fn run_driver_info_iterator_server(
137        mut driver_infos: Vec<fdf::DriverInfo>,
138        iterator: ServerEnd<fdd::DriverInfoIteratorMarker>,
139    ) -> Result<()> {
140        let mut iterator = iterator.into_stream();
141        while let Some(res) = iterator.next().await {
142            let request = res.unwrap();
143            match request {
144                fdd::DriverInfoIteratorRequest::GetNext { responder } => {
145                    responder.send(&driver_infos).unwrap();
146                    driver_infos.clear();
147                }
148            }
149        }
150        Ok(())
151    }
152
153    async fn run_node_info_iterator_server(
154        mut node_infos: Vec<fdd::NodeInfo>,
155        iterator: ServerEnd<fdd::NodeInfoIteratorMarker>,
156    ) -> Result<()> {
157        let mut iterator = iterator.into_stream();
158        while let Some(res) = iterator.next().await {
159            let request = res.unwrap();
160            match request {
161                fdd::NodeInfoIteratorRequest::GetNext { responder } => {
162                    responder.send(&node_infos).unwrap();
163                    node_infos.clear();
164                }
165            }
166        }
167        Ok(())
168    }
169
170    fn mock_driver_infos() -> Vec<fdf::DriverInfo> {
171        vec![
172            fdf::DriverInfo {
173                name: Some("running".to_owned()),
174                url: Some("fuchsia-pkg://fuchsia.com/running#meta/running.cm".to_owned()),
175                ..Default::default()
176            },
177            fdf::DriverInfo {
178                name: Some("binding".to_owned()),
179                url: Some("fuchsia-pkg://fuchsia.com/binding#meta/binding.cm".to_owned()),
180                ..Default::default()
181            },
182            fdf::DriverInfo {
183                name: Some("starting".to_owned()),
184                url: Some("fuchsia-pkg://fuchsia.com/starting#meta/starting.cm".to_owned()),
185                ..Default::default()
186            },
187            fdf::DriverInfo {
188                name: Some("quarantined".to_owned()),
189                url: Some("fuchsia-pkg://fuchsia.com/quarantined#meta/quarantined.cm".to_owned()),
190                ..Default::default()
191            },
192            fdf::DriverInfo {
193                name: Some("unloaded".to_owned()),
194                url: Some("fuchsia-pkg://fuchsia.com/unloaded#meta/unloaded.cm".to_owned()),
195                ..Default::default()
196            },
197        ]
198    }
199
200    fn mock_node_infos() -> Vec<fdd::NodeInfo> {
201        vec![
202            fdd::NodeInfo {
203                bound_driver_url: Some(
204                    "fuchsia-pkg://fuchsia.com/running#meta/running.cm".to_owned(),
205                ),
206                driver_host_koid: Some(12345),
207                is_running: Some(true),
208                quarantined: Some(false),
209                ..Default::default()
210            },
211            fdd::NodeInfo {
212                bound_driver_url: Some(
213                    "fuchsia-pkg://fuchsia.com/binding#meta/binding.cm".to_owned(),
214                ),
215                driver_host_koid: Some(12345),
216                is_running: Some(false),
217                quarantined: Some(false),
218                ..Default::default()
219            },
220            fdd::NodeInfo {
221                bound_driver_url: Some(
222                    "fuchsia-pkg://fuchsia.com/starting#meta/starting.cm".to_owned(),
223                ),
224                driver_host_koid: None,
225                is_running: Some(false),
226                quarantined: Some(false),
227                ..Default::default()
228            },
229            fdd::NodeInfo {
230                bound_driver_url: Some(
231                    "fuchsia-pkg://fuchsia.com/quarantined#meta/quarantined.cm".to_owned(),
232                ),
233                driver_host_koid: None,
234                is_running: Some(false),
235                quarantined: Some(true),
236                ..Default::default()
237            },
238        ]
239    }
240
241    #[fuchsia::test]
242    async fn test_list_all() {
243        #[cfg(feature = "fdomain")]
244        let client = fdomain_local::local_client_empty();
245        let cmd = ListCommand::from_args(&["list"], &[]).unwrap();
246
247        let output = test_list(
248            #[cfg(feature = "fdomain")]
249            client,
250            cmd,
251            |request: fdd::ManagerRequest| async move {
252                match request {
253                    fdd::ManagerRequest::GetDriverInfo {
254                        driver_filter: _,
255                        iterator,
256                        control_handle: _,
257                    } => run_driver_info_iterator_server(
258                        vec![fdf::DriverInfo {
259                            name: Some("foo".to_owned()),
260                            url: Some(
261                                "fuchsia-pkg://fuchsia.com/foo-package#meta/foo.cm".to_owned(),
262                            ),
263                            ..Default::default()
264                        }],
265                        iterator,
266                    )
267                    .await
268                    .context("Failed to run driver info iterator server")?,
269                    _ => {}
270                }
271                Ok(())
272            },
273        )
274        .await
275        .unwrap();
276
277        assert_eq!(
278            output,
279            "foo                 : fuchsia-pkg://fuchsia.com/foo-package#meta/foo.cm\n"
280        );
281    }
282
283    #[fuchsia::test]
284    async fn test_list_loaded() {
285        #[cfg(feature = "fdomain")]
286        let client = fdomain_local::local_client_empty();
287        let cmd = ListCommand::from_args(&["list"], &["--loaded"]).unwrap();
288
289        let output = test_list(
290            #[cfg(feature = "fdomain")]
291            client,
292            cmd,
293            |request: fdd::ManagerRequest| async move {
294                match request {
295                    fdd::ManagerRequest::GetDriverInfo {
296                        driver_filter: _,
297                        iterator,
298                        control_handle: _,
299                    } => run_driver_info_iterator_server(mock_driver_infos(), iterator)
300                        .await
301                        .context("Failed to run driver info iterator server")?,
302                    fdd::ManagerRequest::GetNodeInfo {
303                        node_filter: _,
304                        iterator,
305                        exact_match: _,
306                        control_handle: _,
307                    } => run_node_info_iterator_server(mock_node_infos(), iterator)
308                        .await
309                        .context("Failed to run node info iterator server")?,
310                    _ => {}
311                }
312                Ok(())
313            },
314        )
315        .await
316        .unwrap();
317
318        assert_eq!(
319            output,
320            concat!(
321                "running             : fuchsia-pkg://fuchsia.com/running#meta/running.cm\n",
322                "binding             : fuchsia-pkg://fuchsia.com/binding#meta/binding.cm\n",
323                "starting            : fuchsia-pkg://fuchsia.com/starting#meta/starting.cm\n",
324                "quarantined         : fuchsia-pkg://fuchsia.com/quarantined#meta/quarantined.cm\n",
325            )
326        );
327    }
328
329    #[fuchsia::test]
330    async fn test_list_running() {
331        #[cfg(feature = "fdomain")]
332        let client = fdomain_local::local_client_empty();
333        let cmd = ListCommand::from_args(&["list"], &["--running"]).unwrap();
334
335        let output = test_list(
336            #[cfg(feature = "fdomain")]
337            client,
338            cmd,
339            |request: fdd::ManagerRequest| async move {
340                match request {
341                    fdd::ManagerRequest::GetDriverInfo {
342                        driver_filter: _,
343                        iterator,
344                        control_handle: _,
345                    } => run_driver_info_iterator_server(mock_driver_infos(), iterator)
346                        .await
347                        .context("Failed to run driver info iterator server")?,
348                    fdd::ManagerRequest::GetNodeInfo {
349                        node_filter: _,
350                        iterator,
351                        exact_match: _,
352                        control_handle: _,
353                    } => run_node_info_iterator_server(mock_node_infos(), iterator)
354                        .await
355                        .context("Failed to run node info iterator server")?,
356                    _ => {}
357                }
358                Ok(())
359            },
360        )
361        .await
362        .unwrap();
363
364        assert_eq!(
365            output,
366            "running             : fuchsia-pkg://fuchsia.com/running#meta/running.cm\n"
367        );
368    }
369
370    #[fuchsia::test]
371    async fn test_verbose_deprecated() {
372        #[cfg(feature = "fdomain")]
373        let client = fdomain_local::local_client_empty();
374        let cmd = ListCommand::from_args(&["list"], &["--verbose"]).unwrap();
375
376        let output = test_list(
377            #[cfg(feature = "fdomain")]
378            Arc::clone(&client),
379            cmd,
380            |request: fdd::ManagerRequest| async move {
381                match request {
382                    fdd::ManagerRequest::GetDriverInfo {
383                        driver_filter: _,
384                        iterator,
385                        control_handle: _,
386                    } => run_driver_info_iterator_server(
387                        vec![fdf::DriverInfo {
388                            name: Some("foo".to_owned()),
389                            url: Some(
390                                "fuchsia-pkg://fuchsia.com/foo-package#meta/foo.cm".to_owned(),
391                            ),
392                            ..Default::default()
393                        }],
394                        iterator,
395                    )
396                    .await
397                    .context("Failed to run driver info iterator server")?,
398                    _ => {}
399                }
400                Ok(())
401            },
402        )
403        .await
404        .unwrap();
405
406        assert_eq!(
407            output,
408            "WARNING: The verbose flag is deprecated. Use `ffx driver show` instead.\n"
409        );
410    }
411}