use std::collections::HashMap;
use std::env;
use std::path::{Path, PathBuf};
use std::sync::mpsc;
use std::time::{Duration, Instant};
use mimobox_core::{
DirEntry, FileStat, Sandbox, SandboxConfig, SandboxError, SandboxResult, SandboxSnapshot,
};
use tracing::debug;
use crate::http_proxy::{HttpProxyError, HttpRequest, HttpResponse};
use crate::snapshot::MicrovmSnapshot;
#[cfg(all(target_os = "linux", feature = "kvm"))]
use crate::snapshot::load_state_from_memory_file;
#[cfg(all(target_os = "linux", feature = "kvm", not(feature = "zerocopy-fork")))]
use crate::snapshot::{
FILE_SNAPSHOT_VERSION, SnapshotStateFile, create_snapshot_dir, memory_sha256_hex,
};
use crate::vm_assets::resolve_vm_assets_dir;
#[cfg(all(target_os = "linux", feature = "kvm"))]
use crate::kvm::{KvmBackend, restore_runtime_state};
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct MicrovmConfig {
pub vcpu_count: u8,
pub memory_mb: u32,
#[serde(default)]
pub cpu_quota_us: Option<u64>,
pub kernel_path: PathBuf,
pub rootfs_path: PathBuf,
}
impl Default for MicrovmConfig {
fn default() -> Self {
let assets_dir = resolve_vm_assets_dir(
env::var_os("VM_ASSETS_DIR").map(PathBuf::from),
env::var_os("HOME").map(PathBuf::from),
)
.unwrap_or_else(|_| PathBuf::from("/var/lib/mimobox/vm"));
Self {
vcpu_count: 1,
memory_mb: 256,
cpu_quota_us: None,
kernel_path: assets_dir.join("vmlinux"),
rootfs_path: assets_dir.join("rootfs.cpio.gz"),
}
}
}
impl MicrovmConfig {
pub fn memory_bytes(&self) -> Result<usize, MicrovmError> {
let bytes = u64::from(self.memory_mb)
.checked_mul(1024 * 1024)
.ok_or_else(|| {
MicrovmError::InvalidConfig("memory_mb overflow when converting to bytes".into())
})?;
usize::try_from(bytes).map_err(|_| {
MicrovmError::InvalidConfig(
"required memory size exceeds platform address space".into(),
)
})
}
pub fn validate(&self) -> Result<(), MicrovmError> {
if self.vcpu_count == 0 {
return Err(MicrovmError::InvalidConfig(
"vcpu_count must not be 0".into(),
));
}
if self.memory_mb < 64 {
return Err(MicrovmError::InvalidConfig(
"memory_mb must not be less than 64".into(),
));
}
if self.kernel_path.as_os_str().is_empty() {
return Err(MicrovmError::InvalidConfig(
"kernel_path must not be empty".into(),
));
}
if self.rootfs_path.as_os_str().is_empty() {
return Err(MicrovmError::InvalidConfig(
"rootfs_path must not be empty".into(),
));
}
if !self.kernel_path.exists() {
return Err(MicrovmError::InvalidConfig(format!(
"kernel_path does not exist: {}",
self.kernel_path.display()
)));
}
if !self.rootfs_path.exists() {
return Err(MicrovmError::InvalidConfig(format!(
"rootfs_path does not exist: {}",
self.rootfs_path.display()
)));
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MicrovmState {
Created,
Ready,
Running,
Destroyed,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GuestCommandResult {
pub stdout: Vec<u8>,
pub stderr: Vec<u8>,
pub exit_code: Option<i32>,
pub timed_out: bool,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct GuestExecOptions {
pub env: HashMap<String, String>,
pub timeout: Option<Duration>,
pub cwd: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum StreamEvent {
Stdout(Vec<u8>),
Stderr(Vec<u8>),
Exit(i32),
TimedOut,
}
#[derive(Debug, Clone, thiserror::Error)]
pub enum LifecycleError {
#[error("{message}")]
InvalidState {
expected: String,
current: String,
message: String,
},
#[error("{0}")]
Destroyed(
String,
),
#[error("{0}")]
Released(
String,
),
#[error("snapshot only allowed in Ready state")]
NotReady,
#[error("fork only allowed in Ready state")]
NotReadyForFork,
#[error("vsock command channel is not connected")]
VsockNotConnected,
#[error("vsock command channel unavailable")]
VsockUnavailable,
#[error("{0}")]
Other(
String,
),
}
#[derive(Debug, Clone, thiserror::Error)]
pub enum GuestFileErrorKind {
#[error("path not found")]
NotFound,
#[error("I/O error")]
Io,
#[error("permission denied")]
PermissionDenied,
#[error("out of space")]
OutOfSpace,
#[error("unknown status code {0}")]
Unknown(
u8,
),
}
#[derive(Debug, thiserror::Error)]
pub enum MicrovmError {
#[error("KVM microVM backend not supported on current platform")]
UnsupportedPlatform,
#[error("invalid microVM config: {0}")]
InvalidConfig(
String,
),
#[error("microVM lifecycle error: {0}")]
Lifecycle(
LifecycleError,
),
#[error("KVM backend error: {0}")]
Backend(
String,
),
#[error("guest file error: {path}: {kind}")]
GuestFile {
kind: GuestFileErrorKind,
path: String,
},
#[error(transparent)]
HttpProxy(
#[from]
HttpProxyError,
),
#[error("invalid snapshot format: {0}")]
SnapshotFormat(
String,
),
#[error("I/O error: {0}")]
Io(
#[from]
std::io::Error,
),
}
impl From<MicrovmError> for SandboxError {
fn from(value: MicrovmError) -> Self {
match value {
MicrovmError::UnsupportedPlatform => SandboxError::Unsupported,
MicrovmError::InvalidConfig(message)
| MicrovmError::Backend(message)
| MicrovmError::HttpProxy(crate::http_proxy::HttpProxyError::Internal(message))
| MicrovmError::SnapshotFormat(message) => SandboxError::ExecutionFailed(message),
MicrovmError::Lifecycle(error) => SandboxError::ExecutionFailed(error.to_string()),
error @ MicrovmError::GuestFile { .. } => {
SandboxError::ExecutionFailed(error.to_string())
}
MicrovmError::HttpProxy(error) => SandboxError::ExecutionFailed(error.to_string()),
MicrovmError::Io(error) => SandboxError::Io(error),
}
}
}
#[allow(dead_code)]
enum BackendHandle {
#[cfg(all(target_os = "linux", feature = "kvm"))]
Kvm(Box<KvmBackend>),
Unsupported,
}
impl BackendHandle {
fn create(base_config: SandboxConfig, config: MicrovmConfig) -> Result<Self, MicrovmError> {
#[cfg(all(target_os = "linux", feature = "kvm"))]
{
return Ok(Self::Kvm(Box::new(KvmBackend::create_vm(
base_config,
config,
)?)));
}
#[allow(unreachable_code)]
{
let _ = base_config;
let _ = config;
Err(MicrovmError::UnsupportedPlatform)
}
}
fn create_for_restore(
base_config: SandboxConfig,
config: MicrovmConfig,
) -> Result<Self, MicrovmError> {
#[cfg(all(target_os = "linux", feature = "kvm"))]
{
return Ok(Self::Kvm(Box::new(KvmBackend::create_vm_for_restore(
base_config,
config,
)?)));
}
#[allow(unreachable_code)]
{
let _ = base_config;
let _ = config;
Err(MicrovmError::UnsupportedPlatform)
}
}
#[allow(dead_code)]
fn run_command(&mut self, _cmd: &[String]) -> Result<GuestCommandResult, MicrovmError> {
self.run_command_with_options(_cmd, &GuestExecOptions::default())
}
fn run_command_with_options(
&mut self,
_cmd: &[String],
_options: &GuestExecOptions,
) -> Result<GuestCommandResult, MicrovmError> {
match self {
#[cfg(all(target_os = "linux", feature = "kvm"))]
Self::Kvm(backend) => backend.run_command_with_options(_cmd, _options),
Self::Unsupported => Err(MicrovmError::UnsupportedPlatform),
}
}
#[allow(dead_code)]
fn run_command_streaming(
&mut self,
_cmd: &[String],
) -> Result<mpsc::Receiver<StreamEvent>, MicrovmError> {
self.run_command_streaming_with_options(_cmd, &GuestExecOptions::default())
}
fn run_command_streaming_with_options(
&mut self,
_cmd: &[String],
_options: &GuestExecOptions,
) -> Result<mpsc::Receiver<StreamEvent>, MicrovmError> {
match self {
#[cfg(all(target_os = "linux", feature = "kvm"))]
Self::Kvm(backend) => backend.run_command_streaming_with_options(_cmd, _options),
Self::Unsupported => Err(MicrovmError::UnsupportedPlatform),
}
}
fn read_file(&mut self, _path: &str) -> Result<Vec<u8>, MicrovmError> {
match self {
#[cfg(all(target_os = "linux", feature = "kvm"))]
Self::Kvm(backend) => backend.read_file(_path),
Self::Unsupported => Err(MicrovmError::UnsupportedPlatform),
}
}
fn write_file(&mut self, _path: &str, _data: &[u8]) -> Result<(), MicrovmError> {
match self {
#[cfg(all(target_os = "linux", feature = "kvm"))]
Self::Kvm(backend) => backend.write_file(_path, _data),
Self::Unsupported => Err(MicrovmError::UnsupportedPlatform),
}
}
fn ping(&mut self) -> Result<Duration, MicrovmError> {
match self {
#[cfg(all(target_os = "linux", feature = "kvm"))]
Self::Kvm(backend) => backend.ping(),
Self::Unsupported => Err(MicrovmError::UnsupportedPlatform),
}
}
fn ping_with_timeout(&mut self, _timeout: Duration) -> Result<Duration, MicrovmError> {
match self {
#[cfg(all(target_os = "linux", feature = "kvm"))]
Self::Kvm(backend) => backend.ping_with_timeout(_timeout),
Self::Unsupported => Err(MicrovmError::UnsupportedPlatform),
}
}
fn http_request(&mut self, _request: HttpRequest) -> Result<HttpResponse, MicrovmError> {
match self {
#[cfg(all(target_os = "linux", feature = "kvm"))]
Self::Kvm(backend) => backend.http_request(_request),
Self::Unsupported => Err(MicrovmError::UnsupportedPlatform),
}
}
fn shutdown(&mut self) -> Result<(), MicrovmError> {
match self {
#[cfg(all(target_os = "linux", feature = "kvm"))]
Self::Kvm(backend) => backend.shutdown(),
Self::Unsupported => Err(MicrovmError::UnsupportedPlatform),
}
}
fn is_destroyed(&self) -> bool {
match self {
#[cfg(all(target_os = "linux", feature = "kvm"))]
Self::Kvm(backend) => backend.lifecycle() == crate::kvm::KvmLifecycle::Destroyed,
Self::Unsupported => true,
}
}
fn is_ready(&self) -> bool {
match self {
#[cfg(all(target_os = "linux", feature = "kvm"))]
Self::Kvm(backend) => backend.is_guest_ready(),
Self::Unsupported => false,
}
}
fn snapshot_parts(&self) -> Result<(Vec<u8>, Vec<u8>), MicrovmError> {
match self {
#[cfg(all(target_os = "linux", feature = "kvm"))]
Self::Kvm(backend) => backend.snapshot_state(),
Self::Unsupported => Err(MicrovmError::UnsupportedPlatform),
}
}
fn restore_parts(&mut self, _memory: &[u8], _vcpu_state: &[u8]) -> Result<(), MicrovmError> {
match self {
#[cfg(all(target_os = "linux", feature = "kvm"))]
Self::Kvm(backend) => backend.restore_state(_memory, _vcpu_state),
Self::Unsupported => Err(MicrovmError::UnsupportedPlatform),
}
}
#[cfg(all(target_os = "linux", feature = "kvm"))]
fn restore_from_file_parts(
&mut self,
memory_path: &Path,
vcpu_state: &[u8],
) -> Result<(), MicrovmError> {
match self {
#[cfg(all(target_os = "linux", feature = "kvm"))]
Self::Kvm(backend) => {
let mut restore_profile = backend.take_or_seed_restore_profile();
let restore_memory_started_at = Instant::now();
#[cfg(feature = "zerocopy-fork")]
backend.restore_from_file_zerocopy(memory_path)?;
#[cfg(not(feature = "zerocopy-fork"))]
backend.restore_from_file(memory_path)?;
restore_profile.memory_state_write = restore_memory_started_at.elapsed();
restore_profile.cpuid_config = backend.prepare_restored_vcpus()?;
let runtime_restore_profile = restore_runtime_state(backend, vcpu_state)?;
restore_profile.vcpu_state_restore = runtime_restore_profile.vcpu_state_restore;
restore_profile.device_state_restore = runtime_restore_profile.device_state_restore;
backend.set_lifecycle_ready();
backend.emit_restore_profile_without_resume(&restore_profile);
backend.set_pending_restore_profile(restore_profile);
Ok(())
}
Self::Unsupported => Err(MicrovmError::UnsupportedPlatform),
}
}
}
pub struct MicrovmSandbox {
base_config: SandboxConfig,
microvm_config: MicrovmConfig,
state: MicrovmState,
backend: BackendHandle,
}
impl std::fmt::Debug for MicrovmSandbox {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MicrovmSandbox")
.field("microvm_config", &self.microvm_config)
.field("state", &self.state)
.finish_non_exhaustive()
}
}
impl MicrovmSandbox {
pub fn new(config: MicrovmConfig) -> Result<Self, MicrovmError> {
Self::new_with_base(SandboxConfig::default(), config)
}
pub fn new_with_base(
base_config: SandboxConfig,
microvm_config: MicrovmConfig,
) -> Result<Self, MicrovmError> {
if !cfg!(all(target_os = "linux", feature = "kvm")) {
return Err(MicrovmError::UnsupportedPlatform);
}
microvm_config.validate()?;
debug!(
vcpu_count = microvm_config.vcpu_count,
memory_mb = microvm_config.memory_mb,
"初始化 microVM 沙箱"
);
let backend = BackendHandle::create(base_config.clone(), microvm_config.clone())?;
Ok(Self {
base_config,
microvm_config,
state: MicrovmState::Ready,
backend,
})
}
pub fn snapshot(&mut self) -> Result<SandboxSnapshot, MicrovmError> {
if self.state != MicrovmState::Ready {
return Err(MicrovmError::Lifecycle(LifecycleError::NotReady));
}
let (memory, vcpu_state) = self.backend.snapshot_parts()?;
MicrovmSnapshot::new(
self.base_config.clone(),
self.microvm_config.clone(),
memory,
vcpu_state,
)
.persist_to_files()
}
pub fn restore(snapshot: &SandboxSnapshot) -> Result<Self, MicrovmError> {
let _span = tracing::info_span!("vm_restore").entered();
if let Some(memory_path) = snapshot.memory_file_path() {
return Self::restore_from_file_snapshot(memory_path);
}
let data = snapshot.as_bytes().map_err(map_snapshot_access_error)?;
Self::restore_from_bytes(data)
}
pub fn restore_from_bytes(data: &[u8]) -> Result<Self, MicrovmError> {
let snapshot = MicrovmSnapshot::restore(data)?;
Self::from_snapshot(snapshot)
}
#[cfg(all(target_os = "linux", feature = "kvm"))]
fn restore_from_file_snapshot(memory_path: &Path) -> Result<Self, MicrovmError> {
let (sandbox_config, microvm_config, vcpu_state) =
load_state_from_memory_file(memory_path)?;
let mut backend =
BackendHandle::create_for_restore(sandbox_config.clone(), microvm_config.clone())?;
backend.restore_from_file_parts(memory_path, &vcpu_state)?;
Ok(Self {
base_config: sandbox_config,
microvm_config,
state: MicrovmState::Ready,
backend,
})
}
#[cfg(not(all(target_os = "linux", feature = "kvm")))]
fn restore_from_file_snapshot(_memory_path: &Path) -> Result<Self, MicrovmError> {
Err(MicrovmError::Backend(
"file snapshot restore only supported on Linux".into(),
))
}
#[cfg(all(target_os = "linux", feature = "kvm"))]
#[cfg_attr(docsrs, doc(cfg(feature = "kvm")))]
pub fn fork(&mut self) -> Result<Self, MicrovmError> {
let _span = tracing::info_span!("vm_fork").entered();
if self.state != MicrovmState::Ready {
return Err(MicrovmError::Lifecycle(LifecycleError::NotReadyForFork));
}
#[cfg(feature = "zerocopy-fork")]
{
let (shared_memory, vcpu_state) = match &self.backend {
BackendHandle::Kvm(backend) => backend.snapshot_for_fork()?,
BackendHandle::Unsupported => return Err(MicrovmError::UnsupportedPlatform),
};
let mut backend_handle = BackendHandle::create_for_restore(
self.base_config.clone(),
self.microvm_config.clone(),
)?;
match &mut backend_handle {
BackendHandle::Kvm(backend) => {
backend.restore_from_shared_memory(shared_memory, &vcpu_state)?;
}
BackendHandle::Unsupported => return Err(MicrovmError::UnsupportedPlatform),
}
return Ok(Self {
base_config: self.base_config.clone(),
microvm_config: self.microvm_config.clone(),
state: MicrovmState::Ready,
backend: backend_handle,
});
}
#[cfg(not(feature = "zerocopy-fork"))]
{
use base64::Engine as _;
let (memory, vcpu_state) = self.backend.snapshot_parts()?;
let snapshot_dir = create_snapshot_dir()?;
let memory_path = snapshot_dir.join("memory.bin");
let state_path = snapshot_dir.join("state.json");
let fork_result = (|| {
std::fs::write(&memory_path, &memory)?;
let state = SnapshotStateFile {
version: FILE_SNAPSHOT_VERSION,
sandbox_config: self.base_config.clone(),
microvm_config: self.microvm_config.clone(),
vcpu_state_base64: base64::engine::general_purpose::STANDARD
.encode(&vcpu_state),
memory_hash: Some(memory_sha256_hex(&memory)),
};
let state_bytes = serde_json::to_vec_pretty(&state).map_err(|error| {
MicrovmError::SnapshotFormat(format!("failed to serialize state.json: {error}"))
})?;
std::fs::write(&state_path, state_bytes)?;
Self::restore_from_file_snapshot(&memory_path)
})();
let _ = std::fs::remove_dir_all(snapshot_dir);
fork_result
}
}
#[cfg(not(all(target_os = "linux", feature = "kvm")))]
pub fn fork(&mut self) -> Result<Self, MicrovmError> {
let _span = tracing::info_span!("vm_fork").entered();
Err(MicrovmError::Backend(
"fork only supported on Linux + KVM".into(),
))
}
pub(crate) fn from_snapshot(snapshot: MicrovmSnapshot) -> Result<Self, MicrovmError> {
let (sandbox_config, microvm_config, memory, vcpu_state) = snapshot.into_parts();
let backend =
BackendHandle::create_for_restore(sandbox_config.clone(), microvm_config.clone())?;
let mut sandbox = Self {
base_config: sandbox_config,
microvm_config,
state: MicrovmState::Ready,
backend,
};
sandbox.backend.restore_parts(&memory, &vcpu_state)?;
sandbox.state = MicrovmState::Ready;
Ok(sandbox)
}
fn with_ready_state<F, T>(&mut self, op_name: &str, op: F) -> Result<T, MicrovmError>
where
F: FnOnce(&mut BackendHandle) -> Result<T, MicrovmError>,
{
if self.state != MicrovmState::Ready {
return Err(MicrovmError::Lifecycle(LifecycleError::InvalidState {
expected: "Ready".into(),
current: format!("not Ready for {op_name}"),
message: format!("microVM not ready for {op_name}"),
}));
}
self.state = MicrovmState::Running;
let result = op(&mut self.backend);
self.state = if self.backend.is_destroyed() {
MicrovmState::Destroyed
} else {
MicrovmState::Ready
};
result
}
pub fn read_file(&mut self, path: &str) -> Result<Vec<u8>, MicrovmError> {
self.with_ready_state("read_file", |backend| backend.read_file(path))
}
pub fn write_file(&mut self, path: &str, data: &[u8]) -> Result<(), MicrovmError> {
self.with_ready_state("write_file", |backend| backend.write_file(path, data))
}
pub fn list_dir(&mut self, path: &str) -> Result<Vec<DirEntry>, MicrovmError> {
self.with_ready_state("list_dir", |backend| {
crate::guest_file_ops::list_dir(path, |cmd| backend.run_command(cmd))
})
}
pub fn file_exists(&mut self, path: &str) -> Result<bool, MicrovmError> {
self.with_ready_state("file_exists", |backend| {
crate::guest_file_ops::file_exists(path, |cmd| backend.run_command(cmd))
})
}
pub fn remove_file(&mut self, path: &str) -> Result<(), MicrovmError> {
self.with_ready_state("remove_file", |backend| {
crate::guest_file_ops::remove_file(path, |cmd| backend.run_command(cmd))
})
}
pub fn rename(&mut self, from: &str, to: &str) -> Result<(), MicrovmError> {
self.with_ready_state("rename", |backend| {
crate::guest_file_ops::rename(from, to, |cmd| backend.run_command(cmd))
})
}
pub fn stat(&mut self, path: &str) -> Result<FileStat, MicrovmError> {
self.with_ready_state("stat", |backend| {
crate::guest_file_ops::stat(path, |cmd| backend.run_command(cmd))
})
}
pub fn wait_ready(&mut self, timeout: Duration) -> Result<(), MicrovmError> {
if timeout.is_zero() {
return Err(MicrovmError::InvalidConfig(
"wait_ready timeout must not be zero".into(),
));
}
if self.state == MicrovmState::Destroyed {
return Err(MicrovmError::Lifecycle(LifecycleError::Destroyed(
"microVM destroyed, cannot wait for ready".into(),
)));
}
self.with_ready_state("wait_ready", |backend| {
backend.ping_with_timeout(timeout).map(|_| ())
})
}
pub fn is_ready(&self) -> bool {
self.state == MicrovmState::Ready && self.backend.is_ready()
}
pub fn ping(&mut self) -> Result<Duration, MicrovmError> {
self.with_ready_state("ping", BackendHandle::ping)
}
pub fn stream_execute(
&mut self,
cmd: &[String],
) -> Result<mpsc::Receiver<StreamEvent>, MicrovmError> {
self.stream_execute_with_options(cmd, GuestExecOptions::default())
}
pub fn stream_execute_with_options(
&mut self,
cmd: &[String],
options: GuestExecOptions,
) -> Result<mpsc::Receiver<StreamEvent>, MicrovmError> {
if cmd.is_empty() {
return Err(MicrovmError::InvalidConfig(
"command must not be empty".into(),
));
}
self.with_ready_state("stream_execute", |backend| {
backend.run_command_streaming_with_options(cmd, &options)
})
}
pub fn execute_with_env(
&mut self,
cmd: &[String],
env: HashMap<String, String>,
) -> Result<GuestCommandResult, MicrovmError> {
self.execute_with_options(
cmd,
GuestExecOptions {
env,
timeout: None,
cwd: None,
},
)
}
pub fn execute_with_timeout(
&mut self,
cmd: &[String],
timeout: Duration,
) -> Result<GuestCommandResult, MicrovmError> {
self.execute_with_options(
cmd,
GuestExecOptions {
env: HashMap::new(),
timeout: Some(timeout),
cwd: None,
},
)
}
pub fn execute_with_options(
&mut self,
cmd: &[String],
options: GuestExecOptions,
) -> Result<GuestCommandResult, MicrovmError> {
if cmd.is_empty() {
return Err(MicrovmError::InvalidConfig(
"command must not be empty".into(),
));
}
self.with_ready_state("execute", |backend| {
backend.run_command_with_options(cmd, &options)
})
}
pub fn http_request(&mut self, request: HttpRequest) -> Result<HttpResponse, MicrovmError> {
self.with_ready_state("http_request", |backend| backend.http_request(request))
}
}
impl Sandbox for MicrovmSandbox {
fn new(config: SandboxConfig) -> Result<Self, SandboxError> {
Self::new_with_base(config, MicrovmConfig::default()).map_err(Into::into)
}
fn execute(&mut self, cmd: &[String]) -> Result<SandboxResult, SandboxError> {
self.execute_with_options_for_sandbox(cmd, GuestExecOptions::default())
.map_err(SandboxError::from)
}
fn create_pty(
&mut self,
_config: mimobox_core::PtyConfig,
) -> Result<Box<dyn mimobox_core::PtySession>, SandboxError> {
Err(SandboxError::UnsupportedOperation(
"PTY sessions currently only support OS-level backend, microVM not supported"
.to_string(),
))
}
fn read_file(&mut self, path: &str) -> Result<Vec<u8>, SandboxError> {
MicrovmSandbox::read_file(self, path).map_err(SandboxError::from)
}
fn write_file(&mut self, path: &str, data: &[u8]) -> Result<(), SandboxError> {
MicrovmSandbox::write_file(self, path, data).map_err(SandboxError::from)
}
fn list_dir(&mut self, path: &str) -> Result<Vec<DirEntry>, SandboxError> {
MicrovmSandbox::list_dir(self, path).map_err(SandboxError::from)
}
fn file_exists(&mut self, path: &str) -> Result<bool, SandboxError> {
MicrovmSandbox::file_exists(self, path).map_err(SandboxError::from)
}
fn remove_file(&mut self, path: &str) -> Result<(), SandboxError> {
MicrovmSandbox::remove_file(self, path).map_err(SandboxError::from)
}
fn rename(&mut self, from: &str, to: &str) -> Result<(), SandboxError> {
MicrovmSandbox::rename(self, from, to).map_err(SandboxError::from)
}
fn stat(&mut self, path: &str) -> Result<FileStat, SandboxError> {
MicrovmSandbox::stat(self, path).map_err(SandboxError::from)
}
fn snapshot(&mut self) -> Result<SandboxSnapshot, SandboxError> {
MicrovmSandbox::snapshot(self).map_err(SandboxError::from)
}
fn fork(&mut self) -> Result<Self, SandboxError> {
MicrovmSandbox::fork(self).map_err(SandboxError::from)
}
fn destroy(self) -> Result<(), SandboxError> {
let mut this = self;
this.backend.shutdown().map_err(SandboxError::from)?;
this.state = MicrovmState::Destroyed;
Ok(())
}
}
fn map_snapshot_access_error(error: SandboxError) -> MicrovmError {
match error {
SandboxError::Io(error) => MicrovmError::Io(error),
SandboxError::InvalidSnapshot => {
MicrovmError::SnapshotFormat("invalid sandbox snapshot".into())
}
other => MicrovmError::SnapshotFormat(other.to_string()),
}
}
impl MicrovmSandbox {
fn execute_with_options_for_sandbox(
&mut self,
cmd: &[String],
options: GuestExecOptions,
) -> Result<SandboxResult, MicrovmError> {
if cmd.is_empty() {
return Err(MicrovmError::InvalidConfig(
"command must not be empty".into(),
));
}
self.with_ready_state("execute", |backend| {
let start = Instant::now();
let guest = backend.run_command_with_options(cmd, &options)?;
Ok(SandboxResult {
stdout: guest.stdout,
stderr: guest.stderr,
exit_code: guest.exit_code,
elapsed: start.elapsed(),
timed_out: guest.timed_out,
})
})
}
}