mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2024-09-12 10:04:51 +00:00
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>
271 lines
9.8 KiB
Rust
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();
|
|
}
|
|
}
|
|
}
|