1 // Copyright 2020 The Chromium OS Authors. All rights reserved.
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::borrow::Cow;
6 use std::collections::BTreeMap;
7 use std::fmt::{self, Display};
8 use std::fs::{File, OpenOptions};
9 use std::io::{self, stdin, stdout, ErrorKind};
10 use std::os::unix::net::UnixDatagram;
11 use std::path::{Path, PathBuf};
12 use std::str::FromStr;
13 use std::sync::Arc;
14 use std::thread;
15 use std::time::Duration;
16 
17 use base::{error, info, read_raw_stdin, syslog, AsRawDescriptor, Event, RawDescriptor};
18 use devices::{Bus, ProtectionType, ProxyDevice, Serial, SerialDevice};
19 use minijail::Minijail;
20 use sync::Mutex;
21 
22 use crate::DeviceRegistrationError;
23 
24 #[derive(Debug)]
25 pub enum Error {
26     CloneEvent(base::Error),
27     FileError(std::io::Error),
28     InvalidSerialHardware(String),
29     InvalidSerialType(String),
30     InvalidPath,
31     PathRequired,
32     SocketCreateFailed,
33     Unimplemented(SerialType),
34 }
35 
36 impl Display for Error {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result37     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
38         use self::Error::*;
39 
40         match self {
41             CloneEvent(e) => write!(f, "unable to clone an Event: {}", e),
42             FileError(e) => write!(f, "unable to open/create file: {}", e),
43             InvalidSerialHardware(e) => write!(f, "invalid serial hardware: {}", e),
44             InvalidSerialType(e) => write!(f, "invalid serial type: {}", e),
45             InvalidPath => write!(f, "serial device path is invalid"),
46             PathRequired => write!(f, "serial device type file requires a path"),
47             SocketCreateFailed => write!(f, "failed to create unbound socket"),
48             Unimplemented(e) => write!(f, "serial device type {} not implemented", e.to_string()),
49         }
50     }
51 }
52 
53 /// Enum for possible type of serial devices
54 #[derive(Clone, Debug)]
55 pub enum SerialType {
56     File,
57     Stdout,
58     Sink,
59     Syslog,
60     UnixSocket,
61 }
62 
63 impl Display for SerialType {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result64     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
65         let s = match &self {
66             SerialType::File => "File".to_string(),
67             SerialType::Stdout => "Stdout".to_string(),
68             SerialType::Sink => "Sink".to_string(),
69             SerialType::Syslog => "Syslog".to_string(),
70             SerialType::UnixSocket => "UnixSocket".to_string(),
71         };
72 
73         write!(f, "{}", s)
74     }
75 }
76 
77 impl FromStr for SerialType {
78     type Err = Error;
from_str(s: &str) -> std::result::Result<Self, Self::Err>79     fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
80         match s {
81             "file" | "File" => Ok(SerialType::File),
82             "stdout" | "Stdout" => Ok(SerialType::Stdout),
83             "sink" | "Sink" => Ok(SerialType::Sink),
84             "syslog" | "Syslog" => Ok(SerialType::Syslog),
85             "unix" | "UnixSocket" => Ok(SerialType::UnixSocket),
86             _ => Err(Error::InvalidSerialType(s.to_string())),
87         }
88     }
89 }
90 
91 /// Serial device hardware types
92 #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
93 pub enum SerialHardware {
94     Serial,        // Standard PC-style (8250/16550 compatible) UART
95     VirtioConsole, // virtio-console device
96 }
97 
98 impl Display for SerialHardware {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result99     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
100         let s = match &self {
101             SerialHardware::Serial => "serial".to_string(),
102             SerialHardware::VirtioConsole => "virtio-console".to_string(),
103         };
104 
105         write!(f, "{}", s)
106     }
107 }
108 
109 impl FromStr for SerialHardware {
110     type Err = Error;
from_str(s: &str) -> std::result::Result<Self, Self::Err>111     fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
112         match s {
113             "serial" => Ok(SerialHardware::Serial),
114             "virtio-console" => Ok(SerialHardware::VirtioConsole),
115             _ => Err(Error::InvalidSerialHardware(s.to_string())),
116         }
117     }
118 }
119 
120 struct WriteSocket {
121     sock: UnixDatagram,
122     buf: String,
123 }
124 
125 const BUF_CAPACITY: usize = 1024;
126 
127 impl WriteSocket {
new(s: UnixDatagram) -> WriteSocket128     pub fn new(s: UnixDatagram) -> WriteSocket {
129         WriteSocket {
130             sock: s,
131             buf: String::with_capacity(BUF_CAPACITY),
132         }
133     }
134 
send_buf(&self, buf: &[u8]) -> io::Result<usize>135     pub fn send_buf(&self, buf: &[u8]) -> io::Result<usize> {
136         const SEND_RETRY: usize = 2;
137         let mut sent = 0;
138         for _ in 0..SEND_RETRY {
139             match self.sock.send(&buf[..]) {
140                 Ok(bytes_sent) => {
141                     sent = bytes_sent;
142                     break;
143                 }
144                 Err(e) => info!("Send error: {:?}", e),
145             }
146         }
147         Ok(sent)
148     }
149 }
150 
151 impl io::Write for WriteSocket {
write(&mut self, buf: &[u8]) -> io::Result<usize>152     fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
153         let parsed_str = String::from_utf8_lossy(buf);
154 
155         let last_newline_idx = match parsed_str.rfind('\n') {
156             Some(newline_idx) => Some(self.buf.len() + newline_idx),
157             None => None,
158         };
159         self.buf.push_str(&parsed_str);
160 
161         match last_newline_idx {
162             Some(last_newline_idx) => {
163                 for line in (self.buf[..last_newline_idx]).lines() {
164                     if self.send_buf(line.as_bytes()).is_err() {
165                         break;
166                     }
167                 }
168                 self.buf.drain(..=last_newline_idx);
169             }
170             None => {
171                 if self.buf.len() >= BUF_CAPACITY {
172                     if let Err(e) = self.send_buf(self.buf.as_bytes()) {
173                         info!("Couldn't send full buffer. {:?}", e);
174                     }
175                     self.buf.clear();
176                 }
177             }
178         }
179         Ok(buf.len())
180     }
181 
flush(&mut self) -> io::Result<()>182     fn flush(&mut self) -> io::Result<()> {
183         Ok(())
184     }
185 }
186 
187 /// Holds the parameters for a serial device
188 #[derive(Clone, Debug)]
189 pub struct SerialParameters {
190     pub type_: SerialType,
191     pub hardware: SerialHardware,
192     pub path: Option<PathBuf>,
193     pub input: Option<PathBuf>,
194     pub num: u8,
195     pub console: bool,
196     pub earlycon: bool,
197     pub stdin: bool,
198 }
199 
200 // The maximum length of a path that can be used as the address of a
201 // unix socket. Note that this includes the null-terminator.
202 const MAX_SOCKET_PATH_LENGTH: usize = 108;
203 
204 impl SerialParameters {
205     /// Helper function to create a serial device from the defined parameters.
206     ///
207     /// # Arguments
208     /// * `evt` - event used for interrupt events
209     /// * `keep_rds` - Vector of descriptors required by this device if it were sandboxed
210     ///                in a child process. `evt` will always be added to this vector by
211     ///                this function.
create_serial_device<T: SerialDevice>( &self, protected_vm: ProtectionType, evt: &Event, keep_rds: &mut Vec<RawDescriptor>, ) -> std::result::Result<T, Error>212     pub fn create_serial_device<T: SerialDevice>(
213         &self,
214         protected_vm: ProtectionType,
215         evt: &Event,
216         keep_rds: &mut Vec<RawDescriptor>,
217     ) -> std::result::Result<T, Error> {
218         let evt = evt.try_clone().map_err(Error::CloneEvent)?;
219         keep_rds.push(evt.as_raw_descriptor());
220         let input: Option<Box<dyn io::Read + Send>> = if let Some(input_path) = &self.input {
221             let input_file = File::open(input_path.as_path()).map_err(Error::FileError)?;
222             keep_rds.push(input_file.as_raw_descriptor());
223             Some(Box::new(input_file))
224         } else if self.stdin {
225             keep_rds.push(stdin().as_raw_descriptor());
226             // This wrapper is used in place of the libstd native version because we don't want
227             // buffering for stdin.
228             struct StdinWrapper;
229             impl io::Read for StdinWrapper {
230                 fn read(&mut self, out: &mut [u8]) -> io::Result<usize> {
231                     read_raw_stdin(out).map_err(|e| e.into())
232                 }
233             }
234             Some(Box::new(StdinWrapper))
235         } else {
236             None
237         };
238         let output: Option<Box<dyn io::Write + Send>> = match self.type_ {
239             SerialType::Stdout => {
240                 keep_rds.push(stdout().as_raw_descriptor());
241                 Some(Box::new(stdout()))
242             }
243             SerialType::Sink => None,
244             SerialType::Syslog => {
245                 syslog::push_descriptors(keep_rds);
246                 Some(Box::new(syslog::Syslogger::new(
247                     syslog::Priority::Info,
248                     syslog::Facility::Daemon,
249                 )))
250             }
251             SerialType::File => match &self.path {
252                 Some(path) => {
253                     let file = OpenOptions::new()
254                         .append(true)
255                         .create(true)
256                         .open(path.as_path())
257                         .map_err(Error::FileError)?;
258                     keep_rds.push(file.as_raw_descriptor());
259                     Some(Box::new(file))
260                 }
261                 None => return Err(Error::PathRequired),
262             },
263             SerialType::UnixSocket => {
264                 match &self.path {
265                     Some(path) => {
266                         // If the path is longer than 107 characters,
267                         // then we won't be able to connect directly
268                         // to it. Instead we can shorten the path by
269                         // opening the containing directory and using
270                         // /proc/self/fd/*/ to access it via a shorter
271                         // path.
272                         let mut path_cow = Cow::<Path>::Borrowed(path);
273                         let mut _dir_fd = None;
274                         if path.as_os_str().len() >= MAX_SOCKET_PATH_LENGTH {
275                             let mut short_path = PathBuf::with_capacity(MAX_SOCKET_PATH_LENGTH);
276                             short_path.push("/proc/self/fd/");
277 
278                             // We don't actually want to open this
279                             // directory for reading, but the stdlib
280                             // requires all files be opened as at
281                             // least one of readable, writeable, or
282                             // appeandable.
283                             let dir = OpenOptions::new()
284                                 .read(true)
285                                 .open(path.parent().ok_or(Error::InvalidPath)?)
286                                 .map_err(Error::FileError)?;
287 
288                             short_path.push(dir.as_raw_descriptor().to_string());
289                             short_path.push(path.file_name().ok_or(Error::InvalidPath)?);
290                             path_cow = Cow::Owned(short_path);
291                             _dir_fd = Some(dir);
292                         }
293 
294                         // The shortened path may still be too long,
295                         // in which case we must give up here.
296                         if path_cow.as_os_str().len() >= MAX_SOCKET_PATH_LENGTH {
297                             return Err(Error::InvalidPath);
298                         }
299 
300                         // There's a race condition between
301                         // vmlog_forwarder making the logging socket and
302                         // crosvm starting up, so we loop here until it's
303                         // available.
304                         let sock = UnixDatagram::unbound().map_err(Error::FileError)?;
305                         loop {
306                             match sock.connect(&path_cow) {
307                                 Ok(_) => break,
308                                 Err(e) => {
309                                     match e.kind() {
310                                         ErrorKind::NotFound | ErrorKind::ConnectionRefused => {
311                                             // logging socket doesn't
312                                             // exist yet, sleep for 10 ms
313                                             // and try again.
314                                             thread::sleep(Duration::from_millis(10))
315                                         }
316                                         _ => {
317                                             error!("Unexpected error connecting to logging socket: {:?}", e);
318                                             return Err(Error::FileError(e));
319                                         }
320                                     }
321                                 }
322                             };
323                         }
324                         keep_rds.push(sock.as_raw_descriptor());
325                         Some(Box::new(WriteSocket::new(sock)))
326                     }
327                     None => return Err(Error::PathRequired),
328                 }
329             }
330         };
331         Ok(T::new(protected_vm, evt, input, output, keep_rds.to_vec()))
332     }
333 
add_bind_mounts(&self, jail: &mut Minijail) -> Result<(), minijail::Error>334     pub fn add_bind_mounts(&self, jail: &mut Minijail) -> Result<(), minijail::Error> {
335         if let Some(path) = &self.path {
336             if let SerialType::UnixSocket = self.type_ {
337                 if let Some(parent) = path.as_path().parent() {
338                     if parent.exists() {
339                         info!("Bind mounting dir {}", parent.display());
340                         jail.mount_bind(parent, parent, true)?;
341                     }
342                 }
343             }
344         }
345         Ok(())
346     }
347 }
348 
349 /// Add the default serial parameters for serial ports that have not already been specified.
350 ///
351 /// This ensures that `serial_parameters` will contain parameters for each of the four PC-style
352 /// serial ports (COM1-COM4).
353 ///
354 /// It also sets the first `SerialHardware::Serial` to be the default console device if no other
355 /// serial parameters exist with console=true and the first serial device has not already been
356 /// configured explicitly.
set_default_serial_parameters( serial_parameters: &mut BTreeMap<(SerialHardware, u8), SerialParameters>, )357 pub fn set_default_serial_parameters(
358     serial_parameters: &mut BTreeMap<(SerialHardware, u8), SerialParameters>,
359 ) {
360     // If no console device exists and the first serial port has not been specified,
361     // set the first serial port as a stdout+stdin console.
362     let default_console = (SerialHardware::Serial, 1);
363     if !serial_parameters.iter().any(|(_, p)| p.console) {
364         serial_parameters
365             .entry(default_console)
366             .or_insert(SerialParameters {
367                 type_: SerialType::Stdout,
368                 hardware: SerialHardware::Serial,
369                 path: None,
370                 input: None,
371                 num: 1,
372                 console: true,
373                 earlycon: false,
374                 stdin: true,
375             });
376     }
377 
378     // Ensure all four of the COM ports exist.
379     // If one of these four SerialHardware::Serial port was not configured by the user,
380     // set it up as a sink.
381     for num in 1..=4 {
382         let key = (SerialHardware::Serial, num);
383         serial_parameters.entry(key).or_insert(SerialParameters {
384             type_: SerialType::Sink,
385             hardware: SerialHardware::Serial,
386             path: None,
387             input: None,
388             num,
389             console: false,
390             earlycon: false,
391             stdin: false,
392         });
393     }
394 }
395 
396 /// Address for Serial ports in x86
397 pub const SERIAL_ADDR: [u64; 4] = [0x3f8, 0x2f8, 0x3e8, 0x2e8];
398 
399 /// Adds serial devices to the provided bus based on the serial parameters given.
400 ///
401 /// Only devices with hardware type `SerialHardware::Serial` are added by this function.
402 ///
403 /// # Arguments
404 ///
405 /// * `io_bus` - Bus to add the devices to
406 /// * `com_evt_1_3` - event for com1 and com3
407 /// * `com_evt_1_4` - event for com2 and com4
408 /// * `io_bus` - Bus to add the devices to
409 /// * `serial_parameters` - definitions of serial parameter configurations.
410 ///   All four of the traditional PC-style serial ports (COM1-COM4) must be specified.
add_serial_devices( protected_vm: ProtectionType, io_bus: &mut Bus, com_evt_1_3: &Event, com_evt_2_4: &Event, serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>, serial_jail: Option<Minijail>, ) -> Result<(), DeviceRegistrationError>411 pub fn add_serial_devices(
412     protected_vm: ProtectionType,
413     io_bus: &mut Bus,
414     com_evt_1_3: &Event,
415     com_evt_2_4: &Event,
416     serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>,
417     serial_jail: Option<Minijail>,
418 ) -> Result<(), DeviceRegistrationError> {
419     for x in 0..=3 {
420         let com_evt = match x {
421             0 => com_evt_1_3,
422             1 => com_evt_2_4,
423             2 => com_evt_1_3,
424             3 => com_evt_2_4,
425             _ => com_evt_1_3,
426         };
427 
428         let param = serial_parameters
429             .get(&(SerialHardware::Serial, x + 1))
430             .ok_or(DeviceRegistrationError::MissingRequiredSerialDevice(x + 1))?;
431 
432         let mut preserved_fds = Vec::new();
433         let com = param
434             .create_serial_device::<Serial>(protected_vm, &com_evt, &mut preserved_fds)
435             .map_err(DeviceRegistrationError::CreateSerialDevice)?;
436 
437         match serial_jail.as_ref() {
438             Some(jail) => {
439                 let com = Arc::new(Mutex::new(
440                     ProxyDevice::new(com, &jail, preserved_fds)
441                         .map_err(DeviceRegistrationError::ProxyDeviceCreation)?,
442                 ));
443                 io_bus
444                     .insert(com.clone(), SERIAL_ADDR[x as usize], 0x8)
445                     .unwrap();
446             }
447             None => {
448                 let com = Arc::new(Mutex::new(com));
449                 io_bus
450                     .insert(com.clone(), SERIAL_ADDR[x as usize], 0x8)
451                     .unwrap();
452             }
453         }
454     }
455 
456     Ok(())
457 }
458 
459 #[derive(Debug)]
460 pub enum GetSerialCmdlineError {
461     KernelCmdline(kernel_cmdline::Error),
462     UnsupportedEarlyconHardware(SerialHardware),
463 }
464 
465 impl Display for GetSerialCmdlineError {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result466     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
467         use self::GetSerialCmdlineError::*;
468 
469         match self {
470             KernelCmdline(e) => write!(f, "error appending to cmdline: {}", e),
471             UnsupportedEarlyconHardware(hw) => {
472                 write!(f, "hardware {} not supported as earlycon", hw)
473             }
474         }
475     }
476 }
477 
478 pub type GetSerialCmdlineResult<T> = std::result::Result<T, GetSerialCmdlineError>;
479 
480 /// Add serial options to the provided `cmdline` based on `serial_parameters`.
481 /// `serial_io_type` should be "io" if the platform uses x86-style I/O ports for serial devices
482 /// or "mmio" if the serial ports are memory mapped.
get_serial_cmdline( cmdline: &mut kernel_cmdline::Cmdline, serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>, serial_io_type: &str, ) -> GetSerialCmdlineResult<()>483 pub fn get_serial_cmdline(
484     cmdline: &mut kernel_cmdline::Cmdline,
485     serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>,
486     serial_io_type: &str,
487 ) -> GetSerialCmdlineResult<()> {
488     match serial_parameters
489         .iter()
490         .filter(|(_, p)| p.console)
491         .map(|(k, _)| k)
492         .next()
493     {
494         Some((SerialHardware::Serial, num)) => {
495             cmdline
496                 .insert("console", &format!("ttyS{}", num - 1))
497                 .map_err(GetSerialCmdlineError::KernelCmdline)?;
498         }
499         Some((SerialHardware::VirtioConsole, num)) => {
500             cmdline
501                 .insert("console", &format!("hvc{}", num - 1))
502                 .map_err(GetSerialCmdlineError::KernelCmdline)?;
503         }
504         None => {}
505     }
506 
507     match serial_parameters
508         .iter()
509         .filter(|(_, p)| p.earlycon)
510         .map(|(k, _)| k)
511         .next()
512     {
513         Some((SerialHardware::Serial, num)) => {
514             if let Some(addr) = SERIAL_ADDR.get(*num as usize - 1) {
515                 cmdline
516                     .insert(
517                         "earlycon",
518                         &format!("uart8250,{},0x{:x}", serial_io_type, addr),
519                     )
520                     .map_err(GetSerialCmdlineError::KernelCmdline)?;
521             }
522         }
523         Some((hw, _num)) => {
524             return Err(GetSerialCmdlineError::UnsupportedEarlyconHardware(*hw));
525         }
526         None => {}
527     }
528 
529     Ok(())
530 }
531 
532 #[cfg(test)]
533 mod tests {
534     use super::*;
535     use kernel_cmdline::Cmdline;
536 
537     #[test]
get_serial_cmdline_default()538     fn get_serial_cmdline_default() {
539         let mut cmdline = Cmdline::new(4096);
540         let mut serial_parameters = BTreeMap::new();
541 
542         set_default_serial_parameters(&mut serial_parameters);
543         get_serial_cmdline(&mut cmdline, &serial_parameters, "io")
544             .expect("get_serial_cmdline failed");
545 
546         let cmdline_str = cmdline.as_str();
547         assert!(cmdline_str.contains("console=ttyS0"));
548     }
549 
550     #[test]
get_serial_cmdline_virtio_console()551     fn get_serial_cmdline_virtio_console() {
552         let mut cmdline = Cmdline::new(4096);
553         let mut serial_parameters = BTreeMap::new();
554 
555         // Add a virtio-console device with console=true.
556         serial_parameters.insert(
557             (SerialHardware::VirtioConsole, 1),
558             SerialParameters {
559                 type_: SerialType::Stdout,
560                 hardware: SerialHardware::VirtioConsole,
561                 path: None,
562                 input: None,
563                 num: 1,
564                 console: true,
565                 earlycon: false,
566                 stdin: true,
567             },
568         );
569 
570         set_default_serial_parameters(&mut serial_parameters);
571         get_serial_cmdline(&mut cmdline, &serial_parameters, "io")
572             .expect("get_serial_cmdline failed");
573 
574         let cmdline_str = cmdline.as_str();
575         assert!(cmdline_str.contains("console=hvc0"));
576     }
577 
578     #[test]
get_serial_cmdline_virtio_console_serial_earlycon()579     fn get_serial_cmdline_virtio_console_serial_earlycon() {
580         let mut cmdline = Cmdline::new(4096);
581         let mut serial_parameters = BTreeMap::new();
582 
583         // Add a virtio-console device with console=true.
584         serial_parameters.insert(
585             (SerialHardware::VirtioConsole, 1),
586             SerialParameters {
587                 type_: SerialType::Stdout,
588                 hardware: SerialHardware::VirtioConsole,
589                 path: None,
590                 input: None,
591                 num: 1,
592                 console: true,
593                 earlycon: false,
594                 stdin: true,
595             },
596         );
597 
598         // Override the default COM1 with an earlycon device.
599         serial_parameters.insert(
600             (SerialHardware::Serial, 1),
601             SerialParameters {
602                 type_: SerialType::Stdout,
603                 hardware: SerialHardware::Serial,
604                 path: None,
605                 input: None,
606                 num: 1,
607                 console: false,
608                 earlycon: true,
609                 stdin: false,
610             },
611         );
612 
613         set_default_serial_parameters(&mut serial_parameters);
614         get_serial_cmdline(&mut cmdline, &serial_parameters, "io")
615             .expect("get_serial_cmdline failed");
616 
617         let cmdline_str = cmdline.as_str();
618         assert!(cmdline_str.contains("console=hvc0"));
619         assert!(cmdline_str.contains("earlycon=uart8250,io,0x3f8"));
620     }
621 
622     #[test]
get_serial_cmdline_virtio_console_invalid_earlycon()623     fn get_serial_cmdline_virtio_console_invalid_earlycon() {
624         let mut cmdline = Cmdline::new(4096);
625         let mut serial_parameters = BTreeMap::new();
626 
627         // Try to add a virtio-console device with earlycon=true (unsupported).
628         serial_parameters.insert(
629             (SerialHardware::VirtioConsole, 1),
630             SerialParameters {
631                 type_: SerialType::Stdout,
632                 hardware: SerialHardware::VirtioConsole,
633                 path: None,
634                 input: None,
635                 num: 1,
636                 console: false,
637                 earlycon: true,
638                 stdin: true,
639             },
640         );
641 
642         set_default_serial_parameters(&mut serial_parameters);
643         get_serial_cmdline(&mut cmdline, &serial_parameters, "io")
644             .expect_err("get_serial_cmdline succeeded");
645     }
646 }
647