1use 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
70pub 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#[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 #[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#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
118#[serde(rename_all = "kebab-case")]
119pub enum SerialHardware {
120 Serial,
122
123 #[serde(alias = "legacy-virtio-console")]
125 VirtioConsole,
126
127 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 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 #[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#[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 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 #[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 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 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 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 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 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 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 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 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 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 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 let params = from_serial_arg("out_timestamp=true").unwrap();
446 assert!(params.out_timestamp);
447
448 let params = from_serial_arg("debugcon-port=1026").unwrap();
450 assert_eq!(params.debugcon_port, 1026);
451 let params = from_serial_arg("debugcon_port=1026").unwrap();
453 assert_eq!(params.debugcon_port, 1026);
454
455 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 let params = from_serial_arg("type=stdout,foo=bar");
484 assert!(params.is_err());
485 }
486}