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 {
default() -> Self95 fn default() -> Self {
96 Self::Sink
97 }
98 }
99
100 impl Display for SerialType {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result101 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 {
default() -> Self132 fn default() -> Self {
133 Self::Serial
134 }
135 }
136
137 impl Display for SerialHardware {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result138 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
serial_parameters_default_num() -> u8149 fn serial_parameters_default_num() -> u8 {
150 1
151 }
152
serial_parameters_default_debugcon_port() -> u16153 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 }
185
186 /// Temporary structure containing the parameters of a serial port for easy passing to
187 /// `SerialDevice::new`.
188 #[derive(Default)]
189 pub struct SerialOptions {
190 pub name: Option<String>,
191 pub out_timestamp: bool,
192 pub console: bool,
193 pub pci_address: Option<PciAddress>,
194 }
195
196 impl SerialParameters {
197 /// Helper function to create a serial device from the defined parameters.
198 ///
199 /// # Arguments
200 /// * `evt` - event used for interrupt events
201 /// * `keep_rds` - Vector of FDs required by this device if it were sandboxed in a child
202 /// process. `evt` will always be added to this vector by this function.
create_serial_device<T: SerialDevice>( &self, protection_type: ProtectionType, evt: &Event, keep_rds: &mut Vec<RawDescriptor>, ) -> std::result::Result<T, Error>203 pub fn create_serial_device<T: SerialDevice>(
204 &self,
205 protection_type: ProtectionType,
206 evt: &Event,
207 keep_rds: &mut Vec<RawDescriptor>,
208 ) -> std::result::Result<T, Error> {
209 let evt = evt.try_clone().map_err(Error::CloneEvent)?;
210 keep_rds.push(evt.as_raw_descriptor());
211 cros_tracing::push_descriptors!(keep_rds);
212 metrics::push_descriptors(keep_rds);
213
214 // When `self.input_unix_stream` is specified, use `self.path` for both output and input.
215 #[cfg(unix)]
216 if self.input_unix_stream {
217 if self.input.is_some() {
218 return Err(Error::InvalidConfig(
219 "input-unix-stream can't be passed when input is specified".to_string(),
220 ));
221 }
222 if self.type_ != SerialType::UnixStream {
223 return Err(Error::InvalidConfig(
224 "input-unix-stream must be used with type=unix-stream".to_string(),
225 ));
226 }
227
228 return create_unix_stream_serial_device(self, protection_type, evt, keep_rds);
229 }
230
231 let input: Option<Box<dyn SerialInput>> = if let Some(input_path) = &self.input {
232 let input_path = input_path.as_path();
233
234 let input_file = open_file_or_duplicate(input_path, OpenOptions::new().read(true))
235 .map_err(|e| Error::FileOpen(e.into(), input_path.into()))?;
236
237 keep_rds.push(input_file.as_raw_descriptor());
238 Some(Box::new(input_file))
239 } else if self.stdin {
240 keep_rds.push(stdin().as_raw_descriptor());
241 Some(Box::new(ConsoleInput::new()))
242 } else {
243 None
244 };
245 let (output, sync): (
246 Option<Box<dyn io::Write + Send>>,
247 Option<Box<dyn FileSync + Send>>,
248 ) = match self.type_ {
249 SerialType::Stdout => {
250 keep_rds.push(stdout().as_raw_descriptor());
251 (Some(Box::new(stdout())), None)
252 }
253 SerialType::Sink => (None, None),
254 SerialType::Syslog => {
255 syslog::push_descriptors(keep_rds);
256 (
257 Some(Box::new(syslog::Syslogger::new(base::syslog::Level::Info))),
258 None,
259 )
260 }
261 SerialType::File => match &self.path {
262 Some(path) => {
263 let file =
264 open_file_or_duplicate(path, OpenOptions::new().append(true).create(true))
265 .map_err(|e| Error::FileCreate(e.into(), path.clone()))?;
266 let sync = file.try_clone().map_err(Error::FileClone)?;
267
268 keep_rds.push(file.as_raw_descriptor());
269 keep_rds.push(sync.as_raw_descriptor());
270
271 (Some(Box::new(file)), Some(Box::new(sync)))
272 }
273 None => return Err(Error::PathRequired),
274 },
275 SerialType::SystemSerialType => {
276 return create_system_type_serial_device(
277 self,
278 protection_type,
279 evt,
280 input,
281 keep_rds,
282 );
283 }
284 #[cfg(unix)]
285 SerialType::UnixStream => {
286 let path = self.path.as_ref().ok_or(Error::PathRequired)?;
287 let output = UnixStream::connect(path).map_err(Error::SocketConnect)?;
288 keep_rds.push(output.as_raw_descriptor());
289 (Some(Box::new(output)), None)
290 }
291 };
292 Ok(T::new(
293 protection_type,
294 evt,
295 input,
296 output,
297 sync,
298 SerialOptions {
299 name: self.name.clone(),
300 out_timestamp: self.out_timestamp,
301 console: self.console,
302 pci_address: self.pci_address,
303 },
304 keep_rds.to_vec(),
305 ))
306 }
307 }
308
309 #[cfg(test)]
310 mod tests {
311 use serde_keyvalue::*;
312
313 use super::*;
314
from_serial_arg(options: &str) -> Result<SerialParameters, ParseError>315 fn from_serial_arg(options: &str) -> Result<SerialParameters, ParseError> {
316 from_key_values(options)
317 }
318
319 #[test]
params_from_key_values()320 fn params_from_key_values() {
321 // Defaults
322 let params = from_serial_arg("").unwrap();
323 assert_eq!(
324 params,
325 SerialParameters {
326 type_: SerialType::Sink,
327 hardware: SerialHardware::Serial,
328 name: None,
329 path: None,
330 input: None,
331 #[cfg(unix)]
332 input_unix_stream: false,
333 num: 1,
334 console: false,
335 earlycon: false,
336 stdin: false,
337 out_timestamp: false,
338 debugcon_port: 0x402,
339 pci_address: None,
340 }
341 );
342
343 // type parameter
344 let params = from_serial_arg("type=file").unwrap();
345 assert_eq!(params.type_, SerialType::File);
346 let params = from_serial_arg("type=stdout").unwrap();
347 assert_eq!(params.type_, SerialType::Stdout);
348 let params = from_serial_arg("type=sink").unwrap();
349 assert_eq!(params.type_, SerialType::Sink);
350 let params = from_serial_arg("type=syslog").unwrap();
351 assert_eq!(params.type_, SerialType::Syslog);
352 #[cfg(any(target_os = "android", target_os = "linux"))]
353 let opt = "type=unix";
354 #[cfg(windows)]
355 let opt = "type=namedpipe";
356 let params = from_serial_arg(opt).unwrap();
357 assert_eq!(params.type_, SerialType::SystemSerialType);
358 #[cfg(unix)]
359 {
360 let params = from_serial_arg("type=unix-stream").unwrap();
361 assert_eq!(params.type_, SerialType::UnixStream);
362 }
363 let params = from_serial_arg("type=foobar");
364 assert!(params.is_err());
365
366 // hardware parameter
367 let params = from_serial_arg("hardware=serial").unwrap();
368 assert_eq!(params.hardware, SerialHardware::Serial);
369 let params = from_serial_arg("hardware=virtio-console").unwrap();
370 assert_eq!(params.hardware, SerialHardware::VirtioConsole);
371 let params = from_serial_arg("hardware=debugcon").unwrap();
372 assert_eq!(params.hardware, SerialHardware::Debugcon);
373 let params = from_serial_arg("hardware=foobar");
374 assert!(params.is_err());
375
376 // path parameter
377 let params = from_serial_arg("path=/test/path").unwrap();
378 assert_eq!(params.path, Some("/test/path".into()));
379 let params = from_serial_arg("path");
380 assert!(params.is_err());
381
382 // input parameter
383 let params = from_serial_arg("input=/path/to/input").unwrap();
384 assert_eq!(params.input, Some("/path/to/input".into()));
385 let params = from_serial_arg("input");
386 assert!(params.is_err());
387
388 #[cfg(unix)]
389 {
390 // input-unix-stream parameter
391 let params = from_serial_arg("input-unix-stream").unwrap();
392 assert!(params.input_unix_stream);
393 let params = from_serial_arg("input-unix-stream=true").unwrap();
394 assert!(params.input_unix_stream);
395 let params = from_serial_arg("input-unix-stream=false").unwrap();
396 assert!(!params.input_unix_stream);
397 let params = from_serial_arg("input-unix-stream=foobar");
398 assert!(params.is_err());
399 }
400
401 // console parameter
402 let params = from_serial_arg("console").unwrap();
403 assert!(params.console);
404 let params = from_serial_arg("console=true").unwrap();
405 assert!(params.console);
406 let params = from_serial_arg("console=false").unwrap();
407 assert!(!params.console);
408 let params = from_serial_arg("console=foobar");
409 assert!(params.is_err());
410
411 // earlycon parameter
412 let params = from_serial_arg("earlycon").unwrap();
413 assert!(params.earlycon);
414 let params = from_serial_arg("earlycon=true").unwrap();
415 assert!(params.earlycon);
416 let params = from_serial_arg("earlycon=false").unwrap();
417 assert!(!params.earlycon);
418 let params = from_serial_arg("earlycon=foobar");
419 assert!(params.is_err());
420
421 // stdin parameter
422 let params = from_serial_arg("stdin").unwrap();
423 assert!(params.stdin);
424 let params = from_serial_arg("stdin=true").unwrap();
425 assert!(params.stdin);
426 let params = from_serial_arg("stdin=false").unwrap();
427 assert!(!params.stdin);
428 let params = from_serial_arg("stdin=foobar");
429 assert!(params.is_err());
430
431 // out-timestamp parameter
432 let params = from_serial_arg("out-timestamp").unwrap();
433 assert!(params.out_timestamp);
434 let params = from_serial_arg("out-timestamp=true").unwrap();
435 assert!(params.out_timestamp);
436 let params = from_serial_arg("out-timestamp=false").unwrap();
437 assert!(!params.out_timestamp);
438 let params = from_serial_arg("out-timestamp=foobar");
439 assert!(params.is_err());
440 // backward compatibility
441 let params = from_serial_arg("out_timestamp=true").unwrap();
442 assert!(params.out_timestamp);
443
444 // debugcon-port parameter
445 let params = from_serial_arg("debugcon-port=1026").unwrap();
446 assert_eq!(params.debugcon_port, 1026);
447 // backward compatibility
448 let params = from_serial_arg("debugcon_port=1026").unwrap();
449 assert_eq!(params.debugcon_port, 1026);
450
451 // all together
452 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").unwrap();
453 assert_eq!(
454 params,
455 SerialParameters {
456 type_: SerialType::Stdout,
457 hardware: SerialHardware::VirtioConsole,
458 name: None,
459 path: Some("/some/path".into()),
460 input: Some("/some/input".into()),
461 #[cfg(unix)]
462 input_unix_stream: false,
463 num: 5,
464 console: true,
465 earlycon: true,
466 stdin: true,
467 out_timestamp: true,
468 debugcon_port: 12,
469 pci_address: Some(PciAddress {
470 bus: 0,
471 dev: 14,
472 func: 0
473 }),
474 }
475 );
476
477 // invalid field
478 let params = from_serial_arg("type=stdout,foo=bar");
479 assert!(params.is_err());
480 }
481 }
482