a3s-box-runtime 3.2.3

MicroVM runtime engine — VM lifecycle, OCI images, attestation, networking
Documentation
use std::io::Write;
use std::path::PathBuf;
use std::sync::Mutex;

use super::*;

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub(super) struct DurableFixtureState {
    pub(super) record: ContainerRecord,
    pub(super) create_operation: String,
    pub(super) start_operation: Option<String>,
    pub(super) exit_status: Option<ExitStatus>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub(super) process: Option<DurableFixtureProcess>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub(super) struct DurableFixtureProcess {
    pub(super) request: OciExecRequest,
    pub(super) record: ProcessRecord,
    pub(super) output: Vec<OutputChunk>,
    pub(super) exit_status: Option<ExitStatus>,
    pub(super) stdin_operations: BTreeMap<String, WriteStdinRequest>,
    pub(super) close_stdin_operations: BTreeMap<String, CloseStdinRequest>,
    pub(super) signal_operations: BTreeMap<String, SignalProcessRequest>,
}

pub(super) struct DurableFixtureService {
    pub(super) state_path: PathBuf,
    pub(super) call_log: PathBuf,
    pub(super) lock: Mutex<()>,
}

impl DurableFixtureService {
    pub(super) fn new(state_path: PathBuf, call_log: PathBuf) -> Self {
        Self {
            state_path,
            call_log,
            lock: Mutex::new(()),
        }
    }

    pub(super) fn load(&self, operation: &str) -> OciResult<Option<DurableFixtureState>> {
        match std::fs::read(&self.state_path) {
            Ok(bytes) => serde_json::from_slice(&bytes).map(Some).map_err(|error| {
                oci_error(
                    ErrorCode::Internal,
                    operation,
                    format!(
                        "failed to decode durable process fixture {}: {error}",
                        self.state_path.display()
                    ),
                )
            }),
            Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(None),
            Err(error) => Err(oci_error(
                ErrorCode::Internal,
                operation,
                format!(
                    "failed to read durable process fixture {}: {error}",
                    self.state_path.display()
                ),
            )),
        }
    }

    pub(super) fn store(&self, state: &DurableFixtureState, operation: &str) -> OciResult<()> {
        let bytes = serde_json::to_vec(state).map_err(|error| {
            oci_error(
                ErrorCode::Internal,
                operation,
                format!("failed to encode durable process fixture: {error}"),
            )
        })?;
        let mut file = std::fs::OpenOptions::new()
            .create(true)
            .truncate(true)
            .write(true)
            .open(&self.state_path)
            .map_err(|error| {
                oci_error(
                    ErrorCode::Internal,
                    operation,
                    format!(
                        "failed to open durable process fixture {}: {error}",
                        self.state_path.display()
                    ),
                )
            })?;
        file.write_all(&bytes).map_err(|error| {
            oci_error(
                ErrorCode::Internal,
                operation,
                format!("failed to write durable process fixture: {error}"),
            )
        })?;
        file.sync_all().map_err(|error| {
            oci_error(
                ErrorCode::Internal,
                operation,
                format!("failed to sync durable process fixture: {error}"),
            )
        })
    }

    pub(super) fn append_call(&self, operation: &'static str) -> OciResult<()> {
        let mut file = std::fs::OpenOptions::new()
            .create(true)
            .append(true)
            .open(&self.call_log)
            .map_err(|error| {
                oci_error(
                    ErrorCode::Internal,
                    operation,
                    format!(
                        "failed to open process fixture call log {}: {error}",
                        self.call_log.display()
                    ),
                )
            })?;
        writeln!(file, "{operation}").map_err(|error| {
            oci_error(
                ErrorCode::Internal,
                operation,
                format!("failed to append process fixture call log: {error}"),
            )
        })?;
        file.sync_all().map_err(|error| {
            oci_error(
                ErrorCode::Internal,
                operation,
                format!("failed to sync process fixture call log: {error}"),
            )
        })
    }

    pub(super) fn rewritten_record(
        state: &DurableFixtureState,
        status: ContainerState,
    ) -> OciResult<ContainerRecord> {
        let id = ContainerId::new(state.record.state.id().to_string())?;
        runtime_record(
            &id,
            state.record.generation,
            status,
            state.record.driver,
            state.record.isolation,
            &state.record.config_digest,
            state.record.attachments_digest.as_deref(),
        )
    }
}