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>
This commit is contained in:
Trent Begin 2019-04-17 13:51:25 -06:00 committed by chrome-bot
parent 6868c0a72f
commit 17ccaadc24
9 changed files with 572 additions and 146 deletions

View file

@ -28,8 +28,8 @@ use crate::AARCH64_RTC_SIZE;
use devices::pl030::PL030_AMBA_ID;
// These are serial device related constants.
use crate::AARCH64_SERIAL_1_3_IRQ;
use crate::AARCH64_SERIAL_ADDR;
use crate::AARCH64_SERIAL_IRQ;
use crate::AARCH64_SERIAL_SIZE;
use crate::AARCH64_SERIAL_SPEED;
@ -135,7 +135,7 @@ fn create_serial_node(fdt: &mut Vec<u8>) -> Result<()> {
let serial_reg_prop = generate_prop64(&[AARCH64_SERIAL_ADDR, AARCH64_SERIAL_SIZE]);
let irq = generate_prop32(&[
GIC_FDT_IRQ_TYPE_SPI,
AARCH64_SERIAL_IRQ,
AARCH64_SERIAL_1_3_IRQ,
IRQ_TYPE_EDGE_RISING,
]);

View file

@ -2,16 +2,20 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::collections::BTreeMap;
use std::error::Error as StdError;
use std::ffi::{CStr, CString};
use std::fmt::{self, Display};
use std::fs::File;
use std::io::{self, stdout};
use std::io;
use std::os::unix::io::FromRawFd;
use std::sync::Arc;
use arch::{RunnableLinuxVm, VmComponents};
use devices::{Bus, BusError, PciConfigMmio, PciDevice, PciInterruptPin};
use devices::{
get_serial_tty_string, Bus, BusError, PciConfigMmio, PciDevice, PciInterruptPin,
SerialParameters,
};
use io_jail::Minijail;
use remain::sorted;
use resources::SystemAllocator;
@ -84,7 +88,8 @@ const AARCH64_SERIAL_SIZE: u64 = 0x8;
const AARCH64_SERIAL_SPEED: u32 = 1843200;
// The serial device gets the first interrupt line
// Which gets mapped to the first SPI interrupt (physical 32).
const AARCH64_SERIAL_IRQ: u32 = 0;
const AARCH64_SERIAL_1_3_IRQ: u32 = 0;
const AARCH64_SERIAL_2_4_IRQ: u32 = 2;
// Place the RTC device at page 2
const AARCH64_RTC_ADDR: u64 = 0x2000;
@ -101,8 +106,8 @@ const AARCH64_PCI_CFG_SIZE: u64 = 0x1000000;
const AARCH64_MMIO_BASE: u64 = 0x1010000;
// Size of the whole MMIO region.
const AARCH64_MMIO_SIZE: u64 = 0x100000;
// Virtio devices start at SPI interrupt number 2
const AARCH64_IRQ_BASE: u32 = 2;
// Virtio devices start at SPI interrupt number 3
const AARCH64_IRQ_BASE: u32 = 3;
#[sorted]
#[derive(Debug)]
@ -115,6 +120,7 @@ pub enum Error {
CreateGICFailure(sys_util::Error),
CreateKvm(sys_util::Error),
CreatePciRoot(arch::DeviceRegistrationError),
CreateSerialDevices(arch::DeviceRegistrationError),
CreateSocket(io::Error),
CreateVcpu(sys_util::Error),
CreateVm(sys_util::Error),
@ -145,6 +151,7 @@ impl Display for Error {
CreateGICFailure(e) => write!(f, "failed to create GIC: {}", e),
CreateKvm(e) => write!(f, "failed to open /dev/kvm: {}", e),
CreatePciRoot(e) => write!(f, "failed to create a PCI root hub: {}", e),
CreateSerialDevices(e) => write!(f, "unable to create serial devices: {}", e),
CreateSocket(e) => write!(f, "failed to create socket: {}", e),
CreateVcpu(e) => write!(f, "failed to create VCPU: {}", e),
CreateVm(e) => write!(f, "failed to create vm: {}", e),
@ -187,6 +194,7 @@ impl arch::LinuxArch for AArch64 {
fn build_vm<F, E>(
mut components: VmComponents,
_split_irqchip: bool,
serial_parameters: &BTreeMap<u8, SerialParameters>,
create_devices: F,
) -> Result<RunnableLinuxVm>
where
@ -220,7 +228,6 @@ impl arch::LinuxArch for AArch64 {
let vcpu_affinity = components.vcpu_affinity;
let irq_chip = Self::create_irq_chip(&vm)?;
let mut cmdline = Self::get_base_linux_cmdline();
let mut mmio_bus = devices::Bus::new();
@ -236,7 +243,22 @@ impl arch::LinuxArch for AArch64 {
// ARM doesn't really use the io bus like x86, so just create an empty bus.
let io_bus = devices::Bus::new();
let stdio_serial = Self::add_arch_devs(&mut vm, &mut mmio_bus)?;
Self::add_arch_devs(&mut vm, &mut mmio_bus)?;
let com_evt_1_3 = EventFd::new().map_err(Error::CreateEventFd)?;
let com_evt_2_4 = EventFd::new().map_err(Error::CreateEventFd)?;
let (stdio_serial_num, stdio_serial) = arch::add_serial_devices(
&mut mmio_bus,
&com_evt_1_3,
&com_evt_2_4,
&serial_parameters,
)
.map_err(Error::CreateSerialDevices)?;
vm.register_irqfd(&com_evt_1_3, AARCH64_SERIAL_1_3_IRQ)
.map_err(Error::RegisterIrqfd)?;
vm.register_irqfd(&com_evt_2_4, AARCH64_SERIAL_2_4_IRQ)
.map_err(Error::RegisterIrqfd)?;
mmio_bus
.insert(
@ -247,6 +269,7 @@ impl arch::LinuxArch for AArch64 {
)
.map_err(Error::RegisterPci)?;
let mut cmdline = Self::get_base_linux_cmdline(stdio_serial_num);
for param in components.extra_kernel_params {
cmdline.insert_str(&param).map_err(Error::Cmdline)?;
}
@ -343,9 +366,13 @@ impl AArch64 {
}
/// This returns a base part of the kernel command for this architecture
fn get_base_linux_cmdline() -> kernel_cmdline::Cmdline {
fn get_base_linux_cmdline(stdio_serial_num: Option<u8>) -> kernel_cmdline::Cmdline {
let mut cmdline = kernel_cmdline::Cmdline::new(sys_util::pagesize());
cmdline.insert_str("console=ttyS0 panic=-1").unwrap();
if stdio_serial_num.is_some() {
let tty_string = get_serial_tty_string(stdio_serial_num.unwrap());
cmdline.insert("console", &tty_string).unwrap();
}
cmdline.insert_str("panic=-1").unwrap();
cmdline
}
@ -365,30 +392,16 @@ impl AArch64 {
///
/// * `vm` - The vm to add irqs to.
/// * `bus` - The bus to add devices to.
fn add_arch_devs(vm: &mut Vm, bus: &mut Bus) -> Result<Arc<Mutex<devices::Serial>>> {
fn add_arch_devs(vm: &mut Vm, bus: &mut Bus) -> Result<()> {
let rtc_evt = EventFd::new().map_err(Error::CreateEventFd)?;
vm.register_irqfd(&rtc_evt, AARCH64_RTC_IRQ)
.map_err(Error::RegisterIrqfd)?;
let com_evt_1_3 = EventFd::new().map_err(Error::CreateEventFd)?;
vm.register_irqfd(&com_evt_1_3, AARCH64_SERIAL_IRQ)
.map_err(Error::RegisterIrqfd)?;
let serial = Arc::new(Mutex::new(devices::Serial::new_out(
com_evt_1_3.try_clone().map_err(Error::CloneEventFd)?,
Box::new(stdout()),
)));
bus.insert(
serial.clone(),
AARCH64_SERIAL_ADDR,
AARCH64_SERIAL_SIZE,
false,
)
.expect("failed to add serial device");
let rtc = Arc::new(Mutex::new(devices::pl030::Pl030::new(rtc_evt)));
bus.insert(rtc, AARCH64_RTC_ADDR, AARCH64_RTC_SIZE, false)
.expect("failed to add rtc device");
Ok(serial)
Ok(())
}
/// The creates the interrupt controller device and optionally returns the fd for it.

View file

@ -16,7 +16,7 @@ use std::sync::Arc;
use devices::virtio::VirtioDevice;
use devices::{
Bus, BusDevice, BusError, PciDevice, PciDeviceError, PciInterruptPin, PciRoot, ProxyDevice,
Serial,
Serial, SerialParameters, DEFAULT_SERIAL_PARAMS, SERIAL_ADDR,
};
use io_jail::Minijail;
use kvm::{IoeventAddress, Kvm, Vcpu, Vm};
@ -42,7 +42,7 @@ pub struct RunnableLinuxVm {
pub vm: Vm,
pub kvm: Kvm,
pub resources: SystemAllocator,
pub stdio_serial: Arc<Mutex<Serial>>,
pub stdio_serial: Option<Arc<Mutex<Serial>>>,
pub exit_evt: EventFd,
pub vcpus: Vec<Vcpu>,
pub vcpu_affinity: Vec<usize>,
@ -69,10 +69,12 @@ pub trait LinuxArch {
///
/// * `components` - Parts to use to build the VM.
/// * `split_irqchip` - whether to use a split IRQ chip (i.e. userspace PIT/PIC/IOAPIC)
/// * `serial_parameters` - definitions for how the serial devices should be configured.
/// * `create_devices` - Function to generate a list of devices.
fn build_vm<F, E>(
components: VmComponents,
split_irqchip: bool,
serial_parameters: &BTreeMap<u8, SerialParameters>,
create_devices: F,
) -> Result<RunnableLinuxVm, Self::Error>
where
@ -91,6 +93,8 @@ pub enum DeviceRegistrationError {
AllocateIrq,
/// Could not create the mmio device to wrap a VirtioDevice.
CreateMmioDevice(sys_util::Error),
// Unable to create serial device from serial parameters
CreateSerialDevice(devices::SerialError),
/// Could not create an event fd.
EventFdCreate(sys_util::Error),
/// Could not add a device to the mmio bus.
@ -120,6 +124,7 @@ impl Display for DeviceRegistrationError {
AllocateDeviceAddrs(e) => write!(f, "Allocating device addresses: {}", e),
AllocateIrq => write!(f, "Allocating IRQ number"),
CreateMmioDevice(e) => write!(f, "failed to create mmio device: {}", e),
CreateSerialDevice(e) => write!(f, "failed to create serial device: {}", e),
Cmdline(e) => write!(f, "unable to add device to kernel command line: {}", e),
EventFdCreate(e) => write!(f, "failed to create eventfd: {}", e),
MmioInsert(e) => write!(f, "failed to add to mmio bus: {}", e),
@ -212,6 +217,57 @@ pub fn generate_pci_root(
Ok((root, pci_irqs, pid_labels))
}
/// Adds serial devices to the provided bus based on the serial parameters given. Returns the serial
/// port number and serial device to be used for stdout if defined.
///
/// # Arguments
///
/// * `io_bus` - Bus to add the devices to
/// * `com_evt_1_3` - eventfd for com1 and com3
/// * `com_evt_1_4` - eventfd for com2 and com4
/// * `io_bus` - Bus to add the devices to
/// * `serial_parameters` - definitions of serial parameter configuationis. If a setting is not
/// provided for a port, then it will use the default configuation.
pub fn add_serial_devices(
io_bus: &mut Bus,
com_evt_1_3: &EventFd,
com_evt_2_4: &EventFd,
serial_parameters: &BTreeMap<u8, SerialParameters>,
) -> Result<(Option<u8>, Option<Arc<Mutex<Serial>>>), DeviceRegistrationError> {
let mut stdio_serial_num = None;
let mut stdio_serial = None;
for x in 0..3 {
let com_evt = match x {
0 => com_evt_1_3,
1 => com_evt_2_4,
2 => com_evt_1_3,
3 => com_evt_2_4,
_ => com_evt_1_3,
};
let param = serial_parameters
.get(&(x + 1))
.unwrap_or(&DEFAULT_SERIAL_PARAMS[x as usize]);
let com = Arc::new(Mutex::new(
param
.create_serial_device(&com_evt)
.map_err(DeviceRegistrationError::CreateSerialDevice)?,
));
io_bus
.insert(com.clone(), SERIAL_ADDR[x as usize], 0x8, false)
.unwrap();
if param.console {
stdio_serial_num = Some(x + 1);
stdio_serial = Some(com.clone());
}
}
Ok((stdio_serial_num, stdio_serial))
}
/// Errors for image loading.
#[derive(Debug)]
pub enum LoadImageError {

View file

@ -34,7 +34,10 @@ pub use self::pit::{Pit, PitError};
pub use self::pl030::Pl030;
pub use self::proxy::Error as ProxyError;
pub use self::proxy::ProxyDevice;
pub use self::serial::Serial;
pub use self::serial::Error as SerialError;
pub use self::serial::{
get_serial_tty_string, Serial, SerialParameters, SerialType, DEFAULT_SERIAL_PARAMS, SERIAL_ADDR,
};
pub use self::usb::host_backend::host_backend_device_provider::HostBackendDeviceProvider;
pub use self::usb::xhci::xhci_controller::XhciController;
pub use self::virtio::VirtioPciDevice;

View file

@ -3,9 +3,12 @@
// found in the LICENSE file.
use std::collections::VecDeque;
use std::io;
use std::fmt::{self, Display};
use std::io::{self, stdout};
use std::path::PathBuf;
use std::str::FromStr;
use sys_util::{error, EventFd, Result};
use sys_util::{error, syslog, EventFd, Result};
use crate::BusDevice;
@ -44,6 +47,147 @@ const DEFAULT_MODEM_CONTROL: u8 = 0x8; // Auxiliary output 2
const DEFAULT_MODEM_STATUS: u8 = 0x20 | 0x10 | 0x80; // data ready, clear to send, carrier detect
const DEFAULT_BAUD_DIVISOR: u16 = 12; // 9600 bps
#[derive(Debug)]
pub enum Error {
CloneEventFd(sys_util::Error),
InvalidSerialType(String),
Unimplemented(SerialType),
}
impl Display for Error {
#[remain::check]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::Error::*;
#[sorted]
match self {
CloneEventFd(e) => write!(f, "unable to clone an EventFd: {}", e),
InvalidSerialType(e) => write!(f, "invalid serial type: {}", e),
Unimplemented(e) => write!(f, "serial device type {} not implemented", e.to_string()),
}
}
}
/// Enum for possible type of serial devices
#[derive(Debug)]
pub enum SerialType {
File, // NOT IMPLEMENTED
Stdout,
Sink,
Syslog,
UnixSocket, // NOT IMPLEMENTED
}
impl Display for SerialType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s = match &self {
SerialType::File => "File".to_string(),
SerialType::Stdout => "Stdout".to_string(),
SerialType::Sink => "Sink".to_string(),
SerialType::Syslog => "Syslog".to_string(),
SerialType::UnixSocket => "UnixSocket".to_string(),
};
write!(f, "{}", s)
}
}
impl FromStr for SerialType {
type Err = Error;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s {
"file" | "File" => return Ok(SerialType::File),
"stdout" | "Stdout" => return Ok(SerialType::Stdout),
"sink" | "Sink" => return Ok(SerialType::Sink),
"syslog" | "Syslog" => return Ok(SerialType::Syslog),
"unix" | "UnixSocket" => return Ok(SerialType::UnixSocket),
_ => Err(Error::InvalidSerialType(s.to_string())),
}
}
}
/// Holds the parameters for a serial device
#[derive(Debug)]
pub struct SerialParameters {
pub type_: SerialType,
pub path: Option<PathBuf>,
pub num: u8,
pub console: bool,
}
impl SerialParameters {
/// Helper function to create a serial device from the defined parameters.
///
/// # Arguments
/// * `evt_fd` - eventfd used for interrupt events
pub fn create_serial_device(&self, evt_fd: &EventFd) -> std::result::Result<Serial, Error> {
match self.type_ {
SerialType::Stdout => Ok(Serial::new_out(
evt_fd.try_clone().map_err(Error::CloneEventFd)?,
Box::new(stdout()),
)),
SerialType::Sink => Ok(Serial::new_sink(
evt_fd.try_clone().map_err(Error::CloneEventFd)?,
)),
SerialType::Syslog => Ok(Serial::new_out(
evt_fd.try_clone().map_err(Error::CloneEventFd)?,
Box::new(syslog::Syslogger::new(
syslog::Priority::Info,
syslog::Facility::Daemon,
)),
)),
SerialType::File => Err(Error::Unimplemented(SerialType::File)),
SerialType::UnixSocket => Err(Error::Unimplemented(SerialType::UnixSocket)),
}
}
}
// Structure for holding the default configuration of the serial devices.
pub const DEFAULT_SERIAL_PARAMS: [SerialParameters; 4] = [
SerialParameters {
type_: SerialType::Stdout,
path: None,
num: 1,
console: true,
},
SerialParameters {
type_: SerialType::Sink,
path: None,
num: 2,
console: false,
},
SerialParameters {
type_: SerialType::Sink,
path: None,
num: 3,
console: false,
},
SerialParameters {
type_: SerialType::Sink,
path: None,
num: 4,
console: false,
},
];
/// Address for Serial ports in x86
pub const SERIAL_ADDR: [u64; 4] = [0x3f8, 0x2f8, 0x3e8, 0x2e8];
/// String representations of serial devices
pub const SERIAL_TTY_STRINGS: [&str; 4] = ["ttyS0", "ttyS1", "ttyS2", "ttyS3"];
/// Helper function to get the tty string of a serial device based on the port number. Will default
/// to ttyS0 if an invalid number is given.
pub fn get_serial_tty_string(stdio_serial_num: u8) -> String {
match stdio_serial_num {
1 => SERIAL_TTY_STRINGS[0].to_string(),
2 => SERIAL_TTY_STRINGS[1].to_string(),
3 => SERIAL_TTY_STRINGS[2].to_string(),
4 => SERIAL_TTY_STRINGS[3].to_string(),
_ => SERIAL_TTY_STRINGS[0].to_string(),
}
}
/// Emulates serial COM ports commonly seen on x86 I/O ports 0x3f8/0x2f8/0x3e8/0x2e8.
///
/// This can optionally write the guest's output to a Write trait object. To send input to the

View file

@ -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 => {

View file

@ -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");
}
}

View file

@ -83,6 +83,7 @@ impl Display for Priority {
/// The facility of a syslog message.
///
/// See syslog man pages for information on their semantics.
#[derive(Copy, Clone)]
pub enum Facility {
Kernel = 0,
User = 1 << 3,
@ -387,8 +388,8 @@ fn get_localtime() -> tm {
/// # Arguments
/// * `pri` - The `Priority` (i.e. severity) of the log message.
/// * `fac` - The `Facility` of the log message. Usually `Facility::User` should be used.
/// * `file_name` - Name of the file that generated the log.
/// * `line` - Line number within `file_name` that generated the log.
/// * `file_line` - Optional tuple of the name of the file that generated the
/// log and the line number within that file.
/// * `args` - The log's message to record, in the form of `format_args!()` return value
///
/// # Examples
@ -402,12 +403,11 @@ fn get_localtime() -> tm {
/// # }
/// syslog::log(syslog::Priority::Error,
/// syslog::Facility::User,
/// file!(),
/// line!(),
/// Some((file!(), line!())),
/// format_args!("hello syslog"));
/// # }
/// ```
pub fn log(pri: Priority, fac: Facility, file_name: &str, line: u32, args: fmt::Arguments) {
pub fn log(pri: Priority, fac: Facility, file_line: Option<(&str, u32)>, args: fmt::Arguments) {
const MONTHS: [&str; 12] = [
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
];
@ -417,46 +417,52 @@ pub fn log(pri: Priority, fac: Facility, file_name: &str, line: u32, args: fmt::
if let Some(socket) = &state.socket {
let tm = get_localtime();
let prifac = (pri as u8) | (fac as u8);
let (res, len) = {
let res = {
let mut buf_cursor = Cursor::new(&mut buf[..]);
(
write!(
&mut buf_cursor,
"<{}>{} {:02} {:02}:{:02}:{:02} {}[{}]: [{}:{}] {}",
prifac,
MONTHS[tm.tm_mon as usize],
tm.tm_mday,
tm.tm_hour,
tm.tm_min,
tm.tm_sec,
state.proc_name.as_ref().map(|s| s.as_ref()).unwrap_or("-"),
getpid(),
file_name,
line,
args
),
buf_cursor.position() as usize,
write!(
&mut buf_cursor,
"<{}>{} {:02} {:02}:{:02}:{:02} {}[{}]: ",
prifac,
MONTHS[tm.tm_mon as usize],
tm.tm_mday,
tm.tm_hour,
tm.tm_min,
tm.tm_sec,
state.proc_name.as_ref().map(|s| s.as_ref()).unwrap_or("-"),
getpid()
)
.and_then(|()| {
if let Some((file_name, line)) = &file_line {
write!(&mut buf_cursor, " [{}:{}] ", file_name, line)
} else {
Ok(())
}
})
.and_then(|()| write!(&mut buf_cursor, "{}", args))
.and_then(|()| Ok(buf_cursor.position() as usize))
};
if res.is_ok() {
send_buf(&socket, &buf[..len]);
if let Ok(len) = &res {
send_buf(&socket, &buf[..*len])
}
}
let (res, len) = {
let res = {
let mut buf_cursor = Cursor::new(&mut buf[..]);
(
writeln!(&mut buf_cursor, "[{}:{}:{}] {}", pri, file_name, line, args),
buf_cursor.position() as usize,
)
if let Some((file_name, line)) = &file_line {
write!(&mut buf_cursor, "[{}:{}:{}] ", pri, file_name, line)
} else {
Ok(())
}
.and_then(|()| writeln!(&mut buf_cursor, "{}", args))
.and_then(|()| Ok(buf_cursor.position() as usize))
};
if res.is_ok() {
if let Ok(len) = &res {
if let Some(file) = &mut state.file {
let _ = file.write_all(&buf[..len]);
let _ = file.write_all(&buf[..*len]);
}
if state.stderr {
let _ = stderr().write_all(&buf[..len]);
let _ = stderr().write_all(&buf[..*len]);
}
}
}
@ -467,7 +473,7 @@ pub fn log(pri: Priority, fac: Facility, file_name: &str, line: u32, args: fmt::
#[macro_export]
macro_rules! log {
($pri:expr, $($args:tt)+) => ({
$crate::syslog::log($pri, $crate::syslog::Facility::User, file!(), line!(), format_args!($($args)+))
$crate::syslog::log($pri, $crate::syslog::Facility::User, Some((file!(), line!())), format_args!($($args)+))
})
}
@ -503,6 +509,44 @@ macro_rules! debug {
($($args:tt)+) => ($crate::log!($crate::syslog::Priority::Debug, $($args)*))
}
// Struct that implements io::Write to be used for writing directly to the syslog
pub struct Syslogger {
buf: String,
priority: Priority,
facility: Facility,
}
impl Syslogger {
pub fn new(p: Priority, f: Facility) -> Syslogger {
Syslogger {
buf: String::new(),
priority: p,
facility: f,
}
}
}
impl io::Write for Syslogger {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let parsed_str = String::from_utf8_lossy(buf);
self.buf.push_str(&parsed_str);
if let Some(last_newline_idx) = self.buf.rfind('\n') {
for line in self.buf[..last_newline_idx].lines() {
log(self.priority, self.facility, None, format_args!("{}", line));
}
self.buf.drain(..=last_newline_idx);
}
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
@ -535,8 +579,7 @@ mod tests {
log(
Priority::Error,
Facility::User,
file!(),
line!(),
Some((file!(), line!())),
format_args!("hello syslog"),
);
}
@ -547,16 +590,14 @@ mod tests {
log(
Priority::Error,
Facility::User,
file!(),
line!(),
Some((file!(), line!())),
format_args!("before proc name"),
);
set_proc_name("sys_util-test");
log(
Priority::Error,
Facility::User,
file!(),
line!(),
Some((file!(), line!())),
format_args!("after proc name"),
);
}
@ -579,8 +620,7 @@ mod tests {
log(
Priority::Error,
Facility::User,
file!(),
line!(),
Some((file!(), line!())),
format_args!("{}", TEST_STR),
);
@ -600,4 +640,42 @@ mod tests {
info!("this is info {}", true);
debug!("this is debug info {:?}", Some("helpful stuff"));
}
#[test]
fn syslogger_char() {
init().unwrap();
let mut syslogger = Syslogger::new(Priority::Info, Facility::Daemon);
let string = "Writing chars to syslog";
for c in string.chars() {
syslogger.write(&[c as u8]).expect("error writing char");
}
syslogger
.write(&['\n' as u8])
.expect("error writing newline char");
}
#[test]
fn syslogger_line() {
init().unwrap();
let mut syslogger = Syslogger::new(Priority::Info, Facility::Daemon);
let s = "Writing string to syslog\n";
syslogger
.write(&s.as_bytes())
.expect("error writing string");
}
#[test]
fn syslogger_partial() {
init().unwrap();
let mut syslogger = Syslogger::new(Priority::Info, Facility::Daemon);
let s = "Writing partial string";
// Should not log because there is no newline character
syslogger
.write(&s.as_bytes())
.expect("error writing string");
}
}

View file

@ -54,18 +54,19 @@ mod mptable;
mod regs;
mod smbios;
use std::collections::BTreeMap;
use std::error::Error as StdError;
use std::ffi::{CStr, CString};
use std::fmt::{self, Display};
use std::fs::File;
use std::io::{self, stdout};
use std::io;
use std::mem;
use std::sync::Arc;
use crate::bootparam::boot_params;
use crate::bootparam::E820_RAM;
use arch::{RunnableLinuxVm, VmComponents};
use devices::{PciConfigIo, PciDevice, PciInterruptPin};
use devices::{get_serial_tty_string, PciConfigIo, PciDevice, PciInterruptPin, SerialParameters};
use io_jail::Minijail;
use kvm::*;
use remain::sorted;
@ -87,6 +88,7 @@ pub enum Error {
CreatePciRoot(arch::DeviceRegistrationError),
CreatePit(sys_util::Error),
CreatePitDevice(devices::PitError),
CreateSerialDevices(arch::DeviceRegistrationError),
CreateSocket(io::Error),
CreateVcpu(sys_util::Error),
CreateVm(sys_util::Error),
@ -130,6 +132,7 @@ impl Display for Error {
CreatePciRoot(e) => write!(f, "failed to create a PCI root hub: {}", e),
CreatePit(e) => write!(f, "unable to create PIT: {}", e),
CreatePitDevice(e) => write!(f, "unable to make PIT device: {}", e),
CreateSerialDevices(e) => write!(f, "unable to create serial devices: {}", e),
CreateSocket(e) => write!(f, "failed to create socket: {}", e),
CreateVcpu(e) => write!(f, "failed to create VCPU: {}", e),
CreateVm(e) => write!(f, "failed to create VM: {}", e),
@ -172,6 +175,8 @@ const ZERO_PAGE_OFFSET: u64 = 0x7000;
const KERNEL_START_OFFSET: u64 = 0x200000;
const CMDLINE_OFFSET: u64 = 0x20000;
const CMDLINE_MAX_SIZE: u64 = KERNEL_START_OFFSET - CMDLINE_OFFSET;
const X86_64_SERIAL_1_3_IRQ: u32 = 4;
const X86_64_SERIAL_2_4_IRQ: u32 = 3;
const X86_64_IRQ_BASE: u32 = 5;
fn configure_system(
@ -296,6 +301,7 @@ impl arch::LinuxArch for X8664arch {
fn build_vm<F, E>(
mut components: VmComponents,
split_irqchip: bool,
serial_parameters: &BTreeMap<u8, SerialParameters>,
create_devices: F,
) -> Result<RunnableLinuxVm>
where
@ -329,7 +335,6 @@ impl arch::LinuxArch for X8664arch {
let vcpu_affinity = components.vcpu_affinity;
let irq_chip = Self::create_irq_chip(&vm)?;
let mut cmdline = Self::get_base_linux_cmdline();
let mut mmio_bus = devices::Bus::new();
@ -342,13 +347,17 @@ impl arch::LinuxArch for X8664arch {
.map_err(Error::CreatePciRoot)?;
let pci_bus = Arc::new(Mutex::new(PciConfigIo::new(pci)));
let (io_bus, stdio_serial) = Self::setup_io_bus(
let mut io_bus = Self::setup_io_bus(
&mut vm,
split_irqchip,
exit_evt.try_clone().map_err(Error::CloneEventFd)?,
Some(pci_bus.clone()),
)?;
let (stdio_serial_num, stdio_serial) =
Self::setup_serial_devices(&mut vm, &mut io_bus, &serial_parameters)?;
let mut cmdline = Self::get_base_linux_cmdline(stdio_serial_num);
for param in components.extra_kernel_params {
cmdline.insert_str(&param).map_err(Error::Cmdline)?;
}
@ -533,11 +542,14 @@ impl X8664arch {
}
/// This returns a minimal kernel command for this architecture
fn get_base_linux_cmdline() -> kernel_cmdline::Cmdline {
fn get_base_linux_cmdline(stdio_serial_num: Option<u8>) -> kernel_cmdline::Cmdline {
let mut cmdline = kernel_cmdline::Cmdline::new(CMDLINE_MAX_SIZE as usize);
cmdline
.insert_str("console=ttyS0 noacpi reboot=k panic=-1")
.unwrap();
if stdio_serial_num.is_some() {
let tty_string = get_serial_tty_string(stdio_serial_num.unwrap());
cmdline.insert("console", &tty_string).unwrap();
}
cmdline.insert_str("noacpi reboot=k panic=-1").unwrap();
cmdline
}
@ -565,7 +577,7 @@ impl X8664arch {
split_irqchip: bool,
exit_evt: EventFd,
pci: Option<Arc<Mutex<devices::PciConfigIo>>>,
) -> Result<(devices::Bus, Arc<Mutex<devices::Serial>>)> {
) -> Result<(devices::Bus)> {
struct NoDevice;
impl devices::BusDevice for NoDevice {
fn debug_label(&self) -> String {
@ -575,46 +587,6 @@ impl X8664arch {
let mut io_bus = devices::Bus::new();
let com_evt_1_3 = EventFd::new().map_err(Error::CreateEventFd)?;
let com_evt_2_4 = EventFd::new().map_err(Error::CreateEventFd)?;
let stdio_serial = Arc::new(Mutex::new(devices::Serial::new_out(
com_evt_1_3.try_clone().map_err(Error::CloneEventFd)?,
Box::new(stdout()),
)));
let nul_device = Arc::new(Mutex::new(NoDevice));
io_bus
.insert(stdio_serial.clone(), 0x3f8, 0x8, false)
.unwrap();
io_bus
.insert(
Arc::new(Mutex::new(devices::Serial::new_sink(
com_evt_2_4.try_clone().map_err(Error::CloneEventFd)?,
))),
0x2f8,
0x8,
false,
)
.unwrap();
io_bus
.insert(
Arc::new(Mutex::new(devices::Serial::new_sink(
com_evt_1_3.try_clone().map_err(Error::CloneEventFd)?,
))),
0x3e8,
0x8,
false,
)
.unwrap();
io_bus
.insert(
Arc::new(Mutex::new(devices::Serial::new_sink(
com_evt_2_4.try_clone().map_err(Error::CloneEventFd)?,
))),
0x2e8,
0x8,
false,
)
.unwrap();
io_bus
.insert(Arc::new(Mutex::new(devices::Cmos::new())), 0x70, 0x2, false)
.unwrap();
@ -629,6 +601,7 @@ impl X8664arch {
)
.unwrap();
let nul_device = Arc::new(Mutex::new(NoDevice));
if split_irqchip {
let pit_evt = EventFd::new().map_err(Error::CreateEventFd)?;
let pit = Arc::new(Mutex::new(
@ -664,12 +637,35 @@ impl X8664arch {
.unwrap();
}
vm.register_irqfd(&com_evt_1_3, 4)
Ok(io_bus)
}
/// Sets up the serial devices for this platform. Returns the serial port number and serial
/// device to be used for stdout
///
/// # Arguments
///
/// * - `vm` the vm object
/// * - `io_bus` the I/O bus to add the devices to
/// * - `serial_parmaters` - definitions for how the serial devices should be configured
fn setup_serial_devices(
vm: &mut Vm,
io_bus: &mut devices::Bus,
serial_parameters: &BTreeMap<u8, SerialParameters>,
) -> Result<(Option<u8>, Option<Arc<Mutex<devices::Serial>>>)> {
let com_evt_1_3 = EventFd::new().map_err(Error::CreateEventFd)?;
let com_evt_2_4 = EventFd::new().map_err(Error::CreateEventFd)?;
let (stdio_serial_num, stdio_serial) =
arch::add_serial_devices(io_bus, &com_evt_1_3, &com_evt_2_4, &serial_parameters)
.map_err(Error::CreateSerialDevices)?;
vm.register_irqfd(&com_evt_1_3, X86_64_SERIAL_1_3_IRQ)
.map_err(Error::RegisterIrqfd)?;
vm.register_irqfd(&com_evt_2_4, 3)
vm.register_irqfd(&com_evt_2_4, X86_64_SERIAL_2_4_IRQ)
.map_err(Error::RegisterIrqfd)?;
Ok((io_bus, stdio_serial))
Ok((stdio_serial_num, stdio_serial))
}
/// Configures the vcpu and should be called once per vcpu from the vcpu's thread.