use std::{io::ErrorKind, time::Duration};
use super::{VmiState, context::VmiContext};
use crate::{
Architecture, VcpuId, VmiCore, VmiError, VmiHandler,
driver::{VmiDriver, VmiEventControl, VmiQueryRegisters, VmiVmControl},
os::{NoOS, VmiOs},
};
pub struct VmiSession<'a, Os>
where
Os: VmiOs,
{
core: &'a VmiCore<Os::Driver>,
os: &'a Os,
}
impl<Os> Clone for VmiSession<'_, Os>
where
Os: VmiOs,
{
fn clone(&self) -> Self {
*self
}
}
impl<Os> Copy for VmiSession<'_, Os> where Os: VmiOs {}
impl<Os> std::ops::Deref for VmiSession<'_, Os>
where
Os: VmiOs,
{
type Target = VmiCore<Os::Driver>;
fn deref(&self) -> &Self::Target {
self.core
}
}
impl<'a, Os> VmiSession<'a, Os>
where
Os: VmiOs,
{
pub fn new(core: &'a VmiCore<Os::Driver>, os: &'a Os) -> Self {
Self { core, os }
}
pub fn with_registers(
&'a self,
registers: &'a <Os::Architecture as Architecture>::Registers,
) -> VmiState<'a, Os> {
VmiState::new(self, registers)
}
pub fn without_os(&self) -> VmiSession<'a, NoOS<Os::Driver>> {
VmiSession {
core: self.core,
os: const { &NoOS(std::marker::PhantomData) },
}
}
pub fn core(&self) -> &'a VmiCore<Os::Driver> {
self.core
}
pub fn underlying_os(&self) -> &'a Os {
self.os
}
}
impl<'a, Os> VmiSession<'a, Os>
where
Os: VmiOs,
Os::Driver: VmiEventControl,
{
pub fn wait_for_event(
&self,
timeout: Duration,
handler: &mut impl VmiHandler<Os>,
) -> Result<(), VmiError> {
self.core.wait_for_event(timeout, |event| {
let state = VmiState::new(self, event.registers());
handler.handle_event(VmiContext::new(&state, event))
})
}
}
impl<'a, Os> VmiSession<'a, Os>
where
Os: VmiOs,
Os::Driver: VmiEventControl + VmiVmControl,
{
pub fn handle<Handler>(
&self,
handler_factory: impl FnOnce(&VmiSession<Os>) -> Result<Handler, VmiError>,
) -> Result<Option<Handler::Output>, VmiError>
where
Handler: VmiHandler<Os>,
{
self.handle_with_timeout(Duration::from_millis(5000), handler_factory)
}
pub fn handle_with_timeout<Handler>(
&self,
timeout: Duration,
handler_factory: impl FnOnce(&VmiSession<Os>) -> Result<Handler, VmiError>,
) -> Result<Option<Handler::Output>, VmiError>
where
Handler: VmiHandler<Os>,
{
let mut result;
let mut handler = handler_factory(self)?;
loop {
result = handler.poll();
if result.is_some() {
break;
}
match self.wait_for_event(timeout, &mut handler) {
Ok(_) => {}
Err(VmiError::Timeout) => {
tracing::trace!("timeout");
handler.handle_timeout(self);
}
Err(VmiError::Io(err)) if err.kind() == ErrorKind::Interrupted => {
tracing::trace!("interrupted");
handler.handle_interrupted(self);
break;
}
Err(err) => return Err(err),
}
}
tracing::trace!("disabling monitor");
handler.cleanup(self);
self.core.reset_state()?;
tracing::trace!(pending_events = self.events_pending());
let _pause_guard = self.core().pause_guard()?;
if self.events_pending() > 0 {
match self.wait_for_event(Duration::from_millis(0), &mut handler) {
Err(VmiError::Timeout) => {
tracing::trace!("timeout");
}
Err(err) => return Err(err),
Ok(_) => {}
}
}
Ok(result)
}
}
impl<'a, Os> VmiSession<'a, Os>
where
Os: VmiOs,
Os::Driver: VmiQueryRegisters + VmiVmControl,
{
pub fn pause_guard(&self) -> Result<VmiSessionPauseGuard<'_, Os>, VmiError> {
VmiSessionPauseGuard::new(self)
}
}
pub struct VmiSessionPauseGuard<'a, Os>
where
Os: VmiOs,
Os::Driver: VmiQueryRegisters + VmiVmControl,
{
session: &'a VmiSession<'a, Os>,
registers: <<Os::Driver as VmiDriver>::Architecture as Architecture>::Registers,
}
impl<'a, Os> VmiSessionPauseGuard<'a, Os>
where
Os: VmiOs,
Os::Driver: VmiQueryRegisters + VmiVmControl,
{
pub fn new(session: &'a VmiSession<'a, Os>) -> Result<Self, VmiError> {
session.driver().pause()?;
let registers = match session.registers(VcpuId(0)) {
Ok(registers) => registers,
Err(err) => {
if let Err(resume_err) = session.driver().resume() {
tracing::error!(
err = %resume_err,
"failed to resume after register-query failure"
);
}
return Err(err);
}
};
Ok(Self { session, registers })
}
pub fn session(&self) -> &VmiSession<'a, Os> {
self.session
}
pub fn registers(&self) -> &<Os::Architecture as Architecture>::Registers {
&self.registers
}
pub fn state(&self) -> VmiState<'_, Os> {
VmiState::new(self.session, &self.registers)
}
}
impl<Os> Drop for VmiSessionPauseGuard<'_, Os>
where
Os: VmiOs,
Os::Driver: VmiQueryRegisters + VmiVmControl,
{
fn drop(&mut self) {
if let Err(err) = self.session.driver().resume() {
tracing::error!(%err, "failed to resume the virtual machine");
}
}
}