From 3a29174a7b6a9b0ae07339e2afa3674b9ee5fb33 Mon Sep 17 00:00:00 2001 From: Vikram Auradkar Date: Wed, 16 Mar 2022 02:33:27 +0000 Subject: [PATCH] Upstream windows serial device Bug: b:213149155 Test: cargo test and presubmit Upstream-Crate: devices Change-Id: I1420fee814271dae9502878963872449dae8218e Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/3527444 Reviewed-by: Daniel Verkamp Tested-by: kokoro Commit-Queue: Vikram Auradkar --- arch/src/serial.rs | 6 + devices/Cargo.toml | 1 + devices/src/lib.rs | 1 + devices/src/serial.rs | 8 +- devices/src/serial_device.rs | 264 +++++------------- devices/src/sys.rs | 13 + devices/src/sys/unix.rs | 5 + devices/src/sys/unix/serial_device.rs | 202 ++++++++++++++ devices/src/sys/windows.rs | 5 + devices/src/sys/windows/serial_device.rs | 85 ++++++ devices/src/virtio/console.rs | 4 +- .../src/virtio/vhost/user/device/console.rs | 5 +- src/linux/device_helpers.rs | 2 +- 13 files changed, 398 insertions(+), 203 deletions(-) create mode 100644 devices/src/sys.rs create mode 100644 devices/src/sys/unix.rs create mode 100644 devices/src/sys/unix/serial_device.rs create mode 100644 devices/src/sys/windows.rs create mode 100644 devices/src/sys/windows/serial_device.rs diff --git a/arch/src/serial.rs b/arch/src/serial.rs index 683b51121d..52745dcbe7 100644 --- a/arch/src/serial.rs +++ b/arch/src/serial.rs @@ -42,6 +42,7 @@ pub fn set_default_serial_parameters( console: true, earlycon: false, stdin: true, + out_timestamp: false, }); } @@ -59,6 +60,7 @@ pub fn set_default_serial_parameters( console: false, earlycon: false, stdin: false, + out_timestamp: false, }); } } @@ -230,6 +232,7 @@ mod tests { console: true, earlycon: false, stdin: true, + out_timestamp: false, }, ); @@ -258,6 +261,7 @@ mod tests { console: true, earlycon: false, stdin: true, + out_timestamp: false, }, ); @@ -273,6 +277,7 @@ mod tests { console: false, earlycon: true, stdin: false, + out_timestamp: false, }, ); @@ -302,6 +307,7 @@ mod tests { console: false, earlycon: true, stdin: true, + out_timestamp: false, }, ); diff --git a/devices/Cargo.toml b/devices/Cargo.toml index 7c8c456d74..ebf9736034 100644 --- a/devices/Cargo.toml +++ b/devices/Cargo.toml @@ -28,6 +28,7 @@ audio_streams = "*" balloon_control = { path = "../common/balloon_control" } base = { path = "../base" } bit_field = { path = "../bit_field" } +cfg-if = "1.0.0" cros_async = { path = "../cros_async" } data_model = { path = "../common/data_model" } dbus = { version = "0.9", optional = true } diff --git a/devices/src/lib.rs b/devices/src/lib.rs index c1ec7f73fc..5d4888f302 100644 --- a/devices/src/lib.rs +++ b/devices/src/lib.rs @@ -25,6 +25,7 @@ pub mod acpi; pub mod bat; mod serial; pub mod serial_device; +mod sys; #[cfg(feature = "usb")] pub mod usb; #[cfg(feature = "usb")] diff --git a/devices/src/serial.rs b/devices/src/serial.rs index fc2e58b962..92ca66683d 100644 --- a/devices/src/serial.rs +++ b/devices/src/serial.rs @@ -9,7 +9,7 @@ use std::sync::mpsc::{channel, Receiver, TryRecvError}; use std::sync::Arc; use std::thread::{self}; -use base::{error, Event, RawDescriptor, Result}; +use base::{error, Event, FileSync, RawDescriptor, Result}; use hypervisor::ProtectionType; use crate::bus::BusAccessInfo; @@ -89,6 +89,8 @@ impl SerialDevice for Serial { interrupt_evt: Event, input: Option>, out: Option>, + _sync: Option>, + _out_timestamp: bool, _keep_rds: Vec, ) -> Serial { Serial { @@ -424,6 +426,8 @@ mod tests { intr_evt, None, Some(Box::new(serial_out.clone())), + None, + false, Vec::new(), ); @@ -443,6 +447,8 @@ mod tests { intr_evt.try_clone().unwrap(), None, Some(Box::new(serial_out)), + None, + false, Vec::new(), ); diff --git a/devices/src/serial_device.rs b/devices/src/serial_device.rs index 8e5c6419ab..4b64e9f770 100644 --- a/devices/src/serial_device.rs +++ b/devices/src/serial_device.rs @@ -2,24 +2,20 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -use std::borrow::Cow; use std::fmt::{self, Display}; use std::fs::{File, OpenOptions}; -use std::io::{self, stdin, stdout, ErrorKind}; -use std::os::unix::net::UnixDatagram; -use std::path::{Path, PathBuf}; -use std::thread; -use std::time::Duration; +use std::io::{self, stdin, stdout}; +use std::path::PathBuf; -use base::{ - error, info, read_raw_stdin, safe_descriptor_from_path, syslog, AsRawDescriptor, Event, - RawDescriptor, -}; +use base::{error, syslog, AsRawDescriptor, Event, FileSync, RawDescriptor}; use hypervisor::ProtectionType; use remain::sorted; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use thiserror::Error as ThisError; +pub use crate::sys::serial_device::SerialDevice; +use crate::sys::serial_device::*; + #[sorted] #[derive(ThisError, Debug)] pub enum Error { @@ -37,32 +33,23 @@ pub enum Error { PathRequired, #[error("Failed to create unbound socket")] SocketCreateFailed, + #[error("Unable to open system type serial: {0}")] + SystemTypeError(std::io::Error), #[error("Serial device type {0} not implemented")] Unimplemented(SerialType), } -/// Abstraction over serial-like devices that can be created given an event and optional input and -/// output streams. -pub trait SerialDevice { - fn new( - protected_vm: ProtectionType, - interrupt_evt: Event, - input: Option>, - output: Option>, - keep_rds: Vec, - ) -> Self; -} - /// Enum for possible type of serial devices -#[derive(Clone, Debug, PartialEq, Deserialize)] +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "kebab-case")] pub enum SerialType { File, Stdout, Sink, Syslog, - #[serde(rename = "unix")] - UnixSocket, + #[cfg_attr(unix, serde(rename = "unix"))] + #[cfg_attr(windows, serde(rename = "namedpipe"))] + SystemSerialType, } impl Default for SerialType { @@ -78,7 +65,7 @@ impl Display for SerialType { SerialType::Stdout => "Stdout".to_string(), SerialType::Sink => "Sink".to_string(), SerialType::Syslog => "Syslog".to_string(), - SerialType::UnixSocket => "UnixSocket".to_string(), + SerialType::SystemSerialType => SYSTEM_SERIAL_TYPE_NAME.to_string(), }; write!(f, "{}", s) @@ -86,7 +73,7 @@ impl Display for SerialType { } /// Serial device hardware types -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub enum SerialHardware { Serial, // Standard PC-style (8250/16550 compatible) UART @@ -110,73 +97,6 @@ impl Display for SerialHardware { } } -struct WriteSocket { - sock: UnixDatagram, - buf: String, -} - -const BUF_CAPACITY: usize = 1024; - -impl WriteSocket { - pub fn new(s: UnixDatagram) -> WriteSocket { - WriteSocket { - sock: s, - buf: String::with_capacity(BUF_CAPACITY), - } - } - - pub fn send_buf(&self, buf: &[u8]) -> io::Result { - const SEND_RETRY: usize = 2; - let mut sent = 0; - for _ in 0..SEND_RETRY { - match self.sock.send(buf) { - Ok(bytes_sent) => { - sent = bytes_sent; - break; - } - Err(e) => info!("Send error: {:?}", e), - } - } - Ok(sent) - } -} - -impl io::Write for WriteSocket { - fn write(&mut self, buf: &[u8]) -> io::Result { - let parsed_str = String::from_utf8_lossy(buf); - - let last_newline_idx = match parsed_str.rfind('\n') { - Some(newline_idx) => Some(self.buf.len() + newline_idx), - None => None, - }; - self.buf.push_str(&parsed_str); - - match last_newline_idx { - Some(last_newline_idx) => { - for line in (self.buf[..last_newline_idx]).lines() { - if self.send_buf(line.as_bytes()).is_err() { - break; - } - } - self.buf.drain(..=last_newline_idx); - } - None => { - if self.buf.len() >= BUF_CAPACITY { - if let Err(e) = self.send_buf(self.buf.as_bytes()) { - info!("Couldn't send full buffer. {:?}", e); - } - self.buf.clear(); - } - } - } - Ok(buf.len()) - } - - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - fn serial_parameters_default_num() -> u8 { 1 } @@ -194,20 +114,16 @@ pub struct SerialParameters { pub console: bool, pub earlycon: bool, pub stdin: bool, + pub out_timestamp: bool, } -// The maximum length of a path that can be used as the address of a -// unix socket. Note that this includes the null-terminator. -const MAX_SOCKET_PATH_LENGTH: usize = 108; - impl SerialParameters { /// Helper function to create a serial device from the defined parameters. /// /// # Arguments /// * `evt` - event used for interrupt events - /// * `keep_rds` - Vector of descriptors required by this device if it were sandboxed - /// in a child process. `evt` will always be added to this vector by - /// this function. + /// * `keep_rds` - Vector of FDs required by this device if it were sandboxed in a child + /// process. `evt` will always be added to this vector by this function. pub fn create_serial_device( &self, protected_vm: ProtectionType, @@ -218,130 +134,74 @@ impl SerialParameters { keep_rds.push(evt.as_raw_descriptor()); let input: Option> = if let Some(input_path) = &self.input { let input_path = input_path.as_path(); - let input_file = if let Some(fd) = - safe_descriptor_from_path(input_path).map_err(|e| Error::FileError(e.into()))? - { - fd.into() + + let input_file = if let Some(file) = file_from_path(input_path)? { + file } else { File::open(input_path).map_err(Error::FileError)? }; + keep_rds.push(input_file.as_raw_descriptor()); Some(Box::new(input_file)) } else if self.stdin { keep_rds.push(stdin().as_raw_descriptor()); - // 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)) + Some(Box::new(ConsoleInput)) } else { None }; - let output: Option> = match self.type_ { + let (output, sync): ( + Option>, + Option>, + ) = match self.type_ { SerialType::Stdout => { keep_rds.push(stdout().as_raw_descriptor()); - Some(Box::new(stdout())) + (Some(Box::new(stdout())), None) } - SerialType::Sink => None, + SerialType::Sink => (None, None), SerialType::Syslog => { syslog::push_descriptors(keep_rds); - Some(Box::new(syslog::Syslogger::new( - syslog::Priority::Info, - syslog::Facility::Daemon, - ))) + ( + Some(Box::new(syslog::Syslogger::new( + syslog::Priority::Info, + syslog::Facility::Daemon, + ))), + None, + ) } SerialType::File => match &self.path { Some(path) => { - let path = path.as_path(); - let file = if let Some(fd) = - safe_descriptor_from_path(path).map_err(|e| Error::FileError(e.into()))? - { - fd.into() + let file = if let Some(file) = file_from_path(path.as_path())? { + file } else { OpenOptions::new() .append(true) .create(true) - .open(path) + .open(path.as_path()) .map_err(Error::FileError)? }; + + let sync = file.try_clone().map_err(Error::FileError)?; + keep_rds.push(file.as_raw_descriptor()); - Some(Box::new(file)) + keep_rds.push(sync.as_raw_descriptor()); + + (Some(Box::new(file)), Some(Box::new(sync))) } None => return Err(Error::PathRequired), }, - SerialType::UnixSocket => { - match &self.path { - Some(path) => { - // If the path is longer than 107 characters, - // then we won't be able to connect directly - // to it. Instead we can shorten the path by - // opening the containing directory and using - // /proc/self/fd/*/ to access it via a shorter - // path. - let mut path_cow = Cow::::Borrowed(path); - let mut _dir_fd = None; - if path.as_os_str().len() >= MAX_SOCKET_PATH_LENGTH { - let mut short_path = PathBuf::with_capacity(MAX_SOCKET_PATH_LENGTH); - short_path.push("/proc/self/fd/"); - - // We don't actually want to open this - // directory for reading, but the stdlib - // requires all files be opened as at - // least one of readable, writeable, or - // appeandable. - let dir = OpenOptions::new() - .read(true) - .open(path.parent().ok_or(Error::InvalidPath)?) - .map_err(Error::FileError)?; - - short_path.push(dir.as_raw_descriptor().to_string()); - short_path.push(path.file_name().ok_or(Error::InvalidPath)?); - path_cow = Cow::Owned(short_path); - _dir_fd = Some(dir); - } - - // The shortened path may still be too long, - // in which case we must give up here. - if path_cow.as_os_str().len() >= MAX_SOCKET_PATH_LENGTH { - return Err(Error::InvalidPath); - } - - // There's a race condition between - // vmlog_forwarder making the logging socket and - // crosvm starting up, so we loop here until it's - // available. - let sock = UnixDatagram::unbound().map_err(Error::FileError)?; - loop { - match sock.connect(&path_cow) { - Ok(_) => break, - Err(e) => { - match e.kind() { - ErrorKind::NotFound | ErrorKind::ConnectionRefused => { - // logging socket doesn't - // exist yet, sleep for 10 ms - // and try again. - thread::sleep(Duration::from_millis(10)) - } - _ => { - error!("Unexpected error connecting to logging socket: {:?}", e); - return Err(Error::FileError(e)); - } - } - } - }; - } - keep_rds.push(sock.as_raw_descriptor()); - Some(Box::new(WriteSocket::new(sock))) - } - None => return Err(Error::PathRequired), - } + SerialType::SystemSerialType => { + return create_system_type_serial_device(self, protected_vm, evt, input, keep_rds); } }; - Ok(T::new(protected_vm, evt, input, output, keep_rds.to_vec())) + Ok(T::new( + protected_vm, + evt, + input, + output, + sync, + self.out_timestamp, + keep_rds.to_vec(), + )) } } @@ -369,6 +229,7 @@ mod tests { console: false, earlycon: false, stdin: false, + out_timestamp: false, } ); @@ -381,8 +242,12 @@ mod tests { assert_eq!(params.type_, SerialType::Sink); let params = from_serial_arg("type=syslog").unwrap(); assert_eq!(params.type_, SerialType::Syslog); - let params = from_serial_arg("type=unix").unwrap(); - assert_eq!(params.type_, SerialType::UnixSocket); + #[cfg(unix)] + let opt = "type=unix"; + #[cfg(window)] + let opt = "type=namedpipe"; + let params = from_serial_arg(opt).unwrap(); + assert_eq!(params.type_, SerialType::SystemSerialType); let params = from_serial_arg("type=foobar"); assert!(params.is_err()); @@ -437,7 +302,7 @@ mod tests { assert!(params.is_err()); // all together - let params = from_serial_arg("type=stdout,path=/some/path,hardware=virtio-console,num=5,earlycon,console,stdin,input=/some/input").unwrap(); + let params = from_serial_arg("type=stdout,path=/some/path,hardware=virtio-console,num=5,earlycon,console,stdin,input=/some/input,out_timestamp").unwrap(); assert_eq!( params, SerialParameters { @@ -449,6 +314,7 @@ mod tests { console: true, earlycon: true, stdin: true, + out_timestamp: true, } ); diff --git a/devices/src/sys.rs b/devices/src/sys.rs new file mode 100644 index 0000000000..f21fffb1f0 --- /dev/null +++ b/devices/src/sys.rs @@ -0,0 +1,13 @@ +// Copyright 2022 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. + +cfg_if::cfg_if! { + if #[cfg(unix)] { + mod unix; + pub(crate) use unix::*; + } else if #[cfg(windows)] { + mod windows; + pub(crate) use windows::*; + } +} diff --git a/devices/src/sys/unix.rs b/devices/src/sys/unix.rs new file mode 100644 index 0000000000..b589781660 --- /dev/null +++ b/devices/src/sys/unix.rs @@ -0,0 +1,5 @@ +// Copyright 2022 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. + +pub(crate) mod serial_device; diff --git a/devices/src/sys/unix/serial_device.rs b/devices/src/sys/unix/serial_device.rs new file mode 100644 index 0000000000..8c1eda4116 --- /dev/null +++ b/devices/src/sys/unix/serial_device.rs @@ -0,0 +1,202 @@ +// Copyright 2022 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 crate::serial_device::{Error, SerialParameters}; +use base::{error, AsRawDescriptor, Event, FileSync, RawDescriptor}; +use base::{info, read_raw_stdin, safe_descriptor_from_path}; +use hypervisor::ProtectionType; +use std::borrow::Cow; +use std::fs::{File, OpenOptions}; +use std::io; +use std::io::{ErrorKind, Write}; +use std::os::unix::net::UnixDatagram; +use std::path::{Path, PathBuf}; +use std::thread; +use std::time::Duration; + +pub const SYSTEM_SERIAL_TYPE_NAME: &str = "UnixSocket"; + +// This wrapper is used in place of the libstd native version because we don't want +// buffering for stdin. +pub struct ConsoleInput; +impl io::Read for ConsoleInput { + fn read(&mut self, out: &mut [u8]) -> io::Result { + read_raw_stdin(out).map_err(|e| e.into()) + } +} + +/// Abstraction over serial-like devices that can be created given an event and optional input and +/// output streams. +pub trait SerialDevice { + fn new( + protected_vm: ProtectionType, + interrupt_evt: Event, + input: Option>, + output: Option>, + sync: Option>, + out_timestamp: bool, + keep_rds: Vec, + ) -> Self; +} + +// The maximum length of a path that can be used as the address of a +// unix socket. Note that this includes the null-terminator. +pub const MAX_SOCKET_PATH_LENGTH: usize = 108; + +struct WriteSocket { + sock: UnixDatagram, + buf: String, +} + +const BUF_CAPACITY: usize = 1024; + +impl WriteSocket { + pub fn new(s: UnixDatagram) -> WriteSocket { + WriteSocket { + sock: s, + buf: String::with_capacity(BUF_CAPACITY), + } + } + + pub fn send_buf(&self, buf: &[u8]) -> io::Result { + const SEND_RETRY: usize = 2; + let mut sent = 0; + for _ in 0..SEND_RETRY { + match self.sock.send(buf) { + Ok(bytes_sent) => { + sent = bytes_sent; + break; + } + Err(e) => info!("Send error: {:?}", e), + } + } + Ok(sent) + } +} + +impl io::Write for WriteSocket { + fn write(&mut self, buf: &[u8]) -> io::Result { + let parsed_str = String::from_utf8_lossy(buf); + + let last_newline_idx = match parsed_str.rfind('\n') { + Some(newline_idx) => Some(self.buf.len() + newline_idx), + None => None, + }; + self.buf.push_str(&parsed_str); + + match last_newline_idx { + Some(last_newline_idx) => { + for line in (self.buf[..last_newline_idx]).lines() { + if self.send_buf(line.as_bytes()).is_err() { + break; + } + } + self.buf.drain(..=last_newline_idx); + } + None => { + if self.buf.len() >= BUF_CAPACITY { + if let Err(e) = self.send_buf(self.buf.as_bytes()) { + info!("Couldn't send full buffer. {:?}", e); + } + self.buf.clear(); + } + } + } + Ok(buf.len()) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +pub(crate) fn create_system_type_serial_device( + param: &SerialParameters, + protected_vm: ProtectionType, + evt: Event, + input: Option>, + keep_rds: &mut Vec, +) -> std::result::Result { + match ¶m.path { + Some(path) => { + // If the path is longer than 107 characters, + // then we won't be able to connect directly + // to it. Instead we can shorten the path by + // opening the containing directory and using + // /proc/self/fd/*/ to access it via a shorter + // path. + let mut path_cow = Cow::::Borrowed(path); + let mut _dir_fd = None; + if path.as_os_str().len() >= MAX_SOCKET_PATH_LENGTH { + let mut short_path = PathBuf::with_capacity(MAX_SOCKET_PATH_LENGTH); + short_path.push("/proc/self/fd/"); + + // We don't actually want to open this + // directory for reading, but the stdlib + // requires all files be opened as at + // least one of readable, writeable, or + // appeandable. + let dir = OpenOptions::new() + .read(true) + .open(path.parent().ok_or(Error::InvalidPath)?) + .map_err(Error::FileError)?; + + short_path.push(dir.as_raw_descriptor().to_string()); + short_path.push(path.file_name().ok_or(Error::InvalidPath)?); + path_cow = Cow::Owned(short_path); + _dir_fd = Some(dir); + } + + // The shortened path may still be too long, + // in which case we must give up here. + if path_cow.as_os_str().len() >= MAX_SOCKET_PATH_LENGTH { + return Err(Error::InvalidPath); + } + + // There's a race condition between + // vmlog_forwarder making the logging socket and + // crosvm starting up, so we loop here until it's + // available. + let sock = UnixDatagram::unbound().map_err(Error::FileError)?; + loop { + match sock.connect(&path_cow) { + Ok(_) => break, + Err(e) => { + match e.kind() { + ErrorKind::NotFound | ErrorKind::ConnectionRefused => { + // logging socket doesn't + // exist yet, sleep for 10 ms + // and try again. + thread::sleep(Duration::from_millis(10)) + } + _ => { + error!("Unexpected error connecting to logging socket: {:?}", e); + return Err(Error::FileError(e)); + } + } + } + }; + } + keep_rds.push(sock.as_raw_descriptor()); + let output: Option> = Some(Box::new(WriteSocket::new(sock))); + return Ok(T::new( + protected_vm, + evt, + input, + output, + None, + false, + keep_rds.to_vec(), + )); + } + None => return Err(Error::PathRequired), + } +} + +pub(crate) fn file_from_path(path: &Path) -> Result, Error> { + if let Some(fd) = safe_descriptor_from_path(path).map_err(|e| Error::FileError(e.into()))? { + return Ok(Some(fd.into())); + } + Ok(None) +} diff --git a/devices/src/sys/windows.rs b/devices/src/sys/windows.rs new file mode 100644 index 0000000000..b589781660 --- /dev/null +++ b/devices/src/sys/windows.rs @@ -0,0 +1,5 @@ +// Copyright 2022 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. + +pub(crate) mod serial_device; diff --git a/devices/src/sys/windows/serial_device.rs b/devices/src/sys/windows/serial_device.rs new file mode 100644 index 0000000000..a9f0cb55a7 --- /dev/null +++ b/devices/src/sys/windows/serial_device.rs @@ -0,0 +1,85 @@ +// Copyright 2022 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 crate::serial_device::{Error, SerialParameters}; +use base::named_pipes; +use base::named_pipes::{BlockingMode, FramingMode}; +use base::FileSync; +use base::{AsRawDescriptor, Event, RawDescriptor}; +use hypervisor::ProtectionType; +use std::fs::File; +use std::io::{self}; +use std::path::Path; + +pub use base::Console as ConsoleInput; + +pub const SYSTEM_SERIAL_TYPE_NAME: &str = "NamedPipe"; + +/// Abstraction over serial-like devices that can be created given an event and optional input and +/// output streams. +pub trait SerialDevice { + fn new( + protected_vm: ProtectionType, + interrupt_evt: Event, + input: Option>, + output: Option>, + sync: Option>, + out_timestamp: bool, + keep_rds: Vec, + ) -> Self; + fn new_pipe( + protected_vm: ProtectionType, + interrupt_evt: Event, + pipe_in: named_pipes::PipeConnection, + pipe_out: named_pipes::PipeConnection, + keep_rds: Vec, + ) -> Self; +} + +pub(crate) fn create_system_type_serial_device( + param: &SerialParameters, + protected_vm: ProtectionType, + evt: Event, + _input: Option>, + keep_rds: &mut Vec, +) -> std::result::Result { + match ¶m.path { + None => return Err(Error::PathRequired), + Some(path) => { + // We must create this pipe in non-blocking mode because a blocking + // read in one thread will block a write in another thread having a + // handle to the same end of the pipe, which will hang the + // emulator. This does mean that the event loop writing to the + // pipe's output will need to swallow errors caused by writing to + // the pipe when it's not ready; but in practice this does not seem + // to cause a problem. + let pipe_in = named_pipes::create_server_pipe( + path.to_str().unwrap(), + &FramingMode::Byte, + &BlockingMode::NoWait, + 0, // default timeout + named_pipes::DEFAULT_BUFFER_SIZE, + false, + ) + .map_err(Error::SystemTypeError)?; + + let pipe_out = pipe_in.try_clone().map_err(Error::SystemTypeError)?; + + keep_rds.push(pipe_in.as_raw_descriptor()); + keep_rds.push(pipe_out.as_raw_descriptor()); + + return Ok(T::new_pipe( + protected_vm, + evt, + pipe_in, + pipe_out, + keep_rds.to_vec(), + )); + } + } +} + +pub(crate) fn file_from_path(_path: &Path) -> Result, Error> { + Ok(None) +} diff --git a/devices/src/virtio/console.rs b/devices/src/virtio/console.rs index cb74fb4ea1..7fa55db6e4 100644 --- a/devices/src/virtio/console.rs +++ b/devices/src/virtio/console.rs @@ -9,7 +9,7 @@ use std::result; use std::sync::Arc; use std::thread; -use base::{error, Event, PollToken, RawDescriptor, WaitContext}; +use base::{error, Event, FileSync, PollToken, RawDescriptor, WaitContext}; use data_model::{DataInit, Le16, Le32}; use hypervisor::ProtectionType; use remain::sorted; @@ -376,6 +376,8 @@ impl SerialDevice for Console { _evt: Event, input: Option>, output: Option>, + _sync: Option>, + _out_timestamp: bool, keep_rds: Vec, ) -> Console { Console { diff --git a/devices/src/virtio/vhost/user/device/console.rs b/devices/src/virtio/vhost/user/device/console.rs index b7b1709d80..2425eeb9ce 100644 --- a/devices/src/virtio/vhost/user/device/console.rs +++ b/devices/src/virtio/vhost/user/device/console.rs @@ -9,7 +9,7 @@ use std::path::PathBuf; use std::sync::Arc; use anyhow::{anyhow, bail, Context}; -use base::{error, warn, Event, RawDescriptor, Terminal}; +use base::{error, warn, Event, FileSync, RawDescriptor, Terminal}; use cros_async::{EventAsync, Executor}; use data_model::DataInit; @@ -83,6 +83,8 @@ impl SerialDevice for ConsoleDevice { _evt: Event, input: Option>, output: Option>, + _sync: Option>, + _out_timestamp: bool, _keep_rds: Vec, ) -> ConsoleDevice { let avail_features = 1u64 << crate::virtio::VIRTIO_F_VERSION_1 @@ -308,6 +310,7 @@ pub fn run_console_device(program_name: &str, args: &[&str]) -> anyhow::Result<( earlycon: false, // We do not support stdin-less mode stdin: true, + out_timestamp: false, }; let console = match params.create_serial_device::( diff --git a/src/linux/device_helpers.rs b/src/linux/device_helpers.rs index f1361ed184..ca356b2734 100644 --- a/src/linux/device_helpers.rs +++ b/src/linux/device_helpers.rs @@ -1021,7 +1021,7 @@ pub fn create_iommu_device( fn add_bind_mounts(param: &SerialParameters, jail: &mut Minijail) -> Result<(), minijail::Error> { if let Some(path) = ¶m.path { - if let SerialType::UnixSocket = param.type_ { + if let SerialType::SystemSerialType = param.type_ { if let Some(parent) = path.as_path().parent() { if parent.exists() { info!("Bind mounting dir {}", parent.display());