summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorTrent Begin <tbegin@google.com>2019-04-17 13:51:25 -0600
committerchrome-bot <chrome-bot@chromium.org>2019-05-15 13:36:25 -0700
commit17ccaadc24b7eaeedac578b87ddca4491c48b25f (patch)
tree653850131a0dc267b16aa7f60ded7306f146608d /src
parent6868c0a72ffc556fcd9c48006b673f5774d0d35b (diff)
downloadcrosvm-17ccaadc24b7eaeedac578b87ddca4491c48b25f.tar
crosvm-17ccaadc24b7eaeedac578b87ddca4491c48b25f.tar.gz
crosvm-17ccaadc24b7eaeedac578b87ddca4491c48b25f.tar.bz2
crosvm-17ccaadc24b7eaeedac578b87ddca4491c48b25f.tar.lz
crosvm-17ccaadc24b7eaeedac578b87ddca4491c48b25f.tar.xz
crosvm-17ccaadc24b7eaeedac578b87ddca4491c48b25f.tar.zst
crosvm-17ccaadc24b7eaeedac578b87ddca4491c48b25f.zip
crosvm: add cmdline flags for configuring serial outputs in guest machine
This change allows an output to be set for each serial device for a
guest machine (stdout, syslog, or sink).

BUG=chromium:953983
TEST=FEATURES=test emerge-sarien crosvm; cd sys_util; cargo test;
./build_test; manual testing on x86_64 and aarch_64

Change-Id: I9e7fcb0b296c0f8a5aa8d54b1a74ae801f6badc8
Reviewed-on: https://chromium-review.googlesource.com/1572813
Commit-Ready: Trent Begin <tbegin@chromium.org>
Tested-by: kokoro <noreply+kokoro@google.com>
Tested-by: Trent Begin <tbegin@chromium.org>
Reviewed-by: Dylan Reid <dgreid@chromium.org>
Diffstat (limited to 'src')
-rw-r--r--src/linux.rs45
-rw-r--r--src/main.rs129
2 files changed, 155 insertions, 19 deletions
diff --git a/src/linux.rs b/src/linux.rs
index 803a82e..979666e 100644
--- a/src/linux.rs
+++ b/src/linux.rs
@@ -789,7 +789,7 @@ fn create_virtio_devices(
 }
 
 fn create_devices(
-    cfg: Config,
+    cfg: &Config,
     mem: &GuestMemory,
     exit_evt: &EventFd,
     wayland_device_socket: WlControlRequestSocket,
@@ -1180,17 +1180,22 @@ pub fn run_config(cfg: Config) -> Result<()> {
     }
 
     let sandbox = cfg.sandbox;
-    let linux = Arch::build_vm(components, cfg.split_irqchip, |m, e| {
-        create_devices(
-            cfg,
-            m,
-            e,
-            wayland_device_socket,
-            balloon_device_socket,
-            &mut disk_device_sockets,
-            usb_provider,
-        )
-    })
+    let linux = Arch::build_vm(
+        components,
+        cfg.split_irqchip,
+        &cfg.serial_parameters,
+        |m, e| {
+            create_devices(
+                &cfg,
+                m,
+                e,
+                wayland_device_socket,
+                balloon_device_socket,
+                &mut disk_device_sockets,
+                usb_provider,
+            )
+        },
+    )
     .map_err(Error::BuildVm)?;
 
     let _render_node_host = ();
@@ -1401,13 +1406,15 @@ fn run_control(
                             warn!("error while reading stdin: {}", e);
                             let _ = poll_ctx.delete(&stdin_handle);
                         }
-                        Ok(count) => {
-                            linux
-                                .stdio_serial
-                                .lock()
-                                .queue_input_bytes(&out[..count])
-                                .expect("failed to queue bytes into serial port");
-                        }
+                        Ok(count) => match linux.stdio_serial {
+                            Some(ref stdio_serial) => {
+                                stdio_serial
+                                    .lock()
+                                    .queue_input_bytes(&out[..count])
+                                    .expect("failed to queue bytes into serial port");
+                            }
+                            None => {}
+                        },
                     }
                 }
                 Token::ChildSignal => {
diff --git a/src/main.rs b/src/main.rs
index 48c1edd..6b948e6 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -10,6 +10,7 @@ pub mod panic_hook;
 #[cfg(feature = "plugin")]
 pub mod plugin;
 
+use std::collections::BTreeMap;
 use std::fmt;
 use std::fs::{File, OpenOptions};
 use std::net;
@@ -20,6 +21,7 @@ use std::string::String;
 use std::thread::sleep;
 use std::time::Duration;
 
+use devices::{SerialParameters, SerialType};
 use msg_socket::{MsgReceiver, MsgSender, MsgSocket};
 use qcow::QcowFile;
 use sys_util::{
@@ -102,6 +104,8 @@ pub struct Config {
     software_tpm: bool,
     cras_audio: bool,
     null_audio: bool,
+    serial_parameters: BTreeMap<u8, SerialParameters>,
+    syslog_tag: Option<String>,
     virtio_single_touch: Option<TouchDeviceOption>,
     virtio_trackpad: Option<TouchDeviceOption>,
     virtio_mouse: Option<PathBuf>,
@@ -141,6 +145,8 @@ impl Default for Config {
             seccomp_policy_dir: PathBuf::from(SECCOMP_POLICY_DIR),
             cras_audio: false,
             null_audio: false,
+            serial_parameters: BTreeMap::new(),
+            syslog_tag: None,
             virtio_single_touch: None,
             virtio_trackpad: None,
             virtio_mouse: None,
@@ -221,6 +227,58 @@ fn parse_cpu_set(s: &str) -> argument::Result<Vec<usize>> {
     Ok(cpuset)
 }
 
+fn parse_serial_options(s: &str) -> argument::Result<SerialParameters> {
+    let mut serial_setting = SerialParameters {
+        type_: SerialType::Sink,
+        path: None,
+        num: 0,
+        console: false,
+    };
+
+    let opts = s
+        .split(",")
+        .map(|frag| frag.split("="))
+        .map(|mut kv| (kv.next().unwrap_or(""), kv.next().unwrap_or("")));
+
+    for (k, v) in opts {
+        match k {
+            "type" => {
+                serial_setting.type_ = v
+                    .parse::<SerialType>()
+                    .map_err(|e| argument::Error::UnknownArgument(format!("{}", e)))?
+            }
+            "num" => {
+                let num = v.parse::<u8>().map_err(|e| {
+                    argument::Error::Syntax(format!("serial device number is not parsable: {}", e))
+                })?;
+                if num < 1 || num > 4 {
+                    return Err(argument::Error::InvalidValue {
+                        value: num.to_string(),
+                        expected: "Serial port num must be between 1 - 4",
+                    });
+                }
+                serial_setting.num = num;
+            }
+            "console" => {
+                serial_setting.console = v.parse::<bool>().map_err(|e| {
+                    argument::Error::Syntax(format!(
+                        "serial device console is not parseable: {}",
+                        e
+                    ))
+                })?
+            }
+            _ => {
+                return Err(argument::Error::UnknownArgument(format!(
+                    "serial parameter {}",
+                    k
+                )));
+            }
+        }
+    }
+
+    Ok(serial_setting)
+}
+
 fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::Result<()> {
     match name {
         "" => {
@@ -312,6 +370,38 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
         "null-audio" => {
             cfg.null_audio = true;
         }
+        "serial" => {
+            let serial_params = parse_serial_options(value.unwrap())?;
+            let num = serial_params.num;
+            if cfg.serial_parameters.contains_key(&num) {
+                return Err(argument::Error::TooManyArguments(format!(
+                    "serial num {}",
+                    num
+                )));
+            }
+
+            if serial_params.console {
+                for (_, params) in &cfg.serial_parameters {
+                    if params.console {
+                        return Err(argument::Error::TooManyArguments(format!(
+                            "serial device {} already set as console",
+                            params.num
+                        )));
+                    }
+                }
+            }
+
+            cfg.serial_parameters.insert(num, serial_params);
+        }
+        "syslog-tag" => {
+            if cfg.syslog_tag.is_some() {
+                return Err(argument::Error::TooManyArguments(
+                    "`syslog-tag` already given".to_owned(),
+                ));
+            }
+            syslog::set_proc_name(value.unwrap());
+            cfg.syslog_tag = Some(value.unwrap().to_owned());
+        }
         "root" | "disk" | "rwdisk" | "qcow" | "rwqcow" => {
             let disk_path = PathBuf::from(value.unwrap());
             if !disk_path.exists() {
@@ -702,6 +792,15 @@ fn run_vm(args: std::env::Args) -> std::result::Result<(), ()> {
           Argument::value("mac", "MAC", "MAC address for VM."),
           Argument::flag("cras-audio", "Add an audio device to the VM that plays samples through CRAS server"),
           Argument::flag("null-audio", "Add an audio device to the VM that plays samples to /dev/null"),
+          Argument::value("serial",
+                          "type=TYPE,[path=PATH,num=NUM,console]",
+                          "Comma seperated key=value pairs for setting up serial devices. Can be given more than once.
+                          Possible key values:
+                          type=(stdout,syslog,sink) - Where to route the serial device
+                          num=(1,2,3,4) - Serial Device Number
+                          console - Use this serial device as the guest console. Can only be given once. Will default to first serial port if not provided.
+                          "),
+          Argument::value("syslog-tag", "TAG", "When logging to syslog, use the provided tag."),
           Argument::value("wayland-sock", "PATH", "Path to the Wayland socket to use."),
           #[cfg(feature = "wl-dmabuf")]
           Argument::flag("wayland-dmabuf", "Enable support for DMABufs in Wayland device."),
@@ -1271,4 +1370,34 @@ mod tests {
     fn parse_cpu_set_extra_comma() {
         parse_cpu_set("0,1,2,").expect_err("parse should have failed");
     }
+
+    #[test]
+    fn parse_serial_vaild() {
+        parse_serial_options("type=syslog,num=1,console=true").expect("parse should have succeded");
+    }
+
+    #[test]
+    fn parse_serial_invalid_type() {
+        parse_serial_options("type=wormhole,num=1").expect_err("parse should have failed");
+    }
+
+    #[test]
+    fn parse_serial_invalid_num_upper() {
+        parse_serial_options("type=syslog,num=5").expect_err("parse should have failed");
+    }
+
+    #[test]
+    fn parse_serial_invalid_num_lower() {
+        parse_serial_options("type=syslog,num=0").expect_err("parse should have failed");
+    }
+
+    #[test]
+    fn parse_serial_invalid_num_string() {
+        parse_serial_options("type=syslog,num=number3").expect_err("parse should have failed");
+    }
+
+    #[test]
+    fn parse_serial_invalid_option() {
+        parse_serial_options("type=syslog,speed=lightspeed").expect_err("parse should have failed");
+    }
 }