use std::fs::File;
use std::io::{self, Read};
use std::path::Path;
use microsandbox_image::checkpoint::{
DeviceStateRef, DiskGenerationManifest, LocalObjectStore, ObjectId, ResourceDescriptor,
};
use serde::{Deserialize, Serialize};
use super::local_memory::LocalMemory;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct LocalBranchState {
pub id: String,
pub architecture: String,
pub pause_generation: u64,
pub execution_state: ObjectId,
pub devices: Vec<DeviceStateRef>,
pub resources: Vec<ResourceDescriptor>,
pub disks: Vec<DiskGenerationManifest>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub owned_volumes: Vec<microsandbox_image::snapshot::OwnedVolumeCapture>,
pub memory: LocalMemory,
pub vcpus: u8,
pub max_cpus: u8,
pub memory_mib: u32,
pub max_memory_mib: u32,
}
impl LocalBranchState {
pub fn open(root: &Path) -> io::Result<Self> {
let bytes = read_bounded(&root.join("branch.json"), 16 * 1024 * 1024)?;
let state: Self = serde_json::from_slice(&bytes).map_err(io::Error::other)?;
if state.architecture != std::env::consts::ARCH {
return Err(io::Error::other("branch architecture differs"));
}
for disk in &state.disks {
disk.validate().map_err(io::Error::other)?;
if disk.pause_generation != state.pause_generation {
return Err(io::Error::other(
"branch disk belongs to a different pause epoch",
));
}
}
state.validate_files(root)?;
microsandbox_image::snapshot::validate_owned_volumes(&state.owned_volumes)
.map_err(io::Error::other)?;
microsandbox_image::snapshot::validate_owned_resources(
&state.owned_volumes,
&state.resources,
)
.map_err(io::Error::other)?;
for volume in &state.owned_volumes {
if let microsandbox_image::snapshot::OwnedVolumeData::Disk { generation } = &volume.data
&& (generation.pause_generation != state.pause_generation
|| !state.disks.contains(generation))
{
return Err(io::Error::other(
"owned disk is absent from the branch capture epoch",
));
}
}
microsandbox_image::snapshot::verify_owned_directory_payloads(root, &state.owned_volumes)
.map_err(io::Error::other)?;
Ok(state)
}
pub fn validate_files(&self, root: &Path) -> io::Result<()> {
for disk in &self.disks {
for layer in &disk.layers {
let path = root
.join("layers")
.join(format!("{}.{}", layer.layer_id, layer.format));
let metadata = std::fs::symlink_metadata(path)?;
if !metadata.is_file() || metadata.len() != layer.file_size {
return Err(io::Error::other("branch disk file type or length differs"));
}
}
}
Ok(())
}
pub fn read_object(root: &Path, id: &ObjectId, limit: u64) -> io::Result<Vec<u8>> {
let store = LocalObjectStore::open(root).map_err(io::Error::other)?;
let bytes = read_bounded(&store.object_path(id), limit)?;
if ObjectId::from_bytes(&bytes).map_err(io::Error::other)? != *id {
return Err(io::Error::other("branch state object differs"));
}
Ok(bytes)
}
}
fn read_bounded(path: &Path, limit: u64) -> io::Result<Vec<u8>> {
let mut bytes = Vec::new();
File::open(path)?.take(limit + 1).read_to_end(&mut bytes)?;
if bytes.len() as u64 > limit {
return Err(io::Error::other("local state exceeds size bound"));
}
Ok(bytes)
}