devices/
serial_device.rs

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
5use std::fmt;
6use std::fmt::Display;
7use std::fs::File;
8use std::fs::OpenOptions;
9use std::io;
10use std::io::stdin;
11use std::io::stdout;
12#[cfg(unix)]
13use std::os::unix::net::UnixStream;
14use std::path::PathBuf;
15
16use base::error;
17use base::open_file_or_duplicate;
18use base::syslog;
19#[cfg(windows)]
20use base::windows::Console as WinConsole;
21use base::AsRawDescriptor;
22use base::Event;
23use base::FileSync;
24use base::RawDescriptor;
25use base::ReadNotifier;
26use hypervisor::ProtectionType;
27use remain::sorted;
28use serde::Deserialize;
29use serde::Serialize;
30use serde_keyvalue::FromKeyValues;
31use thiserror::Error as ThisError;
32
33pub use crate::sys::serial_device::SerialDevice;
34use crate::sys::serial_device::*;
35use crate::PciAddress;
36
37#[sorted]
38#[derive(ThisError, Debug)]
39pub 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.
71pub trait SerialInput: io::Read + ReadNotifier + Send {}
72impl SerialInput for File {}
73#[cfg(unix)]
74impl SerialInput for UnixStream {}
75#[cfg(windows)]
76impl 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")]
81pub 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
94impl Default for SerialType {
95    fn default() -> Self {
96        Self::Sink
97    }
98}
99
100impl 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")]
119pub 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
131impl Default for SerialHardware {
132    fn default() -> Self {
133        Self::Serial
134    }
135}
136
137impl 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
149fn serial_parameters_default_num() -> u8 {
150    1
151}
152
153fn 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)]
160pub 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)]
190pub 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
198impl 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)]
313mod 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}