devices/virtio/vhost_user_backend/
console.rs1use 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
34pub struct VhostUserConsoleDevice {
37 console: ConsoleDevice,
38 raw_stdin: bool,
40}
41
42impl Drop for VhostUserConsoleDevice {
43 fn drop(&mut self) {
44 if self.raw_stdin {
45 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 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")]
131pub struct Options {
133 #[argh(option, arg_name = "PATH", hidden_help)]
134 socket: Option<String>,
136 #[argh(option, arg_name = "PATH")]
137 socket_path: Option<String>,
140 #[argh(option, arg_name = "FD")]
141 fd: Option<RawDescriptor>,
144
145 #[argh(option, arg_name = "OUTFILE")]
146 output_file: Option<PathBuf>,
148 #[argh(option, arg_name = "INFILE")]
149 input_file: Option<PathBuf>,
151 #[argh(switch)]
153 syslog: bool,
154 #[argh(option, arg_name = "type=TYPE,[path=PATH,input=PATH,console]")]
155 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 &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, })
186}
187
188fn 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 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
204pub 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 &Event::new()?,
215 keep_rds,
216 )?;
217
218 Ok(VhostUserConsoleDevice {
219 console: device,
220 raw_stdin: params.stdin,
221 })
222}
223
224pub fn run_console_device(opts: Options) -> anyhow::Result<()> {
227 if !opts.port.is_empty() {
229 return run_multi_port_device(opts);
230 }
231
232 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 path: opts.output_file,
254 input: opts.input_file,
255 num: 1,
256 console: true,
257 earlycon: false,
258 stdin: !opts.syslog,
260 out_timestamp: false,
261 ..Default::default()
262 };
263
264 let device = Box::new(create_vu_console_device(¶ms, &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}