2022-12-17 18:31:09 -05:00
|
|
|
use serde::de::IntoDeserializer;
|
2022-12-17 17:55:38 -05:00
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
2022-11-26 08:55:15 -05:00
|
|
|
use std::fmt;
|
2022-11-26 16:23:00 -05:00
|
|
|
use std::fs;
|
2022-12-17 17:55:38 -05:00
|
|
|
use std::path::{Path, PathBuf};
|
2022-11-26 08:55:15 -05:00
|
|
|
|
2022-12-17 18:31:09 -05:00
|
|
|
use anyhow::{anyhow, Context, Result};
|
|
|
|
use bootspec::generation::Generation as BootspecGeneration;
|
2022-12-17 17:55:38 -05:00
|
|
|
use bootspec::BootJson;
|
|
|
|
use bootspec::SpecialisationName;
|
|
|
|
|
2022-12-17 18:31:09 -05:00
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
2022-12-17 17:55:38 -05:00
|
|
|
pub struct SecureBootExtension {
|
2022-12-17 18:31:09 -05:00
|
|
|
#[serde(rename = "osRelease")]
|
|
|
|
pub os_release: PathBuf,
|
2022-12-17 17:55:38 -05:00
|
|
|
}
|
2022-11-26 08:55:15 -05:00
|
|
|
|
2022-12-17 18:31:09 -05:00
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
pub struct ExtendedBootJson {
|
|
|
|
pub bootspec: BootJson,
|
|
|
|
pub extensions: SecureBootExtension,
|
|
|
|
}
|
2022-11-26 16:23:00 -05:00
|
|
|
|
2022-11-26 08:55:15 -05:00
|
|
|
#[derive(Debug)]
|
2022-12-17 18:31:09 -05:00
|
|
|
pub struct Generation {
|
|
|
|
/// Profile symlink index
|
2022-11-26 16:23:00 -05:00
|
|
|
version: u64,
|
2022-12-17 17:55:38 -05:00
|
|
|
/// Top-level specialisation name
|
|
|
|
specialisation_name: Option<SpecialisationName>,
|
2022-12-17 18:31:09 -05:00
|
|
|
/// Top-level extended boot specification
|
|
|
|
pub spec: ExtendedBootJson,
|
2022-12-17 17:55:38 -05:00
|
|
|
}
|
|
|
|
|
2022-12-17 18:31:09 -05:00
|
|
|
impl Generation {
|
|
|
|
fn extract_extensions(bootspec: &BootJson) -> Result<SecureBootExtension> {
|
|
|
|
Ok(Deserialize::deserialize(
|
|
|
|
bootspec.extensions.get("lanzaboote")
|
|
|
|
.context("Failed to extract Lanzaboote-specific extension from Bootspec, missing lanzaboote field in `extensions`")?
|
|
|
|
.clone()
|
|
|
|
.into_deserializer()
|
|
|
|
)?)
|
|
|
|
}
|
2022-11-26 08:55:15 -05:00
|
|
|
|
|
|
|
pub fn from_toplevel(toplevel: impl AsRef<Path>) -> Result<Self> {
|
2022-12-17 18:31:09 -05:00
|
|
|
let bootspec_path = toplevel.as_ref().join("boot.json");
|
|
|
|
let generation: BootspecGeneration = serde_json::from_slice(
|
2022-11-26 17:19:08 -05:00
|
|
|
&fs::read(bootspec_path).context("Failed to read bootspec file")?,
|
2022-11-26 16:23:00 -05:00
|
|
|
)
|
|
|
|
.context("Failed to parse bootspec json")?;
|
|
|
|
|
2022-12-17 18:31:09 -05:00
|
|
|
let bootspec: BootJson = generation
|
|
|
|
.try_into()
|
|
|
|
.map_err(|err: &'static str| anyhow!(err))?;
|
|
|
|
|
|
|
|
let extensions = Self::extract_extensions(&bootspec)?;
|
|
|
|
|
2022-11-26 16:23:00 -05:00
|
|
|
Ok(Self {
|
|
|
|
version: parse_version(toplevel)?,
|
2022-11-27 05:19:02 -05:00
|
|
|
specialisation_name: None,
|
2022-12-17 18:31:09 -05:00
|
|
|
spec: ExtendedBootJson {
|
|
|
|
bootspec,
|
|
|
|
extensions,
|
|
|
|
},
|
2022-11-26 16:23:00 -05:00
|
|
|
})
|
2022-11-26 08:55:15 -05:00
|
|
|
}
|
2022-11-27 05:19:02 -05:00
|
|
|
|
2022-12-17 18:31:09 -05:00
|
|
|
pub fn specialise(&self, name: &SpecialisationName, bootspec: &BootJson) -> Result<Self> {
|
|
|
|
Ok(Self {
|
2022-11-27 05:19:02 -05:00
|
|
|
version: self.version,
|
2022-12-17 17:55:38 -05:00
|
|
|
specialisation_name: Some(name.clone()),
|
2022-12-17 18:31:09 -05:00
|
|
|
spec: ExtendedBootJson {
|
|
|
|
bootspec: bootspec.clone(),
|
|
|
|
extensions: Self::extract_extensions(&bootspec)?,
|
|
|
|
},
|
|
|
|
})
|
2022-11-27 05:19:02 -05:00
|
|
|
}
|
|
|
|
|
2022-12-17 17:55:38 -05:00
|
|
|
pub fn is_specialized(&self) -> Option<SpecialisationName> {
|
2022-11-27 05:19:02 -05:00
|
|
|
self.specialisation_name.clone()
|
|
|
|
}
|
2022-11-26 08:55:15 -05:00
|
|
|
}
|
|
|
|
|
2022-12-17 18:31:09 -05:00
|
|
|
impl fmt::Display for Generation {
|
2022-11-26 08:55:15 -05:00
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
2022-11-26 16:23:00 -05:00
|
|
|
write!(f, "{}", self.version)
|
2022-11-26 08:55:15 -05:00
|
|
|
}
|
|
|
|
}
|
2022-11-26 16:23:00 -05:00
|
|
|
|
|
|
|
fn parse_version(toplevel: impl AsRef<Path>) -> Result<u64> {
|
2022-11-26 17:19:08 -05:00
|
|
|
let file_name = toplevel
|
|
|
|
.as_ref()
|
|
|
|
.file_name()
|
|
|
|
.ok_or_else(|| anyhow::anyhow!("Failed to extract file name from generation"))?;
|
2022-11-26 16:23:00 -05:00
|
|
|
|
|
|
|
let file_name_str = file_name
|
|
|
|
.to_str()
|
|
|
|
.with_context(|| "Failed to convert file name of generation to string")?;
|
|
|
|
|
|
|
|
let generation_version = file_name_str
|
2022-11-26 17:19:08 -05:00
|
|
|
.split('-')
|
2022-11-26 16:23:00 -05:00
|
|
|
.nth(1)
|
2022-11-26 17:19:08 -05:00
|
|
|
.ok_or_else(|| anyhow::anyhow!("Failed to extract version from generation"))?;
|
2022-11-26 16:23:00 -05:00
|
|
|
|
|
|
|
let parsed_generation_version = generation_version
|
|
|
|
.parse()
|
|
|
|
.with_context(|| format!("Failed to parse generation version: {}", generation_version))?;
|
|
|
|
|
|
|
|
Ok(parsed_generation_version)
|
|
|
|
}
|