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