xref: /aosp_15_r20/external/crosvm/devices/src/serial_device.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
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