cloud-hypervisor/vmm/src/serial_manager.rs
William Douglas a8f063db7c vmm: Refactor serial buffer to allow flush on PTY when writable
Refactor the serial buffer handling in order to write the serial
buffer's output to a PTY connected after the serial device stops being
written to by the guest.

This change moves the serial buffer initialization inside the serial
manager. That is done to allow the serial buffer to be made aware of
the PTY and epoll fds needed in order to modify the
EpollDispatch::File trigger. These are then used by the serial buffer
to trigger an epoll event when the PTY fd is writable and the buffer
has content in it. They are also used to remove the trigger when the
buffer is emptied in order to avoid unnecessary wake-ups.

Signed-off-by: William Douglas <william.douglas@intel.com>
2021-09-27 14:18:21 +01:00

271 lines
9.8 KiB
Rust

// Copyright © 2021 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
use crate::config::ConsoleOutputMode;
use crate::device_manager::PtyPair;
use crate::serial_buffer::SerialBuffer;
#[cfg(target_arch = "aarch64")]
use devices::legacy::Pl011;
#[cfg(target_arch = "x86_64")]
use devices::legacy::Serial;
use libc::EFD_NONBLOCK;
use std::fs::File;
use std::io::Read;
use std::os::unix::io::{AsRawFd, FromRawFd};
use std::panic::AssertUnwindSafe;
use std::sync::{Arc, Mutex};
use std::{io, result, thread};
use thiserror::Error;
use vmm_sys_util::eventfd::EventFd;
#[derive(Debug, Error)]
pub enum Error {
/// Cannot clone File.
#[error("Error cloning File: {0}")]
FileClone(#[source] io::Error),
/// Cannot create epoll context.
#[error("Error creating epoll context: {0}")]
Epoll(#[source] io::Error),
/// Cannot handle the VM input stream.
#[error("Error handling VM input: {0:?}")]
ReadInput(#[source] io::Error),
/// Cannot queue input to the serial device.
#[error("Error queuing input to the serial device: {0}")]
QueueInput(#[source] vmm_sys_util::errno::Error),
/// Cannot flush output on the serial buffer.
#[error("Error flushing serial device's output buffer: {0}")]
FlushOutput(#[source] io::Error),
/// Cannot make the file descriptor non-blocking.
#[error("Error making input file descriptor non-blocking: {0}")]
SetNonBlocking(#[source] io::Error),
/// Cannot create EventFd.
#[error("Error creating EventFd: {0}")]
EventFd(#[source] io::Error),
/// Cannot spawn SerialManager thread.
#[error("Error spawning SerialManager thread: {0}")]
SpawnSerialManager(#[source] io::Error),
}
pub type Result<T> = result::Result<T, Error>;
#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(u64)]
pub enum EpollDispatch {
File = 0,
Kill = 1,
Unknown,
}
impl From<u64> for EpollDispatch {
fn from(v: u64) -> Self {
use EpollDispatch::*;
match v {
0 => File,
1 => Kill,
_ => Unknown,
}
}
}
pub struct SerialManager {
#[cfg(target_arch = "x86_64")]
serial: Arc<Mutex<Serial>>,
#[cfg(target_arch = "aarch64")]
serial: Arc<Mutex<Pl011>>,
epoll_file: File,
in_file: File,
kill_evt: EventFd,
handle: Option<thread::JoinHandle<()>>,
}
impl SerialManager {
pub fn new(
#[cfg(target_arch = "x86_64")] serial: Arc<Mutex<Serial>>,
#[cfg(target_arch = "aarch64")] serial: Arc<Mutex<Pl011>>,
pty_pair: Option<Arc<Mutex<PtyPair>>>,
mode: ConsoleOutputMode,
) -> Result<Option<Self>> {
let in_file = match mode {
ConsoleOutputMode::Pty => {
if let Some(pty_pair) = pty_pair {
pty_pair
.lock()
.unwrap()
.main
.try_clone()
.map_err(Error::FileClone)?
} else {
return Ok(None);
}
}
ConsoleOutputMode::Tty => {
// If running on an interactive TTY then accept input
if unsafe { libc::isatty(libc::STDIN_FILENO) == 1 } {
let stdin_clone = unsafe { File::from_raw_fd(libc::dup(libc::STDIN_FILENO)) };
let ret = unsafe {
let mut flags = libc::fcntl(stdin_clone.as_raw_fd(), libc::F_GETFL);
flags |= libc::O_NONBLOCK;
libc::fcntl(stdin_clone.as_raw_fd(), libc::F_SETFL, flags)
};
if ret < 0 {
return Err(Error::SetNonBlocking(std::io::Error::last_os_error()));
}
stdin_clone
} else {
return Ok(None);
}
}
_ => return Ok(None),
};
let epoll_fd = epoll::create(true).map_err(Error::Epoll)?;
let kill_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::EventFd)?;
epoll::ctl(
epoll_fd,
epoll::ControlOptions::EPOLL_CTL_ADD,
kill_evt.as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, EpollDispatch::Kill as u64),
)
.map_err(Error::Epoll)?;
epoll::ctl(
epoll_fd,
epoll::ControlOptions::EPOLL_CTL_ADD,
in_file.as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, EpollDispatch::File as u64),
)
.map_err(Error::Epoll)?;
if mode == ConsoleOutputMode::Pty {
let writer = in_file.try_clone().map_err(Error::FileClone)?;
let mut buffer = SerialBuffer::new(Box::new(writer));
buffer.add_out_fd(in_file.as_raw_fd());
buffer.add_epoll_fd(epoll_fd);
serial.as_ref().lock().unwrap().set_out(Box::new(buffer));
}
// Use 'File' to enforce closing on 'epoll_fd'
let epoll_file = unsafe { File::from_raw_fd(epoll_fd) };
Ok(Some(SerialManager {
serial,
epoll_file,
in_file,
kill_evt,
handle: None,
}))
}
pub fn start_thread(&mut self, exit_evt: EventFd) -> Result<()> {
// Don't allow this to be run if the handle exists
if self.handle.is_some() {
warn!("Tried to start multiple SerialManager threads, ignoring");
return Ok(());
}
let epoll_fd = self.epoll_file.as_raw_fd();
let mut in_file = self.in_file.try_clone().map_err(Error::FileClone)?;
let serial = self.serial.clone();
let thread = thread::Builder::new()
.name("serial-manager".to_string())
.spawn(move || {
std::panic::catch_unwind(AssertUnwindSafe(move || {
// 3 for File, Kill, and Unknown
const EPOLL_EVENTS_LEN: usize = 3;
let mut events =
vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
loop {
let num_events = match epoll::wait(epoll_fd, -1, &mut events[..]) {
Ok(res) => res,
Err(e) => {
if e.kind() == io::ErrorKind::Interrupted {
// It's well defined from the epoll_wait() syscall
// documentation that the epoll loop can be interrupted
// before any of the requested events occurred or the
// timeout expired. In both those cases, epoll_wait()
// returns an error of type EINTR, but this should not
// be considered as a regular error. Instead it is more
// appropriate to retry, by calling into epoll_wait().
continue;
}
return Err(Error::Epoll(e));
}
};
for event in events.iter().take(num_events) {
let dispatch_event: EpollDispatch = event.data.into();
match dispatch_event {
EpollDispatch::Unknown => {
let event = event.data;
warn!("Unknown serial manager loop event: {}", event);
}
EpollDispatch::File => {
if event.events & libc::EPOLLOUT as u32 != 0 {
serial
.as_ref()
.lock()
.unwrap()
.flush_output()
.map_err(Error::FlushOutput)?;
}
if event.events & libc::EPOLLIN as u32 != 0 {
let mut input = [0u8; 64];
let count =
in_file.read(&mut input).map_err(Error::ReadInput)?;
// Replace "\n" with "\r" to deal with Windows SAC (#1170)
if count == 1 && input[0] == 0x0a {
input[0] = 0x0d;
}
serial
.as_ref()
.lock()
.unwrap()
.queue_input_bytes(&input[..count])
.map_err(Error::QueueInput)?;
}
}
EpollDispatch::Kill => {
info!("KILL event received, stopping epoll loop");
return Ok(());
}
}
}
}
}))
.map_err(|_| {
error!("serial-manager thread panicked");
exit_evt.write(1).ok()
})
.ok();
})
.map_err(Error::SpawnSerialManager)?;
self.handle = Some(thread);
Ok(())
}
}
impl Drop for SerialManager {
fn drop(&mut self) {
self.kill_evt.write(1).ok();
if let Some(handle) = self.handle.take() {
handle.join().ok();
}
}
}