Add an option to specify a target device

If more than one Fuchsia device is visible on the network, a device
name is necessary to get the IPV6 address of any device via
netaddr.

Also adds a command to list devices.

Change-Id: I8c46711c77223b58d81d61b0df5edc923f2be50b
diff --git a/src/device.rs b/src/device.rs
index fc74957..dd28773 100644
--- a/src/device.rs
+++ b/src/device.rs
@@ -18,20 +18,46 @@
     }
 }
 
-pub fn netaddr(verbose: bool) -> Result<String> {
+pub fn netaddr(verbose: bool, target_options: &TargetOptions) -> Result<String> {
     let fuchsia_root = fuchsia_root()?;
     let netaddr_binary = fuchsia_root.join("out/build-magenta/tools/netaddr");
-    let netaddr_result = Command::new(netaddr_binary).arg("--fuchsia").output()?;
+    let mut args = vec!["--fuchsia"];
+    if let Some(device_name) = target_options.device_name {
+        args.push(device_name);
+    }
+    let netaddr_result = Command::new(netaddr_binary).args(args).output()?;
     let result = str::from_utf8(&netaddr_result.stdout)
         .unwrap()
         .trim()
         .to_string();
     if verbose {
-        println!("netaddr result = {}", result);
+        println!("netaddr status = {}, result = {}", netaddr_result.status, result);
+    }
+    if !netaddr_result.status.success() {
+        let err_str = str::from_utf8(&netaddr_result.stderr)
+            .unwrap()
+            .trim()
+            .to_string();
+        bail!("netaddr failed with status {:?}: {}", netaddr_result.status, err_str);
     }
     Ok(result)
 }
 
+pub fn netls(verbose: bool) -> Result<()> {
+    let fuchsia_root = fuchsia_root()?;
+    let netls_binary = fuchsia_root.join("out/build-magenta/tools/netls");
+    let mut netls_command = Command::new(netls_binary);
+    netls_command.arg("--nowait").arg("--timeout=500");
+    if verbose {
+        println!("{:?}", netls_command);
+    }
+    let netls_status = netls_command.status()?;
+    if !netls_status.success() {
+        bail!("netlst failed with error {:?}", netls_status);
+    }
+    Ok(())
+}
+
 static SSH_OPTIONS: &'static [&str] = &[
     "-o",
     "UserKnownHostsFile=/dev/null",
@@ -83,7 +109,7 @@
 }
 
 pub fn ssh(verbose: bool, target_options: &TargetOptions, command: &str) -> Result<()> {
-    let netaddr = netaddr(verbose)?;
+    let netaddr = netaddr(verbose, &target_options)?;
     let ssh_config = target_out_dir(&target_options)?.join("ssh-keys/ssh_config");
     if !ssh_config.exists() {
         bail!("ssh config not found at {:?}", ssh_config);
diff --git a/src/lib.rs b/src/lib.rs
index 7e5f11f..0e5341c 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -43,7 +43,7 @@
 use errors::*;
 
 use clap::{App, AppSettings, Arg, SubCommand};
-use device::{netaddr, scp_to_device, ssh, start_emulator, stop_emulator};
+use device::{netaddr, netls, scp_to_device, ssh, start_emulator, stop_emulator};
 use sdk::{rust_c_path, rust_linker_path};
 pub use sdk::TargetOptions;
 use cross::{pkg_config_path, run_configure, run_pkg_config};
@@ -60,7 +60,7 @@
     launch: bool,
     params: &[&str],
 ) -> Result<()> {
-    let netaddr = netaddr(verbose)?;
+    let netaddr = netaddr(verbose, &target_options)?;
     if verbose {
         println!("netaddr {}", netaddr);
     }
@@ -247,6 +247,11 @@
         runner_args.push("-v");
     }
 
+    if let Some(device_name) = target_options.device_name {
+        runner_args.push("--device-name");
+        runner_args.push(device_name);
+    }
+
     runner_args.push("run-on-target");
 
     if launch {
@@ -297,6 +302,10 @@
         .arg(Arg::with_name("debug-os").long("debug-os").help(
             "Use debug user.bootfs and ssh keys",
         ))
+        .arg(Arg::with_name("device-name").long("device-name").short("N")
+        .value_name("device-name").help(
+            "Name of device to target, needed if there are multiple devices visible on the network",
+        ))
         .subcommand(
             SubCommand::with_name("autotest")
                 .about("Auto build and test in Fuchsia device or emulator")
@@ -364,6 +373,10 @@
                 ),
         )
         .subcommand(
+            SubCommand::with_name("list-devices")
+                .about("List visible Fuchsia devices")
+        )
+        .subcommand(
             SubCommand::with_name("start")
                 .about("Start a Fuchsia emulator")
                 .arg(Arg::with_name("graphics").short("g").help(
@@ -436,7 +449,14 @@
         .get_matches();
 
     let verbose = matches.is_present("verbose");
-    let target_options = TargetOptions::new(!matches.is_present("debug-os"));
+    let target_options = TargetOptions::new(
+        !matches.is_present("debug-os"),
+        matches.value_of("device-name"),
+    );
+
+    if verbose {
+        println!("target_options = {:?}", target_options);
+    }
 
     if let Some(autotest_matches) = matches.subcommand_matches("autotest") {
         return autotest(
@@ -510,6 +530,10 @@
         return Ok(());
     }
 
+    if let Some(_) = matches.subcommand_matches("list-devices") {
+        return netls(verbose).chain_err(|| "netls failed");
+    }
+
     if let Some(start_matches) = matches.subcommand_matches("start") {
         return start_emulator(
             start_matches.is_present("graphics"),
diff --git a/src/sdk.rs b/src/sdk.rs
index d72c520..a0364fb 100644
--- a/src/sdk.rs
+++ b/src/sdk.rs
@@ -12,13 +12,15 @@
 /// the Fuchsia target that need to be passed through various internal functions. For
 /// the moment there is no way to set anything but the release_os field, but this
 /// will change when fargo starts supporting ARM targets.
-pub struct TargetOptions {
+#[derive(Debug)]
+pub struct TargetOptions<'a> {
     pub release_os: bool,
-    pub target_cpu: &'static str,
-    pub target_cpu_linker: &'static str,
+    pub target_cpu: &'a str,
+    pub target_cpu_linker: &'a str,
+    pub device_name: Option<&'a str>,
 }
 
-impl TargetOptions {
+impl<'a> TargetOptions<'a> {
     /// Constructs a new `TargetOptions`.
     ///
     /// # Examples
@@ -28,11 +30,13 @@
     ///
     /// let target_options = TargetOptions::new(true);
     /// ```
-    pub fn new(release_os: bool) -> TargetOptions {
+
+    pub fn new(release_os: bool, device_name: Option<&'a str>) -> TargetOptions {
         TargetOptions {
             release_os: release_os,
             target_cpu: "x86-64",
             target_cpu_linker: "x86_64",
+            device_name: device_name,
         }
     }
 }