use std::path::PathBuf;
use anyhow::anyhow;
use anyhow::bail;
use anyhow::Context;
use argh::FromArgs;
use base::error;
use base::Event;
use base::RawDescriptor;
use base::Terminal;
use cros_async::Executor;
use hypervisor::ProtectionType;
use vm_memory::GuestMemory;
use vmm_vhost::message::VhostUserProtocolFeatures;
use vmm_vhost::VHOST_USER_F_PROTOCOL_FEATURES;
use crate::virtio::console::device::ConsoleDevice;
use crate::virtio::console::device::ConsoleSnapshot;
use crate::virtio::console::port::ConsolePort;
use crate::virtio::vhost::user::device::handler::DeviceRequestHandler;
use crate::virtio::vhost::user::device::handler::VhostUserDevice;
use crate::virtio::vhost::user::device::BackendConnection;
use crate::virtio::vhost::user::device::VhostUserDeviceBuilder;
use crate::virtio::Queue;
use crate::SerialHardware;
use crate::SerialParameters;
use crate::SerialType;
pub struct VhostUserConsoleDevice {
console: ConsoleDevice,
raw_stdin: bool,
}
impl Drop for VhostUserConsoleDevice {
fn drop(&mut self) {
if self.raw_stdin {
match std::io::stdin().set_canon_mode() {
Ok(()) => (),
Err(e) => error!("failed to restore canonical mode for terminal: {:#}", e),
}
}
}
}
impl VhostUserDeviceBuilder for VhostUserConsoleDevice {
fn build(mut self: Box<Self>, _ex: &Executor) -> anyhow::Result<Box<dyn vmm_vhost::Backend>> {
if self.raw_stdin {
std::io::stdin()
.set_raw_mode()
.context("failed to set terminal in raw mode")?;
}
self.console.start_input_threads();
let backend = ConsoleBackend { device: *self };
let handler = DeviceRequestHandler::new(backend);
Ok(Box::new(handler))
}
}
struct ConsoleBackend {
device: VhostUserConsoleDevice,
}
impl VhostUserDevice for ConsoleBackend {
fn max_queue_num(&self) -> usize {
self.device.console.max_queues()
}
fn features(&self) -> u64 {
self.device.console.features() | 1 << VHOST_USER_F_PROTOCOL_FEATURES
}
fn protocol_features(&self) -> VhostUserProtocolFeatures {
VhostUserProtocolFeatures::CONFIG
| VhostUserProtocolFeatures::MQ
| VhostUserProtocolFeatures::DEVICE_STATE
}
fn read_config(&self, offset: u64, data: &mut [u8]) {
self.device.console.read_config(offset, data);
}
fn reset(&mut self) {
if let Err(e) = self.device.console.reset() {
error!("console reset failed: {:#}", e);
}
}
fn start_queue(&mut self, idx: usize, queue: Queue, _mem: GuestMemory) -> anyhow::Result<()> {
self.device.console.start_queue(idx, queue)
}
fn stop_queue(&mut self, idx: usize) -> anyhow::Result<Queue> {
match self.device.console.stop_queue(idx) {
Ok(Some(queue)) => Ok(queue),
Ok(None) => Err(anyhow!("queue {idx} not started")),
Err(e) => Err(e).with_context(|| format!("failed to stop queue {idx}")),
}
}
fn enter_suspended_state(&mut self) -> anyhow::Result<()> {
Ok(())
}
fn snapshot(&mut self) -> anyhow::Result<serde_json::Value> {
let snap = self.device.console.snapshot()?;
serde_json::to_value(snap).context("failed to snapshot vhost-user console")
}
fn restore(&mut self, data: serde_json::Value) -> anyhow::Result<()> {
let snap: ConsoleSnapshot =
serde_json::from_value(data).context("failed to deserialize vhost-user console")?;
self.device.console.restore(&snap)
}
}
#[derive(FromArgs)]
#[argh(subcommand, name = "console")]
pub struct Options {
#[argh(option, arg_name = "PATH", hidden_help)]
socket: Option<String>,
#[argh(option, arg_name = "PATH")]
socket_path: Option<String>,
#[argh(option, arg_name = "FD")]
fd: Option<RawDescriptor>,
#[argh(option, arg_name = "OUTFILE")]
output_file: Option<PathBuf>,
#[argh(option, arg_name = "INFILE")]
input_file: Option<PathBuf>,
#[argh(switch)]
syslog: bool,
#[argh(option, arg_name = "type=TYPE,[path=PATH,input=PATH,console]")]
port: Vec<SerialParameters>,
}
fn create_vu_multi_port_device(
params: &[SerialParameters],
keep_rds: &mut Vec<RawDescriptor>,
) -> anyhow::Result<VhostUserConsoleDevice> {
let ports = params
.iter()
.map(|x| {
let port = x
.create_serial_device::<ConsolePort>(
ProtectionType::Unprotected,
&Event::new()?,
keep_rds,
)
.expect("failed to create multiport console");
Ok(port)
})
.collect::<anyhow::Result<Vec<_>>>()?;
let device = ConsoleDevice::new_multi_port(ProtectionType::Unprotected, ports);
Ok(VhostUserConsoleDevice {
console: device,
raw_stdin: false, })
}
fn run_multi_port_device(opts: Options) -> anyhow::Result<()> {
if opts.port.is_empty() {
bail!("console: must have at least one `--port`");
}
let device = Box::new(create_vu_multi_port_device(&opts.port, &mut Vec::new())?);
let ex = Executor::new().context("Failed to create executor")?;
let conn =
BackendConnection::from_opts(opts.socket.as_deref(), opts.socket_path.as_deref(), opts.fd)?;
conn.run_device(ex, device)
}
pub fn create_vu_console_device(
params: &SerialParameters,
keep_rds: &mut Vec<RawDescriptor>,
) -> anyhow::Result<VhostUserConsoleDevice> {
let device = params.create_serial_device::<ConsoleDevice>(
ProtectionType::Unprotected,
&Event::new()?,
keep_rds,
)?;
Ok(VhostUserConsoleDevice {
console: device,
raw_stdin: params.stdin,
})
}
pub fn run_console_device(opts: Options) -> anyhow::Result<()> {
if !opts.port.is_empty() {
return run_multi_port_device(opts);
}
let type_ = match opts.output_file {
Some(_) => {
if opts.syslog {
bail!("--output-file and --syslog options cannot be used together.");
}
SerialType::File
}
None => {
if opts.syslog {
SerialType::Syslog
} else {
SerialType::Stdout
}
}
};
let params = SerialParameters {
type_,
hardware: SerialHardware::VirtioConsole,
path: opts.output_file,
input: opts.input_file,
num: 1,
console: true,
earlycon: false,
stdin: !opts.syslog,
out_timestamp: false,
..Default::default()
};
let device = Box::new(create_vu_console_device(¶ms, &mut Vec::new())?);
let ex = Executor::new().context("Failed to create executor")?;
let conn =
BackendConnection::from_opts(opts.socket.as_deref(), opts.socket_path.as_deref(), opts.fd)?;
conn.run_device(ex, device)
}