mirror of
https://chromium.googlesource.com/crosvm/crosvm
synced 2024-11-24 12:34:31 +00:00
40dcc1885d
The --serial command line option will still accept the old `hardware=legacy-virtio-console` via a serde alias, but the actual enum variant can be removed now that it is equivalent to VirtioConsole (since https://crrev.com/c/5739275). Change-Id: I0db0302c60707e38dfc93da1effe078046a7b321 Reviewed-on: https://chromium-review.googlesource.com/c/crosvm/crosvm/+/5788964 Commit-Queue: Daniel Verkamp <dverkamp@chromium.org> Reviewed-by: Dmitry Torokhov <dtor@chromium.org>
417 lines
14 KiB
Rust
417 lines
14 KiB
Rust
// Copyright 2020 The ChromiumOS Authors
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
use std::fmt;
|
|
use std::fmt::Display;
|
|
use std::fs::File;
|
|
use std::fs::OpenOptions;
|
|
use std::io;
|
|
use std::io::stdin;
|
|
use std::io::stdout;
|
|
use std::path::PathBuf;
|
|
|
|
use base::error;
|
|
use base::open_file_or_duplicate;
|
|
use base::syslog;
|
|
#[cfg(windows)]
|
|
use base::windows::Console as WinConsole;
|
|
use base::AsRawDescriptor;
|
|
use base::Event;
|
|
use base::FileSync;
|
|
use base::RawDescriptor;
|
|
use base::ReadNotifier;
|
|
use hypervisor::ProtectionType;
|
|
use remain::sorted;
|
|
use serde::Deserialize;
|
|
use serde::Serialize;
|
|
use serde_keyvalue::FromKeyValues;
|
|
use thiserror::Error as ThisError;
|
|
|
|
pub use crate::sys::serial_device::SerialDevice;
|
|
use crate::sys::serial_device::*;
|
|
use crate::PciAddress;
|
|
|
|
#[sorted]
|
|
#[derive(ThisError, Debug)]
|
|
pub enum Error {
|
|
#[error("Unable to clone an Event: {0}")]
|
|
CloneEvent(base::Error),
|
|
#[error("Unable to clone file: {0}")]
|
|
FileClone(std::io::Error),
|
|
#[error("Unable to create file '{1}': {0}")]
|
|
FileCreate(std::io::Error, PathBuf),
|
|
#[error("Unable to open file '{1}': {0}")]
|
|
FileOpen(std::io::Error, PathBuf),
|
|
#[error("Serial device path '{0} is invalid")]
|
|
InvalidPath(PathBuf),
|
|
#[error("Invalid serial hardware: {0}")]
|
|
InvalidSerialHardware(String),
|
|
#[error("Invalid serial type: {0}")]
|
|
InvalidSerialType(String),
|
|
#[error("Serial device type file requires a path")]
|
|
PathRequired,
|
|
#[error("Failed to connect to socket: {0}")]
|
|
SocketConnect(std::io::Error),
|
|
#[error("Failed to create unbound socket: {0}")]
|
|
SocketCreate(std::io::Error),
|
|
#[error("Unable to open system type serial: {0}")]
|
|
SystemTypeError(std::io::Error),
|
|
#[error("Serial device type {0} not implemented")]
|
|
Unimplemented(SerialType),
|
|
}
|
|
|
|
/// Trait for types that can be used as input for a serial device.
|
|
pub trait SerialInput: io::Read + ReadNotifier + Send {}
|
|
impl SerialInput for File {}
|
|
#[cfg(windows)]
|
|
impl SerialInput for WinConsole {}
|
|
|
|
/// Enum for possible type of serial devices
|
|
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
|
#[serde(rename_all = "kebab-case")]
|
|
pub enum SerialType {
|
|
File,
|
|
Stdout,
|
|
Sink,
|
|
Syslog,
|
|
#[cfg_attr(unix, serde(rename = "unix"))]
|
|
#[cfg_attr(windows, serde(rename = "namedpipe"))]
|
|
SystemSerialType,
|
|
}
|
|
|
|
impl Default for SerialType {
|
|
fn default() -> Self {
|
|
Self::Sink
|
|
}
|
|
}
|
|
|
|
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::SystemSerialType => SYSTEM_SERIAL_TYPE_NAME.to_string(),
|
|
};
|
|
|
|
write!(f, "{}", s)
|
|
}
|
|
}
|
|
|
|
/// Serial device hardware types
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
|
#[serde(rename_all = "kebab-case")]
|
|
pub enum SerialHardware {
|
|
/// Standard PC-style (8250/16550 compatible) UART
|
|
Serial,
|
|
|
|
/// virtio-console device
|
|
#[serde(alias = "legacy-virtio-console")]
|
|
VirtioConsole,
|
|
|
|
/// Bochs style debug port
|
|
Debugcon,
|
|
}
|
|
|
|
impl Default for SerialHardware {
|
|
fn default() -> Self {
|
|
Self::Serial
|
|
}
|
|
}
|
|
|
|
impl Display for SerialHardware {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
let s = match &self {
|
|
SerialHardware::Serial => "serial".to_string(),
|
|
SerialHardware::VirtioConsole => "virtio-console".to_string(),
|
|
SerialHardware::Debugcon => "debugcon".to_string(),
|
|
};
|
|
|
|
write!(f, "{}", s)
|
|
}
|
|
}
|
|
|
|
fn serial_parameters_default_num() -> u8 {
|
|
1
|
|
}
|
|
|
|
fn serial_parameters_default_debugcon_port() -> u16 {
|
|
// Default to the port OVMF expects.
|
|
0x402
|
|
}
|
|
|
|
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, FromKeyValues)]
|
|
#[serde(deny_unknown_fields, rename_all = "kebab-case", default)]
|
|
pub struct SerialParameters {
|
|
#[serde(rename = "type")]
|
|
pub type_: SerialType,
|
|
pub hardware: SerialHardware,
|
|
pub name: Option<String>,
|
|
pub path: Option<PathBuf>,
|
|
pub input: Option<PathBuf>,
|
|
#[serde(default = "serial_parameters_default_num")]
|
|
pub num: u8,
|
|
pub console: bool,
|
|
pub earlycon: bool,
|
|
pub stdin: bool,
|
|
#[serde(alias = "out_timestamp")]
|
|
pub out_timestamp: bool,
|
|
#[serde(
|
|
alias = "debugcon_port",
|
|
default = "serial_parameters_default_debugcon_port"
|
|
)]
|
|
pub debugcon_port: u16,
|
|
pub pci_address: Option<PciAddress>,
|
|
}
|
|
|
|
/// Temporary structure containing the parameters of a serial port for easy passing to
|
|
/// `SerialDevice::new`.
|
|
#[derive(Default)]
|
|
pub struct SerialOptions {
|
|
pub name: Option<String>,
|
|
pub out_timestamp: bool,
|
|
pub console: bool,
|
|
pub pci_address: Option<PciAddress>,
|
|
}
|
|
|
|
impl SerialParameters {
|
|
/// Helper function to create a serial device from the defined parameters.
|
|
///
|
|
/// # Arguments
|
|
/// * `evt` - event used for interrupt events
|
|
/// * `keep_rds` - Vector of FDs required by this device if it were sandboxed in a child
|
|
/// process. `evt` will always be added to this vector by this function.
|
|
pub fn create_serial_device<T: SerialDevice>(
|
|
&self,
|
|
protection_type: ProtectionType,
|
|
evt: &Event,
|
|
keep_rds: &mut Vec<RawDescriptor>,
|
|
) -> std::result::Result<T, Error> {
|
|
let evt = evt.try_clone().map_err(Error::CloneEvent)?;
|
|
keep_rds.push(evt.as_raw_descriptor());
|
|
cros_tracing::push_descriptors!(keep_rds);
|
|
metrics::push_descriptors(keep_rds);
|
|
let input: Option<Box<dyn SerialInput>> = if let Some(input_path) = &self.input {
|
|
let input_path = input_path.as_path();
|
|
|
|
let input_file = open_file_or_duplicate(input_path, OpenOptions::new().read(true))
|
|
.map_err(|e| Error::FileOpen(e.into(), input_path.into()))?;
|
|
|
|
keep_rds.push(input_file.as_raw_descriptor());
|
|
Some(Box::new(input_file))
|
|
} else if self.stdin {
|
|
keep_rds.push(stdin().as_raw_descriptor());
|
|
Some(Box::new(ConsoleInput::new()))
|
|
} else {
|
|
None
|
|
};
|
|
let (output, sync): (
|
|
Option<Box<dyn io::Write + Send>>,
|
|
Option<Box<dyn FileSync + Send>>,
|
|
) = match self.type_ {
|
|
SerialType::Stdout => {
|
|
keep_rds.push(stdout().as_raw_descriptor());
|
|
(Some(Box::new(stdout())), None)
|
|
}
|
|
SerialType::Sink => (None, None),
|
|
SerialType::Syslog => {
|
|
syslog::push_descriptors(keep_rds);
|
|
(
|
|
Some(Box::new(syslog::Syslogger::new(base::syslog::Level::Info))),
|
|
None,
|
|
)
|
|
}
|
|
SerialType::File => match &self.path {
|
|
Some(path) => {
|
|
let file =
|
|
open_file_or_duplicate(path, OpenOptions::new().append(true).create(true))
|
|
.map_err(|e| Error::FileCreate(e.into(), path.clone()))?;
|
|
let sync = file.try_clone().map_err(Error::FileClone)?;
|
|
|
|
keep_rds.push(file.as_raw_descriptor());
|
|
keep_rds.push(sync.as_raw_descriptor());
|
|
|
|
(Some(Box::new(file)), Some(Box::new(sync)))
|
|
}
|
|
None => return Err(Error::PathRequired),
|
|
},
|
|
SerialType::SystemSerialType => {
|
|
return create_system_type_serial_device(
|
|
self,
|
|
protection_type,
|
|
evt,
|
|
input,
|
|
keep_rds,
|
|
);
|
|
}
|
|
};
|
|
Ok(T::new(
|
|
protection_type,
|
|
evt,
|
|
input,
|
|
output,
|
|
sync,
|
|
SerialOptions {
|
|
name: self.name.clone(),
|
|
out_timestamp: self.out_timestamp,
|
|
console: self.console,
|
|
pci_address: self.pci_address,
|
|
},
|
|
keep_rds.to_vec(),
|
|
))
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use serde_keyvalue::*;
|
|
|
|
use super::*;
|
|
|
|
fn from_serial_arg(options: &str) -> Result<SerialParameters, ParseError> {
|
|
from_key_values(options)
|
|
}
|
|
|
|
#[test]
|
|
fn params_from_key_values() {
|
|
// Defaults
|
|
let params = from_serial_arg("").unwrap();
|
|
assert_eq!(
|
|
params,
|
|
SerialParameters {
|
|
type_: SerialType::Sink,
|
|
hardware: SerialHardware::Serial,
|
|
name: None,
|
|
path: None,
|
|
input: None,
|
|
num: 1,
|
|
console: false,
|
|
earlycon: false,
|
|
stdin: false,
|
|
out_timestamp: false,
|
|
debugcon_port: 0x402,
|
|
pci_address: None,
|
|
}
|
|
);
|
|
|
|
// type parameter
|
|
let params = from_serial_arg("type=file").unwrap();
|
|
assert_eq!(params.type_, SerialType::File);
|
|
let params = from_serial_arg("type=stdout").unwrap();
|
|
assert_eq!(params.type_, SerialType::Stdout);
|
|
let params = from_serial_arg("type=sink").unwrap();
|
|
assert_eq!(params.type_, SerialType::Sink);
|
|
let params = from_serial_arg("type=syslog").unwrap();
|
|
assert_eq!(params.type_, SerialType::Syslog);
|
|
#[cfg(any(target_os = "android", target_os = "linux"))]
|
|
let opt = "type=unix";
|
|
#[cfg(windows)]
|
|
let opt = "type=namedpipe";
|
|
let params = from_serial_arg(opt).unwrap();
|
|
assert_eq!(params.type_, SerialType::SystemSerialType);
|
|
let params = from_serial_arg("type=foobar");
|
|
assert!(params.is_err());
|
|
|
|
// hardware parameter
|
|
let params = from_serial_arg("hardware=serial").unwrap();
|
|
assert_eq!(params.hardware, SerialHardware::Serial);
|
|
let params = from_serial_arg("hardware=virtio-console").unwrap();
|
|
assert_eq!(params.hardware, SerialHardware::VirtioConsole);
|
|
let params = from_serial_arg("hardware=debugcon").unwrap();
|
|
assert_eq!(params.hardware, SerialHardware::Debugcon);
|
|
let params = from_serial_arg("hardware=foobar");
|
|
assert!(params.is_err());
|
|
|
|
// path parameter
|
|
let params = from_serial_arg("path=/test/path").unwrap();
|
|
assert_eq!(params.path, Some("/test/path".into()));
|
|
let params = from_serial_arg("path");
|
|
assert!(params.is_err());
|
|
|
|
// input parameter
|
|
let params = from_serial_arg("input=/path/to/input").unwrap();
|
|
assert_eq!(params.input, Some("/path/to/input".into()));
|
|
let params = from_serial_arg("input");
|
|
assert!(params.is_err());
|
|
|
|
// console parameter
|
|
let params = from_serial_arg("console").unwrap();
|
|
assert!(params.console);
|
|
let params = from_serial_arg("console=true").unwrap();
|
|
assert!(params.console);
|
|
let params = from_serial_arg("console=false").unwrap();
|
|
assert!(!params.console);
|
|
let params = from_serial_arg("console=foobar");
|
|
assert!(params.is_err());
|
|
|
|
// earlycon parameter
|
|
let params = from_serial_arg("earlycon").unwrap();
|
|
assert!(params.earlycon);
|
|
let params = from_serial_arg("earlycon=true").unwrap();
|
|
assert!(params.earlycon);
|
|
let params = from_serial_arg("earlycon=false").unwrap();
|
|
assert!(!params.earlycon);
|
|
let params = from_serial_arg("earlycon=foobar");
|
|
assert!(params.is_err());
|
|
|
|
// stdin parameter
|
|
let params = from_serial_arg("stdin").unwrap();
|
|
assert!(params.stdin);
|
|
let params = from_serial_arg("stdin=true").unwrap();
|
|
assert!(params.stdin);
|
|
let params = from_serial_arg("stdin=false").unwrap();
|
|
assert!(!params.stdin);
|
|
let params = from_serial_arg("stdin=foobar");
|
|
assert!(params.is_err());
|
|
|
|
// out-timestamp parameter
|
|
let params = from_serial_arg("out-timestamp").unwrap();
|
|
assert!(params.out_timestamp);
|
|
let params = from_serial_arg("out-timestamp=true").unwrap();
|
|
assert!(params.out_timestamp);
|
|
let params = from_serial_arg("out-timestamp=false").unwrap();
|
|
assert!(!params.out_timestamp);
|
|
let params = from_serial_arg("out-timestamp=foobar");
|
|
assert!(params.is_err());
|
|
// backward compatibility
|
|
let params = from_serial_arg("out_timestamp=true").unwrap();
|
|
assert!(params.out_timestamp);
|
|
|
|
// debugcon-port parameter
|
|
let params = from_serial_arg("debugcon-port=1026").unwrap();
|
|
assert_eq!(params.debugcon_port, 1026);
|
|
// backward compatibility
|
|
let params = from_serial_arg("debugcon_port=1026").unwrap();
|
|
assert_eq!(params.debugcon_port, 1026);
|
|
|
|
// all together
|
|
let params = from_serial_arg("type=stdout,path=/some/path,hardware=virtio-console,num=5,earlycon,console,stdin,input=/some/input,out_timestamp,debugcon_port=12,pci-address=00:0e.0").unwrap();
|
|
assert_eq!(
|
|
params,
|
|
SerialParameters {
|
|
type_: SerialType::Stdout,
|
|
hardware: SerialHardware::VirtioConsole,
|
|
name: None,
|
|
path: Some("/some/path".into()),
|
|
input: Some("/some/input".into()),
|
|
num: 5,
|
|
console: true,
|
|
earlycon: true,
|
|
stdin: true,
|
|
out_timestamp: true,
|
|
debugcon_port: 12,
|
|
pci_address: Some(PciAddress {
|
|
bus: 0,
|
|
dev: 14,
|
|
func: 0
|
|
}),
|
|
}
|
|
);
|
|
|
|
// invalid field
|
|
let params = from_serial_arg("type=stdout,foo=bar");
|
|
assert!(params.is_err());
|
|
}
|
|
}
|