2019-02-22 16:04:35 +00:00
|
|
|
// Copyright © 2019 Intel Corporation
|
|
|
|
//
|
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
//
|
|
|
|
|
2019-05-19 02:24:47 +00:00
|
|
|
#[macro_use]
|
|
|
|
extern crate log;
|
2019-09-25 12:09:33 +00:00
|
|
|
extern crate vmm_sys_util;
|
2019-02-22 16:04:35 +00:00
|
|
|
|
2019-09-25 12:40:14 +00:00
|
|
|
use crate::api::{ApiError, ApiRequest, ApiResponse, ApiResponsePayload};
|
|
|
|
use crate::vm::{Error as VmError, ExitBehaviour, Vm};
|
2019-09-25 12:09:33 +00:00
|
|
|
use libc::EFD_NONBLOCK;
|
|
|
|
use std::io;
|
2019-09-25 12:25:08 +00:00
|
|
|
use std::os::unix::io::{AsRawFd, RawFd};
|
2019-09-25 12:40:14 +00:00
|
|
|
use std::sync::mpsc::{channel, Receiver, RecvError, SendError, Sender};
|
2019-09-24 14:00:00 +00:00
|
|
|
use std::sync::Arc;
|
2019-09-25 12:40:14 +00:00
|
|
|
use std::{result, thread};
|
2019-09-25 12:09:33 +00:00
|
|
|
use vmm_sys_util::eventfd::EventFd;
|
2019-03-07 13:56:43 +00:00
|
|
|
|
2019-09-25 12:14:15 +00:00
|
|
|
pub mod api;
|
2019-05-23 19:48:05 +00:00
|
|
|
pub mod config;
|
2019-09-04 13:55:14 +00:00
|
|
|
pub mod device_manager;
|
2019-02-28 13:16:58 +00:00
|
|
|
pub mod vm;
|
2019-02-22 16:04:35 +00:00
|
|
|
|
2019-05-23 19:48:05 +00:00
|
|
|
use self::config::VmConfig;
|
2019-09-25 12:40:14 +00:00
|
|
|
//use self::vm::{ExitBehaviour, Vm};
|
2019-05-10 08:46:27 +00:00
|
|
|
|
2019-09-25 12:40:14 +00:00
|
|
|
/// Errors associated with VMM management
|
2019-05-10 08:46:27 +00:00
|
|
|
#[derive(Debug)]
|
2019-09-25 12:40:14 +00:00
|
|
|
#[allow(clippy::large_enum_variant)]
|
2019-05-10 08:46:27 +00:00
|
|
|
pub enum Error {
|
2019-09-25 12:40:14 +00:00
|
|
|
/// API request receive error
|
|
|
|
ApiRequestRecv(RecvError),
|
|
|
|
|
|
|
|
/// API response receive error
|
|
|
|
ApiResponseRecv(RecvError),
|
|
|
|
|
|
|
|
/// API request send error
|
|
|
|
ApiRequestSend(SendError<ApiRequest>),
|
|
|
|
|
|
|
|
/// API response send error
|
|
|
|
ApiResponseSend(SendError<ApiResponse>),
|
|
|
|
|
|
|
|
/// Cannot create a VM from the API
|
|
|
|
ApiVmCreate(ApiError),
|
|
|
|
|
|
|
|
/// Cannot start a VM from the API
|
|
|
|
ApiVmStart(ApiError),
|
|
|
|
|
|
|
|
/// Cannot clone EventFd.
|
|
|
|
EventFdClone(io::Error),
|
|
|
|
|
2019-09-25 12:09:33 +00:00
|
|
|
/// Cannot create EventFd.
|
2019-09-25 12:40:14 +00:00
|
|
|
EventFdCreate(io::Error),
|
2019-09-25 12:09:33 +00:00
|
|
|
|
2019-09-25 12:40:14 +00:00
|
|
|
/// Cannot read from EventFd.
|
|
|
|
EventFdRead(io::Error),
|
2019-05-10 08:46:27 +00:00
|
|
|
|
2019-09-25 12:40:14 +00:00
|
|
|
/// Cannot write to EventFd.
|
|
|
|
EventFdWrite(io::Error),
|
2019-09-24 14:14:04 +00:00
|
|
|
|
2019-09-25 12:40:14 +00:00
|
|
|
/// Cannot create epoll context.
|
|
|
|
Epoll(io::Error),
|
2019-05-10 08:46:27 +00:00
|
|
|
|
2019-09-25 12:40:14 +00:00
|
|
|
/// Cannot handle the VM STDIN stream
|
|
|
|
Stdin(VmError),
|
2019-05-10 08:46:27 +00:00
|
|
|
|
2019-09-25 12:40:14 +00:00
|
|
|
/// Cannot create a VM
|
|
|
|
VmCreate(VmError),
|
|
|
|
|
|
|
|
/// Cannot start a VM
|
|
|
|
VmStart(VmError),
|
|
|
|
|
|
|
|
/// Cannot stop a VM
|
|
|
|
VmStop(VmError),
|
2019-09-25 12:45:23 +00:00
|
|
|
|
|
|
|
/// Cannot create VMM thread
|
|
|
|
VmmThreadSpawn(io::Error),
|
2019-05-10 08:46:27 +00:00
|
|
|
}
|
2019-09-25 12:40:14 +00:00
|
|
|
pub type Result<T> = result::Result<T, Error>;
|
2019-02-22 16:04:35 +00:00
|
|
|
|
2019-09-25 12:25:08 +00:00
|
|
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
|
|
pub enum EpollDispatch {
|
|
|
|
Exit,
|
|
|
|
Reset,
|
|
|
|
Stdin,
|
|
|
|
Api,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct EpollContext {
|
|
|
|
raw_fd: RawFd,
|
|
|
|
dispatch_table: Vec<Option<EpollDispatch>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl EpollContext {
|
|
|
|
pub fn new() -> result::Result<EpollContext, io::Error> {
|
|
|
|
let raw_fd = epoll::create(true)?;
|
|
|
|
|
|
|
|
// Initial capacity needs to be large enough to hold:
|
|
|
|
// * 1 exit event
|
|
|
|
// * 1 reset event
|
|
|
|
// * 1 stdin event
|
|
|
|
// * 1 API event
|
|
|
|
let mut dispatch_table = Vec::with_capacity(5);
|
|
|
|
dispatch_table.push(None);
|
|
|
|
|
|
|
|
Ok(EpollContext {
|
|
|
|
raw_fd,
|
|
|
|
dispatch_table,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn add_stdin(&mut self) -> result::Result<(), io::Error> {
|
|
|
|
let dispatch_index = self.dispatch_table.len() as u64;
|
|
|
|
epoll::ctl(
|
|
|
|
self.raw_fd,
|
|
|
|
epoll::ControlOptions::EPOLL_CTL_ADD,
|
|
|
|
libc::STDIN_FILENO,
|
|
|
|
epoll::Event::new(epoll::Events::EPOLLIN, dispatch_index),
|
|
|
|
)?;
|
|
|
|
|
|
|
|
self.dispatch_table.push(Some(EpollDispatch::Stdin));
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn add_event<T>(&mut self, fd: &T, token: EpollDispatch) -> result::Result<(), io::Error>
|
|
|
|
where
|
|
|
|
T: AsRawFd,
|
|
|
|
{
|
|
|
|
let dispatch_index = self.dispatch_table.len() as u64;
|
|
|
|
epoll::ctl(
|
|
|
|
self.raw_fd,
|
|
|
|
epoll::ControlOptions::EPOLL_CTL_ADD,
|
|
|
|
fd.as_raw_fd(),
|
|
|
|
epoll::Event::new(epoll::Events::EPOLLIN, dispatch_index),
|
|
|
|
)?;
|
|
|
|
self.dispatch_table.push(Some(token));
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl AsRawFd for EpollContext {
|
|
|
|
fn as_raw_fd(&self) -> RawFd {
|
|
|
|
self.raw_fd
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-25 12:45:23 +00:00
|
|
|
pub fn start_vmm_thread(
|
|
|
|
api_event: EventFd,
|
|
|
|
api_receiver: Receiver<ApiRequest>,
|
|
|
|
) -> Result<thread::JoinHandle<Result<()>>> {
|
|
|
|
thread::Builder::new()
|
|
|
|
.name("vmm".to_string())
|
|
|
|
.spawn(move || {
|
|
|
|
let mut vmm = Vmm::new(api_event)?;
|
|
|
|
|
|
|
|
let receiver = Arc::new(api_receiver);
|
|
|
|
'outer: loop {
|
|
|
|
match vmm.control_loop(Arc::clone(&receiver)) {
|
|
|
|
Ok(ExitBehaviour::Reset) => {
|
|
|
|
// The VMM control loop exites with a reset behaviour.
|
|
|
|
// We have to reboot the VM, i.e. we create a new VM
|
|
|
|
// based on the same VM config, start it and restart
|
|
|
|
// the control loop.
|
|
|
|
|
|
|
|
// Without ACPI, a reset is equivalent to a shutdown
|
|
|
|
#[cfg(not(feature = "acpi"))]
|
|
|
|
{
|
|
|
|
if let Some(ref mut vm) = vmm.vm {
|
|
|
|
vm.stop().map_err(Error::VmStop)?;
|
|
|
|
break 'outer;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// First we stop the current VM and create a new one.
|
|
|
|
if let Some(ref mut vm) = vmm.vm {
|
|
|
|
let config = vm.get_config();
|
|
|
|
vm.stop().map_err(Error::VmStop)?;
|
|
|
|
|
|
|
|
let exit_evt = vmm.exit_evt.try_clone().map_err(Error::EventFdClone)?;
|
|
|
|
let reset_evt =
|
|
|
|
vmm.reset_evt.try_clone().map_err(Error::EventFdClone)?;
|
|
|
|
|
|
|
|
vmm.vm = Some(
|
|
|
|
Vm::new(config, exit_evt, reset_evt).map_err(Error::VmCreate)?,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Then we start the new VM.
|
|
|
|
if let Some(ref mut vm) = vmm.vm {
|
|
|
|
vm.start().map_err(Error::VmStart)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Continue and restart the VMM control loop
|
|
|
|
continue 'outer;
|
|
|
|
}
|
|
|
|
Ok(ExitBehaviour::Shutdown) => {
|
|
|
|
// The VMM control loop exites with a shutdown behaviour.
|
|
|
|
// We have to stop the VM and we exit thr thread.
|
|
|
|
if let Some(ref mut vm) = vmm.vm {
|
|
|
|
vm.stop().map_err(Error::VmStop)?;
|
|
|
|
}
|
|
|
|
break 'outer;
|
|
|
|
}
|
|
|
|
Err(e) => return Err(e),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
})
|
|
|
|
.map_err(Error::VmmThreadSpawn)
|
|
|
|
}
|
|
|
|
|
2019-09-25 12:47:30 +00:00
|
|
|
pub fn vm_create(
|
|
|
|
api_evt: EventFd,
|
|
|
|
api_sender: Sender<ApiRequest>,
|
|
|
|
config: Arc<VmConfig>,
|
|
|
|
) -> Result<()> {
|
|
|
|
let (response_sender, response_receiver) = channel();
|
|
|
|
|
|
|
|
// Send the VM creation request.
|
|
|
|
api_sender
|
|
|
|
.send(ApiRequest::VmCreate(config, response_sender))
|
|
|
|
.map_err(Error::ApiRequestSend)?;
|
|
|
|
api_evt.write(1).map_err(Error::EventFdWrite)?;
|
|
|
|
|
|
|
|
response_receiver
|
|
|
|
.recv()
|
|
|
|
.map_err(Error::ApiResponseRecv)?
|
|
|
|
.map_err(Error::ApiVmCreate)?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn vm_start(api_evt: EventFd, api_sender: Sender<ApiRequest>) -> Result<()> {
|
|
|
|
let (response_sender, response_receiver) = channel();
|
|
|
|
|
|
|
|
// Send the VM start request.
|
|
|
|
api_sender
|
|
|
|
.send(ApiRequest::VmStart(response_sender))
|
|
|
|
.map_err(Error::ApiRequestSend)?;
|
|
|
|
api_evt.write(1).map_err(Error::EventFdWrite)?;
|
|
|
|
|
|
|
|
response_receiver
|
|
|
|
.recv()
|
|
|
|
.map_err(Error::ApiResponseRecv)?
|
|
|
|
.map_err(Error::ApiVmStart)?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-09-25 12:40:14 +00:00
|
|
|
pub struct Vmm {
|
|
|
|
epoll: EpollContext,
|
|
|
|
exit_evt: EventFd,
|
|
|
|
reset_evt: EventFd,
|
|
|
|
api_evt: EventFd,
|
|
|
|
vm: Option<Vm>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Vmm {
|
|
|
|
fn new(api_evt: EventFd) -> Result<Self> {
|
|
|
|
let mut epoll = EpollContext::new().map_err(Error::Epoll)?;
|
|
|
|
let exit_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::EventFdCreate)?;
|
|
|
|
let reset_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::EventFdCreate)?;
|
|
|
|
|
|
|
|
if unsafe { libc::isatty(libc::STDIN_FILENO as i32) } != 0 {
|
|
|
|
epoll.add_stdin().map_err(Error::Epoll)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
epoll
|
|
|
|
.add_event(&exit_evt, EpollDispatch::Exit)
|
|
|
|
.map_err(Error::Epoll)?;
|
|
|
|
|
|
|
|
epoll
|
|
|
|
.add_event(&reset_evt, EpollDispatch::Reset)
|
|
|
|
.map_err(Error::Epoll)?;
|
|
|
|
|
|
|
|
epoll
|
|
|
|
.add_event(&api_evt, EpollDispatch::Api)
|
|
|
|
.map_err(Error::Epoll)?;
|
|
|
|
|
|
|
|
Ok(Vmm {
|
|
|
|
epoll,
|
|
|
|
exit_evt,
|
|
|
|
reset_evt,
|
|
|
|
api_evt,
|
|
|
|
vm: None,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
fn control_loop(&mut self, api_receiver: Arc<Receiver<ApiRequest>>) -> Result<ExitBehaviour> {
|
|
|
|
const EPOLL_EVENTS_LEN: usize = 100;
|
|
|
|
|
|
|
|
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
|
|
|
|
let epoll_fd = self.epoll.as_raw_fd();
|
|
|
|
|
|
|
|
let exit_behaviour;
|
|
|
|
|
|
|
|
'outer: 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_idx = event.data as usize;
|
|
|
|
|
|
|
|
if let Some(dispatch_type) = self.epoll.dispatch_table[dispatch_idx] {
|
|
|
|
match dispatch_type {
|
|
|
|
EpollDispatch::Exit => {
|
|
|
|
// Consume the event.
|
|
|
|
self.exit_evt.read().map_err(Error::EventFdRead)?;
|
|
|
|
exit_behaviour = ExitBehaviour::Shutdown;
|
|
|
|
|
|
|
|
break 'outer;
|
|
|
|
}
|
|
|
|
EpollDispatch::Reset => {
|
|
|
|
// Consume the event.
|
|
|
|
self.reset_evt.read().map_err(Error::EventFdRead)?;
|
|
|
|
exit_behaviour = ExitBehaviour::Reset;
|
|
|
|
|
|
|
|
break 'outer;
|
|
|
|
}
|
|
|
|
EpollDispatch::Stdin => {
|
|
|
|
if let Some(ref vm) = self.vm {
|
|
|
|
vm.handle_stdin().map_err(Error::Stdin)?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
EpollDispatch::Api => {
|
|
|
|
// Consume the event.
|
|
|
|
self.api_evt.read().map_err(Error::EventFdRead)?;
|
|
|
|
|
|
|
|
// Read from the API receiver channel
|
|
|
|
let api_request = api_receiver.recv().map_err(Error::ApiRequestRecv)?;
|
|
|
|
|
|
|
|
match api_request {
|
|
|
|
ApiRequest::VmCreate(config, sender) => {
|
|
|
|
let exit_evt =
|
|
|
|
self.exit_evt.try_clone().map_err(Error::EventFdClone)?;
|
|
|
|
let reset_evt =
|
|
|
|
self.reset_evt.try_clone().map_err(Error::EventFdClone)?;
|
|
|
|
let response = match Vm::new(config, exit_evt, reset_evt) {
|
|
|
|
Ok(vm) => {
|
|
|
|
self.vm = Some(vm);
|
|
|
|
Ok(ApiResponsePayload::Empty)
|
|
|
|
}
|
|
|
|
Err(e) => Err(ApiError::VmCreate(e)),
|
|
|
|
};
|
|
|
|
|
|
|
|
sender.send(response).map_err(Error::ApiResponseSend)?;
|
|
|
|
}
|
|
|
|
ApiRequest::VmStart(sender) => {
|
|
|
|
if let Some(ref mut vm) = self.vm {
|
|
|
|
let response = match vm.start() {
|
|
|
|
Ok(_) => Ok(ApiResponsePayload::Empty),
|
|
|
|
Err(e) => Err(ApiError::VmStart(e)),
|
|
|
|
};
|
|
|
|
|
|
|
|
sender.send(response).map_err(Error::ApiResponseSend)?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(exit_behaviour)
|
|
|
|
}
|
|
|
|
}
|