Path: blob/main/devices/src/virtio/vhost_user_backend/console.rs
5394 views
// Copyright 2021 The ChromiumOS Authors1// Use of this source code is governed by a BSD-style license that can be2// found in the LICENSE file.34use std::path::PathBuf;56use anyhow::anyhow;7use anyhow::bail;8use anyhow::Context;9use argh::FromArgs;10use base::error;11use base::Event;12use base::RawDescriptor;13use base::Terminal;14use cros_async::Executor;15use hypervisor::ProtectionType;16use snapshot::AnySnapshot;17use vm_memory::GuestMemory;18use vmm_vhost::message::VhostUserProtocolFeatures;19use vmm_vhost::VHOST_USER_F_PROTOCOL_FEATURES;2021use crate::virtio::console::device::ConsoleDevice;22use crate::virtio::console::device::ConsoleSnapshot;23use crate::virtio::console::port::ConsolePort;24use crate::virtio::vhost_user_backend::handler::DeviceRequestHandler;25use crate::virtio::vhost_user_backend::handler::VhostUserDevice;26use crate::virtio::vhost_user_backend::BackendConnection;27use crate::virtio::vhost_user_backend::VhostUserDeviceBuilder;28use crate::virtio::Queue;29use crate::SerialHardware;30use crate::SerialParameters;31use crate::SerialType;3233/// Console device for use with vhost-user. Will set stdin back to canon mode if we are getting34/// input from it.35pub struct VhostUserConsoleDevice {36console: ConsoleDevice,37/// Whether we should set stdin to raw mode because we are getting user input from there.38raw_stdin: bool,39}4041impl Drop for VhostUserConsoleDevice {42fn drop(&mut self) {43if self.raw_stdin {44// Restore terminal capabilities back to what they were before45match std::io::stdin().set_canon_mode() {46Ok(()) => (),47Err(e) => error!("failed to restore canonical mode for terminal: {:#}", e),48}49}50}51}5253impl VhostUserDeviceBuilder for VhostUserConsoleDevice {54fn build(mut self: Box<Self>, _ex: &Executor) -> anyhow::Result<Box<dyn vmm_vhost::Backend>> {55if self.raw_stdin {56// Set stdin() to raw mode so we can send over individual keystrokes unbuffered57std::io::stdin()58.set_raw_mode()59.context("failed to set terminal in raw mode")?;60}6162self.console.start_input_threads();6364let backend = ConsoleBackend { device: *self };6566let handler = DeviceRequestHandler::new(backend);67Ok(Box::new(handler))68}69}7071struct ConsoleBackend {72device: VhostUserConsoleDevice,73}7475impl VhostUserDevice for ConsoleBackend {76fn max_queue_num(&self) -> usize {77self.device.console.max_queues()78}7980fn features(&self) -> u64 {81self.device.console.features() | 1 << VHOST_USER_F_PROTOCOL_FEATURES82}8384fn protocol_features(&self) -> VhostUserProtocolFeatures {85VhostUserProtocolFeatures::CONFIG86| VhostUserProtocolFeatures::MQ87| VhostUserProtocolFeatures::DEVICE_STATE88}8990fn read_config(&self, offset: u64, data: &mut [u8]) {91self.device.console.read_config(offset, data);92}9394fn reset(&mut self) {95if let Err(e) = self.device.console.reset() {96error!("console reset failed: {:#}", e);97}98}99100fn start_queue(&mut self, idx: usize, queue: Queue, _mem: GuestMemory) -> anyhow::Result<()> {101self.device.console.start_queue(idx, queue)102}103104fn stop_queue(&mut self, idx: usize) -> anyhow::Result<Queue> {105match self.device.console.stop_queue(idx) {106Ok(Some(queue)) => Ok(queue),107Ok(None) => Err(anyhow!("queue {idx} not started")),108Err(e) => Err(e).with_context(|| format!("failed to stop queue {idx}")),109}110}111112fn enter_suspended_state(&mut self) -> anyhow::Result<()> {113Ok(())114}115116fn snapshot(&mut self) -> anyhow::Result<AnySnapshot> {117let snap = self.device.console.snapshot()?;118AnySnapshot::to_any(snap).context("failed to snapshot vhost-user console")119}120121fn restore(&mut self, data: AnySnapshot) -> anyhow::Result<()> {122let snap: ConsoleSnapshot =123AnySnapshot::from_any(data).context("failed to deserialize vhost-user console")?;124self.device.console.restore(&snap)125}126}127128#[derive(FromArgs)]129#[argh(subcommand, name = "console")]130/// Console device131pub struct Options {132#[argh(option, arg_name = "PATH", hidden_help)]133/// deprecated - please use --socket-path instead134socket: Option<String>,135#[argh(option, arg_name = "PATH")]136/// path to the vhost-user socket to bind to.137/// If this flag is set, --fd cannot be specified.138socket_path: Option<String>,139#[argh(option, arg_name = "FD")]140/// file descriptor of a connected vhost-user socket.141/// If this flag is set, --socket-path cannot be specified.142fd: Option<RawDescriptor>,143144#[argh(option, arg_name = "OUTFILE")]145/// path to a file146output_file: Option<PathBuf>,147#[argh(option, arg_name = "INFILE")]148/// path to a file149input_file: Option<PathBuf>,150/// whether we are logging to syslog or not151#[argh(switch)]152syslog: bool,153#[argh(option, arg_name = "type=TYPE,[path=PATH,input=PATH,console]")]154/// multiport parameters155port: Vec<SerialParameters>,156}157158fn create_vu_multi_port_device(159params: &[SerialParameters],160keep_rds: &mut Vec<RawDescriptor>,161) -> anyhow::Result<VhostUserConsoleDevice> {162let ports = params163.iter()164.map(|x| {165let port = x166.create_serial_device::<ConsolePort>(167ProtectionType::Unprotected,168// We need to pass an event as per Serial Device API but we don't really use it169// anyway.170&Event::new()?,171keep_rds,172)173.expect("failed to create multiport console");174175Ok(port)176})177.collect::<anyhow::Result<Vec<_>>>()?;178179let device = ConsoleDevice::new_multi_port(ProtectionType::Unprotected, ports);180181Ok(VhostUserConsoleDevice {182console: device,183raw_stdin: false, // currently we are not support stdin raw mode184})185}186187/// Starts a multiport enabled vhost-user console device.188/// Returns an error if the given `args` is invalid or the device fails to run.189fn run_multi_port_device(opts: Options) -> anyhow::Result<()> {190if opts.port.is_empty() {191bail!("console: must have at least one `--port`");192}193194// We won't jail the device and can simply ignore `keep_rds`.195let device = Box::new(create_vu_multi_port_device(&opts.port, &mut Vec::new())?);196let ex = Executor::new().context("Failed to create executor")?;197198let conn =199BackendConnection::from_opts(opts.socket.as_deref(), opts.socket_path.as_deref(), opts.fd)?;200conn.run_device(ex, device)201}202203/// Return a new vhost-user console device. `params` are the device's configuration, and `keep_rds`204/// is a vector into which `RawDescriptors` that need to survive a fork are added, in case the205/// device is meant to run within a child process.206pub fn create_vu_console_device(207params: &SerialParameters,208keep_rds: &mut Vec<RawDescriptor>,209) -> anyhow::Result<VhostUserConsoleDevice> {210let device = params.create_serial_device::<ConsoleDevice>(211ProtectionType::Unprotected,212// We need to pass an event as per Serial Device API but we don't really use it anyway.213&Event::new()?,214keep_rds,215)?;216217Ok(VhostUserConsoleDevice {218console: device,219raw_stdin: params.stdin,220})221}222223/// Starts a vhost-user console device.224/// Returns an error if the given `args` is invalid or the device fails to run.225pub fn run_console_device(opts: Options) -> anyhow::Result<()> {226// try to start a multiport console first227if !opts.port.is_empty() {228return run_multi_port_device(opts);229}230231// fall back to a multiport disabled console232let type_ = match opts.output_file {233Some(_) => {234if opts.syslog {235bail!("--output-file and --syslog options cannot be used together.");236}237SerialType::File238}239None => {240if opts.syslog {241SerialType::Syslog242} else {243SerialType::Stdout244}245}246};247248let params = SerialParameters {249type_,250hardware: SerialHardware::VirtioConsole,251// Required only if type_ is SerialType::File or SerialType::UnixSocket252path: opts.output_file,253input: opts.input_file,254num: 1,255console: true,256earlycon: false,257// We don't use stdin if syslog mode is enabled258stdin: !opts.syslog,259out_timestamp: false,260..Default::default()261};262263// We won't jail the device and can simply ignore `keep_rds`.264let device = Box::new(create_vu_console_device(¶ms, &mut Vec::new())?);265let ex = Executor::new().context("Failed to create executor")?;266267let conn =268BackendConnection::from_opts(opts.socket.as_deref(), opts.socket_path.as_deref(), opts.fd)?;269270conn.run_device(ex, device)271}272273274