• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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(&params, &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