Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
google
GitHub Repository: google/crosvm
Path: blob/main/devices/src/virtio/vhost_user_backend/console.rs
5394 views
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
5
use std::path::PathBuf;
6
7
use anyhow::anyhow;
8
use anyhow::bail;
9
use anyhow::Context;
10
use argh::FromArgs;
11
use base::error;
12
use base::Event;
13
use base::RawDescriptor;
14
use base::Terminal;
15
use cros_async::Executor;
16
use hypervisor::ProtectionType;
17
use snapshot::AnySnapshot;
18
use vm_memory::GuestMemory;
19
use vmm_vhost::message::VhostUserProtocolFeatures;
20
use vmm_vhost::VHOST_USER_F_PROTOCOL_FEATURES;
21
22
use crate::virtio::console::device::ConsoleDevice;
23
use crate::virtio::console::device::ConsoleSnapshot;
24
use crate::virtio::console::port::ConsolePort;
25
use crate::virtio::vhost_user_backend::handler::DeviceRequestHandler;
26
use crate::virtio::vhost_user_backend::handler::VhostUserDevice;
27
use crate::virtio::vhost_user_backend::BackendConnection;
28
use crate::virtio::vhost_user_backend::VhostUserDeviceBuilder;
29
use crate::virtio::Queue;
30
use crate::SerialHardware;
31
use crate::SerialParameters;
32
use 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.
36
pub 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
42
impl 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
54
impl 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
72
struct ConsoleBackend {
73
device: VhostUserConsoleDevice,
74
}
75
76
impl 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
132
pub 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
159
fn 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.
190
fn 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.
207
pub 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.
226
pub 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
}
273
274