devices/virtio/vhost_user_backend/
console.rs

1// Copyright 2021 The ChromiumOS Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use std::path::PathBuf;
6
7use anyhow::anyhow;
8use anyhow::bail;
9use anyhow::Context;
10use argh::FromArgs;
11use base::error;
12use base::Event;
13use base::RawDescriptor;
14use base::Terminal;
15use cros_async::Executor;
16use hypervisor::ProtectionType;
17use snapshot::AnySnapshot;
18use vm_memory::GuestMemory;
19use vmm_vhost::message::VhostUserProtocolFeatures;
20use vmm_vhost::VHOST_USER_F_PROTOCOL_FEATURES;
21
22use crate::virtio::console::device::ConsoleDevice;
23use crate::virtio::console::device::ConsoleSnapshot;
24use crate::virtio::console::port::ConsolePort;
25use crate::virtio::vhost_user_backend::handler::DeviceRequestHandler;
26use crate::virtio::vhost_user_backend::handler::VhostUserDevice;
27use crate::virtio::vhost_user_backend::BackendConnection;
28use crate::virtio::vhost_user_backend::VhostUserDeviceBuilder;
29use crate::virtio::Queue;
30use crate::SerialHardware;
31use crate::SerialParameters;
32use crate::SerialType;
33
34/// Console device for use with vhost-user. Will set stdin back to canon mode if we are getting
35/// input from it.
36pub struct VhostUserConsoleDevice {
37    console: ConsoleDevice,
38    /// Whether we should set stdin to raw mode because we are getting user input from there.
39    raw_stdin: bool,
40}
41
42impl Drop for VhostUserConsoleDevice {
43    fn drop(&mut self) {
44        if self.raw_stdin {
45            // Restore terminal capabilities back to what they were before
46            match std::io::stdin().set_canon_mode() {
47                Ok(()) => (),
48                Err(e) => error!("failed to restore canonical mode for terminal: {:#}", e),
49            }
50        }
51    }
52}
53
54impl VhostUserDeviceBuilder for VhostUserConsoleDevice {
55    fn build(mut self: Box<Self>, _ex: &Executor) -> anyhow::Result<Box<dyn vmm_vhost::Backend>> {
56        if self.raw_stdin {
57            // Set stdin() to raw mode so we can send over individual keystrokes unbuffered
58            std::io::stdin()
59                .set_raw_mode()
60                .context("failed to set terminal in raw mode")?;
61        }
62
63        self.console.start_input_threads();
64
65        let backend = ConsoleBackend { device: *self };
66
67        let handler = DeviceRequestHandler::new(backend);
68        Ok(Box::new(handler))
69    }
70}
71
72struct ConsoleBackend {
73    device: VhostUserConsoleDevice,
74}
75
76impl VhostUserDevice for ConsoleBackend {
77    fn max_queue_num(&self) -> usize {
78        self.device.console.max_queues()
79    }
80
81    fn features(&self) -> u64 {
82        self.device.console.features() | 1 << VHOST_USER_F_PROTOCOL_FEATURES
83    }
84
85    fn protocol_features(&self) -> VhostUserProtocolFeatures {
86        VhostUserProtocolFeatures::CONFIG
87            | VhostUserProtocolFeatures::MQ
88            | VhostUserProtocolFeatures::DEVICE_STATE
89    }
90
91    fn read_config(&self, offset: u64, data: &mut [u8]) {
92        self.device.console.read_config(offset, data);
93    }
94
95    fn reset(&mut self) {
96        if let Err(e) = self.device.console.reset() {
97            error!("console reset failed: {:#}", e);
98        }
99    }
100
101    fn start_queue(&mut self, idx: usize, queue: Queue, _mem: GuestMemory) -> anyhow::Result<()> {
102        self.device.console.start_queue(idx, queue)
103    }
104
105    fn stop_queue(&mut self, idx: usize) -> anyhow::Result<Queue> {
106        match self.device.console.stop_queue(idx) {
107            Ok(Some(queue)) => Ok(queue),
108            Ok(None) => Err(anyhow!("queue {idx} not started")),
109            Err(e) => Err(e).with_context(|| format!("failed to stop queue {idx}")),
110        }
111    }
112
113    fn enter_suspended_state(&mut self) -> anyhow::Result<()> {
114        Ok(())
115    }
116
117    fn snapshot(&mut self) -> anyhow::Result<AnySnapshot> {
118        let snap = self.device.console.snapshot()?;
119        AnySnapshot::to_any(snap).context("failed to snapshot vhost-user console")
120    }
121
122    fn restore(&mut self, data: AnySnapshot) -> anyhow::Result<()> {
123        let snap: ConsoleSnapshot =
124            AnySnapshot::from_any(data).context("failed to deserialize vhost-user console")?;
125        self.device.console.restore(&snap)
126    }
127}
128
129#[derive(FromArgs)]
130#[argh(subcommand, name = "console")]
131/// Console device
132pub struct Options {
133    #[argh(option, arg_name = "PATH", hidden_help)]
134    /// deprecated - please use --socket-path instead
135    socket: Option<String>,
136    #[argh(option, arg_name = "PATH")]
137    /// path to the vhost-user socket to bind to.
138    /// If this flag is set, --fd cannot be specified.
139    socket_path: Option<String>,
140    #[argh(option, arg_name = "FD")]
141    /// file descriptor of a connected vhost-user socket.
142    /// If this flag is set, --socket-path cannot be specified.
143    fd: Option<RawDescriptor>,
144
145    #[argh(option, arg_name = "OUTFILE")]
146    /// path to a file
147    output_file: Option<PathBuf>,
148    #[argh(option, arg_name = "INFILE")]
149    /// path to a file
150    input_file: Option<PathBuf>,
151    /// whether we are logging to syslog or not
152    #[argh(switch)]
153    syslog: bool,
154    #[argh(option, arg_name = "type=TYPE,[path=PATH,input=PATH,console]")]
155    /// multiport parameters
156    port: Vec<SerialParameters>,
157}
158
159fn create_vu_multi_port_device(
160    params: &[SerialParameters],
161    keep_rds: &mut Vec<RawDescriptor>,
162) -> anyhow::Result<VhostUserConsoleDevice> {
163    let ports = params
164        .iter()
165        .map(|x| {
166            let port = x
167                .create_serial_device::<ConsolePort>(
168                    ProtectionType::Unprotected,
169                    // We need to pass an event as per Serial Device API but we don't really use it
170                    // anyway.
171                    &Event::new()?,
172                    keep_rds,
173                )
174                .expect("failed to create multiport console");
175
176            Ok(port)
177        })
178        .collect::<anyhow::Result<Vec<_>>>()?;
179
180    let device = ConsoleDevice::new_multi_port(ProtectionType::Unprotected, ports);
181
182    Ok(VhostUserConsoleDevice {
183        console: device,
184        raw_stdin: false, // currently we are not support stdin raw mode
185    })
186}
187
188/// Starts a multiport enabled vhost-user console device.
189/// Returns an error if the given `args` is invalid or the device fails to run.
190fn run_multi_port_device(opts: Options) -> anyhow::Result<()> {
191    if opts.port.is_empty() {
192        bail!("console: must have at least one `--port`");
193    }
194
195    // We won't jail the device and can simply ignore `keep_rds`.
196    let device = Box::new(create_vu_multi_port_device(&opts.port, &mut Vec::new())?);
197    let ex = Executor::new().context("Failed to create executor")?;
198
199    let conn =
200        BackendConnection::from_opts(opts.socket.as_deref(), opts.socket_path.as_deref(), opts.fd)?;
201    conn.run_device(ex, device)
202}
203
204/// Return a new vhost-user console device. `params` are the device's configuration, and `keep_rds`
205/// is a vector into which `RawDescriptors` that need to survive a fork are added, in case the
206/// device is meant to run within a child process.
207pub fn create_vu_console_device(
208    params: &SerialParameters,
209    keep_rds: &mut Vec<RawDescriptor>,
210) -> anyhow::Result<VhostUserConsoleDevice> {
211    let device = params.create_serial_device::<ConsoleDevice>(
212        ProtectionType::Unprotected,
213        // We need to pass an event as per Serial Device API but we don't really use it anyway.
214        &Event::new()?,
215        keep_rds,
216    )?;
217
218    Ok(VhostUserConsoleDevice {
219        console: device,
220        raw_stdin: params.stdin,
221    })
222}
223
224/// Starts a vhost-user console device.
225/// Returns an error if the given `args` is invalid or the device fails to run.
226pub fn run_console_device(opts: Options) -> anyhow::Result<()> {
227    // try to start a multiport console first
228    if !opts.port.is_empty() {
229        return run_multi_port_device(opts);
230    }
231
232    // fall back to a multiport disabled console
233    let type_ = match opts.output_file {
234        Some(_) => {
235            if opts.syslog {
236                bail!("--output-file and --syslog options cannot be used together.");
237            }
238            SerialType::File
239        }
240        None => {
241            if opts.syslog {
242                SerialType::Syslog
243            } else {
244                SerialType::Stdout
245            }
246        }
247    };
248
249    let params = SerialParameters {
250        type_,
251        hardware: SerialHardware::VirtioConsole,
252        // Required only if type_ is SerialType::File or SerialType::UnixSocket
253        path: opts.output_file,
254        input: opts.input_file,
255        num: 1,
256        console: true,
257        earlycon: false,
258        // We don't use stdin if syslog mode is enabled
259        stdin: !opts.syslog,
260        out_timestamp: false,
261        ..Default::default()
262    };
263
264    // We won't jail the device and can simply ignore `keep_rds`.
265    let device = Box::new(create_vu_console_device(&params, &mut Vec::new())?);
266    let ex = Executor::new().context("Failed to create executor")?;
267
268    let conn =
269        BackendConnection::from_opts(opts.socket.as_deref(), opts.socket_path.as_deref(), opts.fd)?;
270
271    conn.run_device(ex, device)
272}