// Copyright 2020 The Chromium OS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. use std::collections::BTreeMap; use std::fmt::{self, Display}; use std::fs::File; use std::io::{self, stdin, stdout}; use std::os::unix::io::{AsRawFd, RawFd}; use std::path::PathBuf; use std::str::FromStr; use std::sync::Arc; use devices::{Bus, ProxyDevice, Serial}; use io_jail::Minijail; use sync::Mutex; use sys_util::{read_raw_stdin, syslog, EventFd}; use crate::DeviceRegistrationError; #[derive(Debug)] pub enum Error { CloneEventFd(sys_util::Error), FileError(std::io::Error), InvalidSerialType(String), PathRequired, Unimplemented(SerialType), } impl Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use self::Error::*; match self { CloneEventFd(e) => write!(f, "unable to clone an EventFd: {}", e), FileError(e) => write!(f, "unable to open/create file: {}", e), InvalidSerialType(e) => write!(f, "invalid serial type: {}", e), PathRequired => write!(f, "serial device type file requires a path"), Unimplemented(e) => write!(f, "serial device type {} not implemented", e.to_string()), } } } /// Enum for possible type of serial devices #[derive(Debug)] pub enum SerialType { File, Stdout, Sink, Syslog, UnixSocket, // NOT IMPLEMENTED } impl Display for SerialType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let s = match &self { SerialType::File => "File".to_string(), SerialType::Stdout => "Stdout".to_string(), SerialType::Sink => "Sink".to_string(), SerialType::Syslog => "Syslog".to_string(), SerialType::UnixSocket => "UnixSocket".to_string(), }; write!(f, "{}", s) } } impl FromStr for SerialType { type Err = Error; fn from_str(s: &str) -> std::result::Result { match s { "file" | "File" => Ok(SerialType::File), "stdout" | "Stdout" => Ok(SerialType::Stdout), "sink" | "Sink" => Ok(SerialType::Sink), "syslog" | "Syslog" => Ok(SerialType::Syslog), "unix" | "UnixSocket" => Ok(SerialType::UnixSocket), _ => Err(Error::InvalidSerialType(s.to_string())), } } } /// Holds the parameters for a serial device #[derive(Debug)] pub struct SerialParameters { pub type_: SerialType, pub path: Option, pub input: Option, pub num: u8, pub console: bool, pub stdin: bool, } impl SerialParameters { /// Helper function to create a serial device from the defined parameters. /// /// # Arguments /// * `evt_fd` - eventfd used for interrupt events /// * `keep_fds` - Vector of FDs required by this device if it were sandboxed in a child /// process. `evt_fd` will always be added to this vector by this function. pub fn create_serial_device( &self, evt_fd: &EventFd, keep_fds: &mut Vec, ) -> std::result::Result { let evt_fd = evt_fd.try_clone().map_err(Error::CloneEventFd)?; keep_fds.push(evt_fd.as_raw_fd()); let input: Option> = if let Some(input_path) = &self.input { let input_file = File::open(input_path.as_path()).map_err(Error::FileError)?; keep_fds.push(input_file.as_raw_fd()); Some(Box::new(input_file)) } else if self.stdin { keep_fds.push(stdin().as_raw_fd()); // This wrapper is used in place of the libstd native version because we don't want // buffering for stdin. struct StdinWrapper; impl io::Read for StdinWrapper { fn read(&mut self, out: &mut [u8]) -> io::Result { read_raw_stdin(out).map_err(|e| e.into()) } } Some(Box::new(StdinWrapper)) } else { None }; match self.type_ { SerialType::Stdout => { keep_fds.push(stdout().as_raw_fd()); Ok(Serial::new(evt_fd, input, Some(Box::new(stdout())))) } SerialType::Sink => Ok(Serial::new(evt_fd, input, None)), SerialType::Syslog => { syslog::push_fds(keep_fds); Ok(Serial::new( evt_fd, input, Some(Box::new(syslog::Syslogger::new( syslog::Priority::Info, syslog::Facility::Daemon, ))), )) } SerialType::File => match &self.path { Some(path) => { let file = File::create(path.as_path()).map_err(Error::FileError)?; keep_fds.push(file.as_raw_fd()); Ok(Serial::new(evt_fd, input, Some(Box::new(file)))) } _ => Err(Error::PathRequired), }, SerialType::UnixSocket => Err(Error::Unimplemented(SerialType::UnixSocket)), } } } // Structure for holding the default configuration of the serial devices. const DEFAULT_SERIAL_PARAMS: [SerialParameters; 4] = [ SerialParameters { type_: SerialType::Stdout, path: None, input: None, num: 1, console: true, stdin: true, }, SerialParameters { type_: SerialType::Sink, path: None, input: None, num: 2, console: false, stdin: false, }, SerialParameters { type_: SerialType::Sink, path: None, input: None, num: 3, console: false, stdin: false, }, SerialParameters { type_: SerialType::Sink, path: None, input: None, num: 4, console: false, stdin: false, }, ]; /// Address for Serial ports in x86 pub const SERIAL_ADDR: [u64; 4] = [0x3f8, 0x2f8, 0x3e8, 0x2e8]; /// String representations of serial devices const SERIAL_TTY_STRINGS: [&str; 4] = ["ttyS0", "ttyS1", "ttyS2", "ttyS3"]; /// Helper function to get the tty string of a serial device based on the port number. Will default /// to ttyS0 if an invalid number is given. pub fn get_serial_tty_string(stdio_serial_num: u8) -> String { stdio_serial_num .checked_sub(1) .and_then(|i| SERIAL_TTY_STRINGS.get(i as usize)) .unwrap_or(&SERIAL_TTY_STRINGS[0]) .to_string() } /// Adds serial devices to the provided bus based on the serial parameters given. Returns the serial /// port number and serial device to be used for stdout if defined. /// /// # Arguments /// /// * `io_bus` - Bus to add the devices to /// * `com_evt_1_3` - eventfd for com1 and com3 /// * `com_evt_1_4` - eventfd for com2 and com4 /// * `io_bus` - Bus to add the devices to /// * `serial_parameters` - definitions of serial parameter configuationis. If a setting is not /// provided for a port, then it will use the default configuation. pub fn add_serial_devices( io_bus: &mut Bus, com_evt_1_3: &EventFd, com_evt_2_4: &EventFd, serial_parameters: &BTreeMap, serial_jail: Option, ) -> Result, DeviceRegistrationError> { let mut stdio_serial_num = None; for x in 0..=3 { let com_evt = match x { 0 => com_evt_1_3, 1 => com_evt_2_4, 2 => com_evt_1_3, 3 => com_evt_2_4, _ => com_evt_1_3, }; let param = serial_parameters .get(&(x + 1)) .unwrap_or(&DEFAULT_SERIAL_PARAMS[x as usize]); if param.console { stdio_serial_num = Some(x + 1); } let mut preserved_fds = Vec::new(); let com = param .create_serial_device(&com_evt, &mut preserved_fds) .map_err(DeviceRegistrationError::CreateSerialDevice)?; match serial_jail.as_ref() { Some(jail) => { let com = Arc::new(Mutex::new( ProxyDevice::new(com, &jail, preserved_fds) .map_err(DeviceRegistrationError::ProxyDeviceCreation)?, )); io_bus .insert(com.clone(), SERIAL_ADDR[x as usize], 0x8, false) .unwrap(); } None => { let com = Arc::new(Mutex::new(com)); io_bus .insert(com.clone(), SERIAL_ADDR[x as usize], 0x8, false) .unwrap(); } } } Ok(stdio_serial_num) }