Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
google
GitHub Repository: google/crosvm
Path: blob/main/devices/src/serial_device.rs
5394 views
1
// Copyright 2020 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::fmt;
6
use std::fmt::Display;
7
use std::fs::File;
8
use std::fs::OpenOptions;
9
use std::io;
10
use std::io::stdin;
11
use std::io::stdout;
12
#[cfg(unix)]
13
use std::os::unix::net::UnixStream;
14
use std::path::PathBuf;
15
16
use base::error;
17
use base::open_file_or_duplicate;
18
use base::syslog;
19
#[cfg(windows)]
20
use base::windows::Console as WinConsole;
21
use base::AsRawDescriptor;
22
use base::Event;
23
use base::FileSync;
24
use base::RawDescriptor;
25
use base::ReadNotifier;
26
use hypervisor::ProtectionType;
27
use remain::sorted;
28
use serde::Deserialize;
29
use serde::Serialize;
30
use serde_keyvalue::FromKeyValues;
31
use thiserror::Error as ThisError;
32
33
pub use crate::sys::serial_device::SerialDevice;
34
use crate::sys::serial_device::*;
35
use crate::PciAddress;
36
37
#[sorted]
38
#[derive(ThisError, Debug)]
39
pub enum Error {
40
#[error("Unable to clone an Event: {0}")]
41
CloneEvent(base::Error),
42
#[error("Unable to clone a Unix Stream: {0}")]
43
CloneUnixStream(std::io::Error),
44
#[error("Unable to clone file: {0}")]
45
FileClone(std::io::Error),
46
#[error("Unable to create file '{1}': {0}")]
47
FileCreate(std::io::Error, PathBuf),
48
#[error("Unable to open file '{1}': {0}")]
49
FileOpen(std::io::Error, PathBuf),
50
#[error("Invalid serial config specified: {0}")]
51
InvalidConfig(String),
52
#[error("Serial device path '{0} is invalid")]
53
InvalidPath(PathBuf),
54
#[error("Invalid serial hardware: {0}")]
55
InvalidSerialHardware(String),
56
#[error("Invalid serial type: {0}")]
57
InvalidSerialType(String),
58
#[error("Serial device type file requires a path")]
59
PathRequired,
60
#[error("Failed to connect to socket: {0}")]
61
SocketConnect(std::io::Error),
62
#[error("Failed to create unbound socket: {0}")]
63
SocketCreate(std::io::Error),
64
#[error("Unable to open system type serial: {0}")]
65
SystemTypeError(std::io::Error),
66
#[error("Serial device type {0} not implemented")]
67
Unimplemented(SerialType),
68
}
69
70
/// Trait for types that can be used as input for a serial device.
71
pub trait SerialInput: io::Read + ReadNotifier + Send {}
72
impl SerialInput for File {}
73
#[cfg(unix)]
74
impl SerialInput for UnixStream {}
75
#[cfg(windows)]
76
impl SerialInput for WinConsole {}
77
78
/// Enum for possible type of serial devices
79
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
80
#[serde(rename_all = "kebab-case")]
81
pub enum SerialType {
82
File,
83
Stdout,
84
Sink,
85
Syslog,
86
#[cfg_attr(unix, serde(rename = "unix"))]
87
#[cfg_attr(windows, serde(rename = "namedpipe"))]
88
SystemSerialType,
89
// Use the same Unix domain socket for input and output.
90
#[cfg(unix)]
91
UnixStream,
92
}
93
94
impl Default for SerialType {
95
fn default() -> Self {
96
Self::Sink
97
}
98
}
99
100
impl Display for SerialType {
101
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
102
let s = match &self {
103
SerialType::File => "File".to_string(),
104
SerialType::Stdout => "Stdout".to_string(),
105
SerialType::Sink => "Sink".to_string(),
106
SerialType::Syslog => "Syslog".to_string(),
107
SerialType::SystemSerialType => SYSTEM_SERIAL_TYPE_NAME.to_string(),
108
#[cfg(unix)]
109
SerialType::UnixStream => "UnixStream".to_string(),
110
};
111
112
write!(f, "{s}")
113
}
114
}
115
116
/// Serial device hardware types
117
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
118
#[serde(rename_all = "kebab-case")]
119
pub enum SerialHardware {
120
/// Standard PC-style (8250/16550 compatible) UART
121
Serial,
122
123
/// virtio-console device
124
#[serde(alias = "legacy-virtio-console")]
125
VirtioConsole,
126
127
/// Bochs style debug port
128
Debugcon,
129
}
130
131
impl Default for SerialHardware {
132
fn default() -> Self {
133
Self::Serial
134
}
135
}
136
137
impl Display for SerialHardware {
138
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
139
let s = match &self {
140
SerialHardware::Serial => "serial".to_string(),
141
SerialHardware::VirtioConsole => "virtio-console".to_string(),
142
SerialHardware::Debugcon => "debugcon".to_string(),
143
};
144
145
write!(f, "{s}")
146
}
147
}
148
149
fn serial_parameters_default_num() -> u8 {
150
1
151
}
152
153
fn serial_parameters_default_debugcon_port() -> u16 {
154
// Default to the port OVMF expects.
155
0x402
156
}
157
158
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, FromKeyValues)]
159
#[serde(deny_unknown_fields, rename_all = "kebab-case", default)]
160
pub struct SerialParameters {
161
#[serde(rename = "type")]
162
pub type_: SerialType,
163
pub hardware: SerialHardware,
164
pub name: Option<String>,
165
pub path: Option<PathBuf>,
166
pub input: Option<PathBuf>,
167
/// Use the given `UnixStream` as input as well as output.
168
/// This flag can be used only when `type_` is `UnixStream`.
169
#[cfg(unix)]
170
pub input_unix_stream: bool,
171
#[serde(default = "serial_parameters_default_num")]
172
pub num: u8,
173
pub console: bool,
174
pub earlycon: bool,
175
pub stdin: bool,
176
#[serde(alias = "out_timestamp")]
177
pub out_timestamp: bool,
178
#[serde(
179
alias = "debugcon_port",
180
default = "serial_parameters_default_debugcon_port"
181
)]
182
pub debugcon_port: u16,
183
pub pci_address: Option<PciAddress>,
184
pub max_queue_sizes: Option<Vec<u16>>,
185
}
186
187
/// Temporary structure containing the parameters of a serial port for easy passing to
188
/// `SerialDevice::new`.
189
#[derive(Default)]
190
pub struct SerialOptions {
191
pub name: Option<String>,
192
pub out_timestamp: bool,
193
pub console: bool,
194
pub pci_address: Option<PciAddress>,
195
pub max_queue_sizes: Option<Vec<u16>>,
196
}
197
198
impl SerialParameters {
199
/// Helper function to create a serial device from the defined parameters.
200
///
201
/// # Arguments
202
/// * `evt` - event used for interrupt events
203
/// * `keep_rds` - Vector of FDs required by this device if it were sandboxed in a child
204
/// process. `evt` will always be added to this vector by this function.
205
pub fn create_serial_device<T: SerialDevice>(
206
&self,
207
protection_type: ProtectionType,
208
evt: &Event,
209
keep_rds: &mut Vec<RawDescriptor>,
210
) -> std::result::Result<T, Error> {
211
let evt = evt.try_clone().map_err(Error::CloneEvent)?;
212
keep_rds.push(evt.as_raw_descriptor());
213
cros_tracing::push_descriptors!(keep_rds);
214
metrics::push_descriptors(keep_rds);
215
216
// When `self.input_unix_stream` is specified, use `self.path` for both output and input.
217
#[cfg(unix)]
218
if self.input_unix_stream {
219
if self.input.is_some() {
220
return Err(Error::InvalidConfig(
221
"input-unix-stream can't be passed when input is specified".to_string(),
222
));
223
}
224
if self.type_ != SerialType::UnixStream {
225
return Err(Error::InvalidConfig(
226
"input-unix-stream must be used with type=unix-stream".to_string(),
227
));
228
}
229
230
return create_unix_stream_serial_device(self, protection_type, evt, keep_rds);
231
}
232
233
let input: Option<Box<dyn SerialInput>> = if let Some(input_path) = &self.input {
234
let input_path = input_path.as_path();
235
236
let input_file = open_file_or_duplicate(input_path, OpenOptions::new().read(true))
237
.map_err(|e| Error::FileOpen(e.into(), input_path.into()))?;
238
239
keep_rds.push(input_file.as_raw_descriptor());
240
Some(Box::new(input_file))
241
} else if self.stdin {
242
keep_rds.push(stdin().as_raw_descriptor());
243
Some(Box::new(ConsoleInput::new()))
244
} else {
245
None
246
};
247
let (output, sync): (
248
Option<Box<dyn io::Write + Send>>,
249
Option<Box<dyn FileSync + Send>>,
250
) = match self.type_ {
251
SerialType::Stdout => {
252
keep_rds.push(stdout().as_raw_descriptor());
253
(Some(Box::new(stdout())), None)
254
}
255
SerialType::Sink => (None, None),
256
SerialType::Syslog => {
257
syslog::push_descriptors(keep_rds);
258
(
259
Some(Box::new(syslog::Syslogger::new(base::syslog::Level::Info))),
260
None,
261
)
262
}
263
SerialType::File => match &self.path {
264
Some(path) => {
265
let file =
266
open_file_or_duplicate(path, OpenOptions::new().append(true).create(true))
267
.map_err(|e| Error::FileCreate(e.into(), path.clone()))?;
268
let sync = file.try_clone().map_err(Error::FileClone)?;
269
270
keep_rds.push(file.as_raw_descriptor());
271
keep_rds.push(sync.as_raw_descriptor());
272
273
(Some(Box::new(file)), Some(Box::new(sync)))
274
}
275
None => return Err(Error::PathRequired),
276
},
277
SerialType::SystemSerialType => {
278
return create_system_type_serial_device(
279
self,
280
protection_type,
281
evt,
282
input,
283
keep_rds,
284
);
285
}
286
#[cfg(unix)]
287
SerialType::UnixStream => {
288
let path = self.path.as_ref().ok_or(Error::PathRequired)?;
289
let output = UnixStream::connect(path).map_err(Error::SocketConnect)?;
290
keep_rds.push(output.as_raw_descriptor());
291
(Some(Box::new(output)), None)
292
}
293
};
294
Ok(T::new(
295
protection_type,
296
evt,
297
input,
298
output,
299
sync,
300
SerialOptions {
301
name: self.name.clone(),
302
out_timestamp: self.out_timestamp,
303
console: self.console,
304
pci_address: self.pci_address,
305
max_queue_sizes: self.max_queue_sizes.clone(),
306
},
307
keep_rds.to_vec(),
308
))
309
}
310
}
311
312
#[cfg(test)]
313
mod tests {
314
use serde_keyvalue::*;
315
316
use super::*;
317
318
fn from_serial_arg(options: &str) -> Result<SerialParameters, ParseError> {
319
from_key_values(options)
320
}
321
322
#[test]
323
fn params_from_key_values() {
324
// Defaults
325
let params = from_serial_arg("").unwrap();
326
assert_eq!(
327
params,
328
SerialParameters {
329
type_: SerialType::Sink,
330
hardware: SerialHardware::Serial,
331
name: None,
332
path: None,
333
input: None,
334
#[cfg(unix)]
335
input_unix_stream: false,
336
num: 1,
337
console: false,
338
earlycon: false,
339
stdin: false,
340
out_timestamp: false,
341
debugcon_port: 0x402,
342
pci_address: None,
343
max_queue_sizes: None,
344
}
345
);
346
347
// type parameter
348
let params = from_serial_arg("type=file").unwrap();
349
assert_eq!(params.type_, SerialType::File);
350
let params = from_serial_arg("type=stdout").unwrap();
351
assert_eq!(params.type_, SerialType::Stdout);
352
let params = from_serial_arg("type=sink").unwrap();
353
assert_eq!(params.type_, SerialType::Sink);
354
let params = from_serial_arg("type=syslog").unwrap();
355
assert_eq!(params.type_, SerialType::Syslog);
356
#[cfg(any(target_os = "android", target_os = "linux"))]
357
let opt = "type=unix";
358
#[cfg(windows)]
359
let opt = "type=namedpipe";
360
let params = from_serial_arg(opt).unwrap();
361
assert_eq!(params.type_, SerialType::SystemSerialType);
362
#[cfg(unix)]
363
{
364
let params = from_serial_arg("type=unix-stream").unwrap();
365
assert_eq!(params.type_, SerialType::UnixStream);
366
}
367
let params = from_serial_arg("type=foobar");
368
assert!(params.is_err());
369
370
// hardware parameter
371
let params = from_serial_arg("hardware=serial").unwrap();
372
assert_eq!(params.hardware, SerialHardware::Serial);
373
let params = from_serial_arg("hardware=virtio-console").unwrap();
374
assert_eq!(params.hardware, SerialHardware::VirtioConsole);
375
let params = from_serial_arg("hardware=debugcon").unwrap();
376
assert_eq!(params.hardware, SerialHardware::Debugcon);
377
let params = from_serial_arg("hardware=foobar");
378
assert!(params.is_err());
379
380
// path parameter
381
let params = from_serial_arg("path=/test/path").unwrap();
382
assert_eq!(params.path, Some("/test/path".into()));
383
let params = from_serial_arg("path");
384
assert!(params.is_err());
385
386
// input parameter
387
let params = from_serial_arg("input=/path/to/input").unwrap();
388
assert_eq!(params.input, Some("/path/to/input".into()));
389
let params = from_serial_arg("input");
390
assert!(params.is_err());
391
392
#[cfg(unix)]
393
{
394
// input-unix-stream parameter
395
let params = from_serial_arg("input-unix-stream").unwrap();
396
assert!(params.input_unix_stream);
397
let params = from_serial_arg("input-unix-stream=true").unwrap();
398
assert!(params.input_unix_stream);
399
let params = from_serial_arg("input-unix-stream=false").unwrap();
400
assert!(!params.input_unix_stream);
401
let params = from_serial_arg("input-unix-stream=foobar");
402
assert!(params.is_err());
403
}
404
405
// console parameter
406
let params = from_serial_arg("console").unwrap();
407
assert!(params.console);
408
let params = from_serial_arg("console=true").unwrap();
409
assert!(params.console);
410
let params = from_serial_arg("console=false").unwrap();
411
assert!(!params.console);
412
let params = from_serial_arg("console=foobar");
413
assert!(params.is_err());
414
415
// earlycon parameter
416
let params = from_serial_arg("earlycon").unwrap();
417
assert!(params.earlycon);
418
let params = from_serial_arg("earlycon=true").unwrap();
419
assert!(params.earlycon);
420
let params = from_serial_arg("earlycon=false").unwrap();
421
assert!(!params.earlycon);
422
let params = from_serial_arg("earlycon=foobar");
423
assert!(params.is_err());
424
425
// stdin parameter
426
let params = from_serial_arg("stdin").unwrap();
427
assert!(params.stdin);
428
let params = from_serial_arg("stdin=true").unwrap();
429
assert!(params.stdin);
430
let params = from_serial_arg("stdin=false").unwrap();
431
assert!(!params.stdin);
432
let params = from_serial_arg("stdin=foobar");
433
assert!(params.is_err());
434
435
// out-timestamp parameter
436
let params = from_serial_arg("out-timestamp").unwrap();
437
assert!(params.out_timestamp);
438
let params = from_serial_arg("out-timestamp=true").unwrap();
439
assert!(params.out_timestamp);
440
let params = from_serial_arg("out-timestamp=false").unwrap();
441
assert!(!params.out_timestamp);
442
let params = from_serial_arg("out-timestamp=foobar");
443
assert!(params.is_err());
444
// backward compatibility
445
let params = from_serial_arg("out_timestamp=true").unwrap();
446
assert!(params.out_timestamp);
447
448
// debugcon-port parameter
449
let params = from_serial_arg("debugcon-port=1026").unwrap();
450
assert_eq!(params.debugcon_port, 1026);
451
// backward compatibility
452
let params = from_serial_arg("debugcon_port=1026").unwrap();
453
assert_eq!(params.debugcon_port, 1026);
454
455
// all together
456
let params = from_serial_arg("type=stdout,path=/some/path,hardware=virtio-console,num=5,earlycon,console,stdin,input=/some/input,out_timestamp,debugcon_port=12,pci-address=00:0e.0,max-queue-sizes=[1,2]").unwrap();
457
assert_eq!(
458
params,
459
SerialParameters {
460
type_: SerialType::Stdout,
461
hardware: SerialHardware::VirtioConsole,
462
name: None,
463
path: Some("/some/path".into()),
464
input: Some("/some/input".into()),
465
#[cfg(unix)]
466
input_unix_stream: false,
467
num: 5,
468
console: true,
469
earlycon: true,
470
stdin: true,
471
out_timestamp: true,
472
debugcon_port: 12,
473
pci_address: Some(PciAddress {
474
bus: 0,
475
dev: 14,
476
func: 0
477
}),
478
max_queue_sizes: Some(vec![1, 2]),
479
}
480
);
481
482
// invalid field
483
let params = from_serial_arg("type=stdout,foo=bar");
484
assert!(params.is_err());
485
}
486
}
487
488