use std::os::unix::net::UnixStream;
use std::path::Path;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use tokio::sync::broadcast;
use super::fake_driver::{Knobs, NAME};
use super::fake_staging::StagingArea;
use super::fake_vsock::{FakeListener, Inbound, echo_peer};
use super::lock;
use crate::capability::{
AfterCheckpoint, Balloon, BalloonStats, Checkpoint, CheckpointFormat, CheckpointImage,
CheckpointKind, CheckpointOptions, Console, DebugSnapshot, Detach, Staging, Vsock, VsockListen,
VsockListener,
};
use crate::driver::{
DriverCapabilities, ExitStatus, IoMode, ShutdownMode, VmEvent, VmHandle, VmRecord, VmState,
VsockConn,
};
use crate::error::{Error, Result};
use crate::spec::{ConsoleSpec, VmId, VmSpec};
const SIGKILL: i32 = 9;
pub const CHECKPOINT_FORMAT: &str = "fake/v1";
pub(super) const VMSTATE_FILE: &str = "vmstate";
pub(super) const MEM_FILE: &str = "mem";
const MEM_IMAGE: &[u8] = b"fake guest memory\n";
#[derive(Debug, Serialize, Deserialize)]
pub(super) struct CheckpointFile {
pub(super) format: CheckpointFormat,
pub(super) kind: CheckpointKind,
pub(super) spec: VmSpec,
pub(super) balloon_target_bytes: u64,
}
pub(super) struct VmInner {
spec: VmSpec,
record: VmRecord,
caps: DriverCapabilities,
knobs: Arc<Knobs>,
state: Mutex<VmState>,
owned: AtomicBool,
restored: bool,
events: broadcast::Sender<VmEvent>,
inbound: Arc<Inbound>,
area: StagingArea,
console: Mutex<Vec<u8>>,
balloon_target_bytes: AtomicU64,
shutdowns: Mutex<Vec<ShutdownMode>>,
}
impl VmInner {
pub(super) fn new(
spec: VmSpec,
record: VmRecord,
caps: DriverCapabilities,
knobs: Arc<Knobs>,
balloon_target_bytes: u64,
inbound: Arc<Inbound>,
restored: bool,
) -> Arc<Self> {
let (events, _) = broadcast::channel(16);
let area = StagingArea::new(&record.runtime_dir);
Arc::new(Self {
spec,
record,
caps,
knobs,
state: Mutex::new(VmState::Running),
owned: AtomicBool::new(false),
restored,
events,
inbound,
area,
console: Mutex::new(Vec::new()),
balloon_target_bytes: AtomicU64::new(balloon_target_bytes),
shutdowns: Mutex::new(Vec::new()),
})
}
pub(super) fn id(&self) -> &VmId {
&self.spec.id
}
pub(super) fn state(&self) -> VmState {
*lock(&self.state)
}
pub(super) fn is_owned(&self) -> bool {
self.owned.load(Ordering::Acquire)
}
pub(super) const fn is_restored(&self) -> bool {
self.restored
}
pub(super) fn exit(&self, status: ExitStatus) -> ExitStatus {
{
let mut state = lock(&self.state);
if let VmState::Exited(earlier) = *state {
return earlier;
}
*state = VmState::Exited(status);
}
let _ = self.events.send(VmEvent::Exited(status));
self.inbound.close(status);
status
}
fn require_running(&self) -> Result<()> {
match self.state() {
VmState::Running => Ok(()),
state => Err(Error::WrongState {
id: self.spec.id.clone(),
state,
expected: "running",
}),
}
}
fn require_alive(&self) -> Result<()> {
match self.state() {
state @ VmState::Exited(_) => Err(Error::WrongState {
id: self.spec.id.clone(),
state,
expected: "running or quiesced",
}),
VmState::Running | VmState::Quiesced => Ok(()),
}
}
pub(super) fn push_inbound(&self, port: u32, stream: UnixStream) {
self.inbound.push(port, stream);
}
pub(super) fn push_console(&self, bytes: &[u8]) {
lock(&self.console).extend_from_slice(bytes);
}
pub(super) fn shutdowns(&self) -> Vec<ShutdownMode> {
lock(&self.shutdowns).clone()
}
}
pub struct FakeVm {
vm: Arc<VmInner>,
checkpointer: Checkpointer,
ownership: Ownership,
}
impl FakeVm {
pub(super) fn new(vm: Arc<VmInner>) -> Self {
vm.owned.store(true, Ordering::Release);
Self {
checkpointer: Checkpointer(Arc::clone(&vm)),
ownership: Ownership {
vm: Arc::clone(&vm),
detached: AtomicBool::new(false),
},
vm,
}
}
fn has_vsock(&self) -> bool {
self.vm.spec.vsock.is_some()
}
}
impl Drop for FakeVm {
fn drop(&mut self) {
if !self.ownership.detached.load(Ordering::Acquire) {
self.vm.exit(ExitStatus::signaled(SIGKILL));
}
}
}
#[async_trait]
impl VmHandle for FakeVm {
fn id(&self) -> &VmId {
self.vm.id()
}
fn record(&self) -> VmRecord {
self.vm.record.clone()
}
fn state(&self) -> VmState {
self.vm.state()
}
fn events(&self) -> broadcast::Receiver<VmEvent> {
self.vm.events.subscribe()
}
async fn shutdown(&self, mode: ShutdownMode) -> Result<ExitStatus> {
lock(&self.vm.shutdowns).push(mode);
let status = match mode {
ShutdownMode::Graceful { .. } => ExitStatus::exited(0),
ShutdownMode::Kill => ExitStatus::signaled(SIGKILL),
};
Ok(self.vm.exit(status))
}
fn vsock(&self) -> Option<&dyn Vsock> {
(self.vm.caps.vsock && self.has_vsock()).then_some(self)
}
fn checkpoint(&self) -> Option<&dyn Checkpoint> {
self.vm.caps.checkpoint.then_some(&self.checkpointer)
}
fn vsock_listener(&self) -> Option<&dyn VsockListen> {
(self.vm.caps.vsock_listen && self.has_vsock()).then_some(self)
}
fn detach(&self) -> Option<&dyn Detach> {
self.vm.caps.adopt.then_some(&self.ownership)
}
fn staging(&self) -> Option<&dyn Staging> {
self.vm.caps.staging.then_some(&self.vm.area)
}
fn balloon(&self) -> Option<&dyn Balloon> {
(self.vm.caps.balloon && self.vm.spec.balloon).then_some(self)
}
fn console(&self) -> Option<&dyn Console> {
(self.vm.caps.console && self.vm.spec.console != ConsoleSpec::Off).then_some(self)
}
fn debug(&self) -> Option<&dyn DebugSnapshot> {
self.vm.caps.debug.then_some(self)
}
}
#[async_trait]
impl Vsock for FakeVm {
async fn dial(&self, _port: u32) -> Result<VsockConn> {
self.vm.require_running()?;
Ok(VsockConn {
fd: echo_peer()?.into(),
mode: IoMode::Async,
})
}
}
#[async_trait]
impl VsockListen for FakeVm {
async fn listen(&self, port: u32) -> Result<Box<dyn VsockListener>> {
self.vm.require_alive()?;
Ok(Box::new(FakeListener {
inbound: Arc::clone(&self.vm.inbound),
port,
}))
}
}
struct Checkpointer(Arc<VmInner>);
#[async_trait]
impl Checkpoint for Checkpointer {
async fn checkpoint(&self, dst: &Path, opts: CheckpointOptions) -> Result<CheckpointImage> {
let vm = &self.0;
vm.require_alive()?;
if opts.kind == CheckpointKind::Diff && !vm.caps.diff_checkpoint {
return Err(Error::Driver {
driver: NAME,
message: "diff checkpoints are not enabled on this fake".into(),
source: None,
});
}
if vm.knobs.fail_checkpoint.swap(false, Ordering::AcqRel) {
return Err(Error::Driver {
driver: NAME,
message: "scripted checkpoint failure".into(),
source: None,
});
}
if vm.knobs.freeze_checkpoint.swap(false, Ordering::AcqRel) {
{
let mut state = lock(&vm.state);
if !matches!(*state, VmState::Exited(_)) {
*state = VmState::Quiesced;
}
}
return Err(Error::Driver {
driver: NAME,
message: "the guest stays quiesced: it could not be resumed after a failed \
checkpoint"
.into(),
source: None,
});
}
let file = CheckpointFile {
format: CheckpointFormat::new(CHECKPOINT_FORMAT),
kind: opts.kind,
spec: vm.spec.clone(),
balloon_target_bytes: vm.balloon_target_bytes.load(Ordering::Acquire),
};
let json = serde_json::to_vec_pretty(&file).map_err(std::io::Error::from)?;
tokio::fs::create_dir_all(dst).await?;
tokio::fs::write(dst.join(VMSTATE_FILE), json).await?;
if opts.kind == CheckpointKind::Full {
tokio::fs::write(dst.join(MEM_FILE), MEM_IMAGE).await?;
} else if let Err(error) = tokio::fs::remove_file(dst.join(MEM_FILE)).await
&& error.kind() != std::io::ErrorKind::NotFound
{
return Err(Error::Io(error));
}
{
let mut state = lock(&vm.state);
if !matches!(*state, VmState::Exited(_)) {
*state = match opts.after {
AfterCheckpoint::Resume => VmState::Running,
AfterCheckpoint::HoldQuiesced => VmState::Quiesced,
};
}
}
Ok(CheckpointImage {
dir: dst.to_path_buf(),
format: file.format,
kind: opts.kind,
})
}
}
struct Ownership {
vm: Arc<VmInner>,
detached: AtomicBool,
}
#[async_trait]
impl Detach for Ownership {
async fn detach(&self) -> Result<VmRecord> {
self.vm.require_alive()?;
self.detached.store(true, Ordering::Release);
self.vm.owned.store(false, Ordering::Release);
Ok(self.vm.record.clone())
}
}
#[async_trait]
impl Balloon for FakeVm {
async fn set_target(&self, bytes: u64) -> Result<()> {
self.vm.require_alive()?;
self.vm.balloon_target_bytes.store(bytes, Ordering::Release);
Ok(())
}
async fn stats(&self) -> Result<BalloonStats> {
let target_bytes = self.vm.balloon_target_bytes.load(Ordering::Acquire);
Ok(BalloonStats {
target_bytes,
current_bytes: Some(target_bytes),
})
}
}
#[async_trait]
impl Console for FakeVm {
async fn read_output(&self, max_bytes: usize) -> Result<Vec<u8>> {
let mut console = lock(&self.vm.console);
let n = max_bytes.min(console.len());
Ok(console.drain(..n).collect())
}
}
impl DebugSnapshot for FakeVm {
fn snapshot(&self) -> serde_json::Value {
serde_json::json!({
"driver": NAME,
"id": self.vm.id().as_str(),
"state": self.vm.state().to_string(),
"balloon_target_bytes": self.vm.balloon_target_bytes.load(Ordering::Acquire),
"console_pending_bytes": lock(&self.vm.console).len(),
"inbound_ports": self.vm.inbound.pending_ports(),
})
}
}