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 use std::sync::Arc;
7
8 use anyhow::anyhow;
9 use anyhow::bail;
10 use anyhow::Context;
11 use argh::FromArgs;
12 use base::error;
13 use base::Event;
14 use base::RawDescriptor;
15 use base::Terminal;
16 use cros_async::Executor;
17 use data_model::Le32;
18 use hypervisor::ProtectionType;
19 use sync::Mutex;
20 use vm_memory::GuestMemory;
21 use vmm_vhost::message::VhostUserProtocolFeatures;
22 use vmm_vhost::VHOST_USER_F_PROTOCOL_FEATURES;
23 use zerocopy::AsBytes;
24
25 use crate::virtio;
26 use crate::virtio::console::asynchronous::ConsoleDevice;
27 use crate::virtio::console::asynchronous::ConsolePort;
28 use crate::virtio::console::virtio_console_config;
29 use crate::virtio::copy_config;
30 use crate::virtio::vhost::user::device::handler::DeviceRequestHandler;
31 use crate::virtio::vhost::user::device::handler::Error as DeviceError;
32 use crate::virtio::vhost::user::device::handler::VhostUserDevice;
33 use crate::virtio::vhost::user::device::listener::sys::VhostUserListener;
34 use crate::virtio::vhost::user::device::listener::VhostUserListenerTrait;
35 use crate::virtio::vhost::user::device::VhostUserDeviceBuilder;
36 use crate::virtio::Interrupt;
37 use crate::virtio::Queue;
38 use crate::SerialHardware;
39 use crate::SerialParameters;
40 use crate::SerialType;
41
42 /// Console device for use with vhost-user. Will set stdin back to canon mode if we are getting
43 /// input from it.
44 pub struct VhostUserConsoleDevice {
45 console: ConsoleDevice,
46 /// Whether we should set stdin to raw mode because we are getting user input from there.
47 raw_stdin: bool,
48 }
49
50 impl Drop for VhostUserConsoleDevice {
drop(&mut self)51 fn drop(&mut self) {
52 if self.raw_stdin {
53 // Restore terminal capabilities back to what they were before
54 match std::io::stdin().set_canon_mode() {
55 Ok(()) => (),
56 Err(e) => error!("failed to restore canonical mode for terminal: {:#}", e),
57 }
58 }
59 }
60 }
61
62 impl VhostUserDeviceBuilder for VhostUserConsoleDevice {
build(self: Box<Self>, ex: &Executor) -> anyhow::Result<Box<dyn vmm_vhost::Backend>>63 fn build(self: Box<Self>, ex: &Executor) -> anyhow::Result<Box<dyn vmm_vhost::Backend>> {
64 if self.raw_stdin {
65 // Set stdin() to raw mode so we can send over individual keystrokes unbuffered
66 std::io::stdin()
67 .set_raw_mode()
68 .context("failed to set terminal in raw mode")?;
69 }
70
71 let queue_num = self.console.max_queues();
72 let active_queues = vec![None; queue_num];
73
74 let backend = ConsoleBackend {
75 device: *self,
76 acked_features: 0,
77 acked_protocol_features: VhostUserProtocolFeatures::empty(),
78 ex: ex.clone(),
79 active_queues,
80 };
81
82 let handler = DeviceRequestHandler::new(backend);
83 Ok(Box::new(handler))
84 }
85 }
86
87 struct ConsoleBackend {
88 device: VhostUserConsoleDevice,
89 acked_features: u64,
90 acked_protocol_features: VhostUserProtocolFeatures,
91 ex: Executor,
92 active_queues: Vec<Option<Arc<Mutex<Queue>>>>,
93 }
94
95 impl VhostUserDevice for ConsoleBackend {
max_queue_num(&self) -> usize96 fn max_queue_num(&self) -> usize {
97 self.device.console.max_queues()
98 }
99
features(&self) -> u64100 fn features(&self) -> u64 {
101 self.device.console.avail_features() | 1 << VHOST_USER_F_PROTOCOL_FEATURES
102 }
103
ack_features(&mut self, value: u64) -> anyhow::Result<()>104 fn ack_features(&mut self, value: u64) -> anyhow::Result<()> {
105 let unrequested_features = value & !self.features();
106 if unrequested_features != 0 {
107 bail!("invalid features are given: {:#x}", unrequested_features);
108 }
109
110 self.acked_features |= value;
111
112 Ok(())
113 }
114
acked_features(&self) -> u64115 fn acked_features(&self) -> u64 {
116 self.acked_features
117 }
118
protocol_features(&self) -> VhostUserProtocolFeatures119 fn protocol_features(&self) -> VhostUserProtocolFeatures {
120 VhostUserProtocolFeatures::CONFIG | VhostUserProtocolFeatures::MQ
121 }
122
ack_protocol_features(&mut self, features: u64) -> anyhow::Result<()>123 fn ack_protocol_features(&mut self, features: u64) -> anyhow::Result<()> {
124 let features = VhostUserProtocolFeatures::from_bits(features)
125 .ok_or_else(|| anyhow!("invalid protocol features are given: {:#x}", features))?;
126 let supported = self.protocol_features();
127 self.acked_protocol_features = features & supported;
128 Ok(())
129 }
130
acked_protocol_features(&self) -> u64131 fn acked_protocol_features(&self) -> u64 {
132 self.acked_protocol_features.bits()
133 }
134
read_config(&self, offset: u64, data: &mut [u8])135 fn read_config(&self, offset: u64, data: &mut [u8]) {
136 let max_nr_ports = self.device.console.max_ports();
137 let config = virtio_console_config {
138 max_nr_ports: Le32::from(max_nr_ports as u32),
139 ..Default::default()
140 };
141 copy_config(data, 0, config.as_bytes(), offset);
142 }
143
reset(&mut self)144 fn reset(&mut self) {
145 for queue_num in 0..self.max_queue_num() {
146 if let Err(e) = self.stop_queue(queue_num) {
147 error!("Failed to stop_queue during reset: {}", e);
148 }
149 }
150 }
151
start_queue( &mut self, idx: usize, queue: virtio::Queue, _mem: GuestMemory, doorbell: Interrupt, ) -> anyhow::Result<()>152 fn start_queue(
153 &mut self,
154 idx: usize,
155 queue: virtio::Queue,
156 _mem: GuestMemory,
157 doorbell: Interrupt,
158 ) -> anyhow::Result<()> {
159 let queue = Arc::new(Mutex::new(queue));
160 let res = self
161 .device
162 .console
163 .start_queue(&self.ex, idx, queue.clone(), doorbell);
164
165 self.active_queues[idx].replace(queue);
166
167 res
168 }
169
stop_queue(&mut self, idx: usize) -> anyhow::Result<virtio::Queue>170 fn stop_queue(&mut self, idx: usize) -> anyhow::Result<virtio::Queue> {
171 if let Err(e) = self.device.console.stop_queue(idx) {
172 error!("error while stopping queue {}: {}", idx, e);
173 }
174
175 if let Some(active_queue) = self.active_queues[idx].take() {
176 let queue = Arc::try_unwrap(active_queue)
177 .expect("failed to recover queue from worker")
178 .into_inner();
179 Ok(queue)
180 } else {
181 Err(anyhow::Error::new(DeviceError::WorkerNotFound))
182 }
183 }
184 }
185
186 #[derive(FromArgs)]
187 #[argh(subcommand, name = "console")]
188 /// Console device
189 pub struct Options {
190 #[argh(option, arg_name = "PATH")]
191 /// path to a vhost-user socket
192 socket: String,
193 #[argh(option, arg_name = "OUTFILE")]
194 /// path to a file
195 output_file: Option<PathBuf>,
196 #[argh(option, arg_name = "INFILE")]
197 /// path to a file
198 input_file: Option<PathBuf>,
199 /// whether we are logging to syslog or not
200 #[argh(switch)]
201 syslog: bool,
202 #[argh(option, arg_name = "type=TYPE,[path=PATH,input=PATH,console]")]
203 /// multiport parameters
204 port: Vec<SerialParameters>,
205 }
206
create_vu_multi_port_device( params: &[SerialParameters], keep_rds: &mut Vec<RawDescriptor>, ) -> anyhow::Result<VhostUserConsoleDevice>207 fn create_vu_multi_port_device(
208 params: &[SerialParameters],
209 keep_rds: &mut Vec<RawDescriptor>,
210 ) -> anyhow::Result<VhostUserConsoleDevice> {
211 let mut ports = params
212 .iter()
213 .map(|x| {
214 let port = x
215 .create_serial_device::<ConsolePort>(
216 ProtectionType::Unprotected,
217 // We need to pass an event as per Serial Device API but we don't really use it
218 // anyway.
219 &Event::new()?,
220 keep_rds,
221 )
222 .expect("failed to create multiport console");
223
224 Ok(port)
225 })
226 .collect::<anyhow::Result<Vec<_>>>()?;
227
228 let port0 = ports.remove(0);
229 let device = ConsoleDevice::new_multi_port(ProtectionType::Unprotected, port0, ports);
230
231 Ok(VhostUserConsoleDevice {
232 console: device,
233 raw_stdin: false, // currently we are not support stdin raw mode
234 })
235 }
236
237 /// Starts a multiport enabled vhost-user console device.
238 /// Returns an error if the given `args` is invalid or the device fails to run.
run_multi_port_device(opts: Options) -> anyhow::Result<()>239 fn run_multi_port_device(opts: Options) -> anyhow::Result<()> {
240 if opts.port.is_empty() {
241 bail!("console: must have at least one `--port`");
242 }
243
244 // We won't jail the device and can simply ignore `keep_rds`.
245 let device = Box::new(create_vu_multi_port_device(&opts.port, &mut Vec::new())?);
246 let ex = Executor::new().context("Failed to create executor")?;
247
248 let listener = VhostUserListener::new_socket(&opts.socket, None)?;
249
250 listener.run_device(ex, device)
251 }
252
253 /// Return a new vhost-user console device. `params` are the device's configuration, and `keep_rds`
254 /// is a vector into which `RawDescriptors` that need to survive a fork are added, in case the
255 /// device is meant to run within a child process.
create_vu_console_device( params: &SerialParameters, keep_rds: &mut Vec<RawDescriptor>, ) -> anyhow::Result<VhostUserConsoleDevice>256 pub fn create_vu_console_device(
257 params: &SerialParameters,
258 keep_rds: &mut Vec<RawDescriptor>,
259 ) -> anyhow::Result<VhostUserConsoleDevice> {
260 let device = params.create_serial_device::<ConsoleDevice>(
261 ProtectionType::Unprotected,
262 // We need to pass an event as per Serial Device API but we don't really use it anyway.
263 &Event::new()?,
264 keep_rds,
265 )?;
266
267 Ok(VhostUserConsoleDevice {
268 console: device,
269 raw_stdin: params.stdin,
270 })
271 }
272
273 /// Starts a vhost-user console device.
274 /// Returns an error if the given `args` is invalid or the device fails to run.
run_console_device(opts: Options) -> anyhow::Result<()>275 pub fn run_console_device(opts: Options) -> anyhow::Result<()> {
276 // try to start a multiport console first
277 if !opts.port.is_empty() {
278 return run_multi_port_device(opts);
279 }
280
281 // fall back to a multiport disabled console
282 let type_ = match opts.output_file {
283 Some(_) => {
284 if opts.syslog {
285 bail!("--output-file and --syslog options cannot be used together.");
286 }
287 SerialType::File
288 }
289 None => {
290 if opts.syslog {
291 SerialType::Syslog
292 } else {
293 SerialType::Stdout
294 }
295 }
296 };
297
298 let params = SerialParameters {
299 type_,
300 hardware: SerialHardware::VirtioConsole,
301 // Required only if type_ is SerialType::File or SerialType::UnixSocket
302 path: opts.output_file,
303 input: opts.input_file,
304 num: 1,
305 console: true,
306 earlycon: false,
307 // We don't use stdin if syslog mode is enabled
308 stdin: !opts.syslog,
309 out_timestamp: false,
310 ..Default::default()
311 };
312
313 // We won't jail the device and can simply ignore `keep_rds`.
314 let device = Box::new(create_vu_console_device(¶ms, &mut Vec::new())?);
315 let ex = Executor::new().context("Failed to create executor")?;
316
317 let listener = VhostUserListener::new_socket(&opts.socket, None)?;
318
319 listener.run_device(ex, device)
320 }
321