2023-01-25 17:39:46 -05:00
|
|
|
use std::ffi::CStr;
|
|
|
|
use std::fs;
|
|
|
|
use std::path::Path;
|
|
|
|
use std::str::FromStr;
|
|
|
|
|
|
|
|
use anyhow::{Context, Result};
|
|
|
|
|
2023-07-22 14:49:46 -04:00
|
|
|
use lanzaboote_tool::os_release::OsRelease;
|
|
|
|
use lanzaboote_tool::pe;
|
2023-01-25 17:39:46 -05:00
|
|
|
|
|
|
|
/// A systemd version.
|
|
|
|
///
|
2023-11-22 13:20:38 -05:00
|
|
|
/// systemd does not follow semver standards, but we try to map it anyway. Version components that are not there are treated as zero.
|
|
|
|
///
|
|
|
|
/// A notible quirk here is our handling of release candidate
|
|
|
|
/// versions. We treat 255-rc2 as 255.-1.2, which should give us the
|
|
|
|
/// correct ordering.
|
2023-01-25 17:39:46 -05:00
|
|
|
#[derive(PartialEq, PartialOrd, Eq, Debug)]
|
2023-11-22 13:20:38 -05:00
|
|
|
pub struct SystemdVersion {
|
|
|
|
major: u32,
|
|
|
|
|
|
|
|
/// This is a signed integer, so we can model "rc" versions as -1 here.
|
|
|
|
minor: i32,
|
|
|
|
|
|
|
|
patch: u32,
|
|
|
|
}
|
2023-01-25 17:39:46 -05:00
|
|
|
|
|
|
|
impl SystemdVersion {
|
|
|
|
/// Read the systemd version from the `.osrel` section of a systemd-boot binary.
|
|
|
|
pub fn from_systemd_boot_binary(path: &Path) -> Result<Self> {
|
|
|
|
let file_data = fs::read(path).with_context(|| format!("Failed to read file {path:?}"))?;
|
|
|
|
let section_data = pe::read_section_data(&file_data, ".osrel")
|
|
|
|
.with_context(|| format!("PE section '.osrel' is empty: {path:?}"))?;
|
|
|
|
|
|
|
|
// The `.osrel` section in the systemd-boot binary is a NUL terminated string and thus needs
|
|
|
|
// special handling.
|
|
|
|
let section_data_cstr =
|
|
|
|
CStr::from_bytes_with_nul(section_data).context("Failed to parse C string.")?;
|
|
|
|
let section_data_string = section_data_cstr
|
|
|
|
.to_str()
|
|
|
|
.context("Failed to convert C string to Rust string.")?;
|
|
|
|
|
|
|
|
let os_release = OsRelease::from_str(section_data_string)
|
|
|
|
.with_context(|| format!("Failed to parse os-release from {section_data_string}"))?;
|
|
|
|
|
|
|
|
let version_str = os_release
|
|
|
|
.0
|
|
|
|
.get("VERSION")
|
|
|
|
.context("Failed to extract VERSION key from: {os_release:#?}")?;
|
|
|
|
|
|
|
|
Self::from_str(version_str)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl FromStr for SystemdVersion {
|
|
|
|
type Err = anyhow::Error;
|
|
|
|
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
|
|
let split_version = s
|
|
|
|
.split('.')
|
|
|
|
.take(2)
|
|
|
|
.map(u32::from_str)
|
|
|
|
.collect::<Result<Vec<u32>, std::num::ParseIntError>>()
|
|
|
|
.context("Failed to parse version string into u32 vector.")?;
|
|
|
|
|
|
|
|
let major = split_version
|
|
|
|
.first()
|
2023-11-22 13:20:38 -05:00
|
|
|
.copied()
|
2023-01-25 17:39:46 -05:00
|
|
|
.context("Failed to parse major version.")?;
|
2023-11-22 13:20:38 -05:00
|
|
|
let minor = split_version
|
|
|
|
.get(1)
|
|
|
|
.copied()
|
|
|
|
.unwrap_or(0)
|
|
|
|
.try_into()
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
Ok(Self {
|
|
|
|
major,
|
|
|
|
minor,
|
|
|
|
patch: 0,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2023-01-25 17:39:46 -05:00
|
|
|
|
2023-11-22 13:20:38 -05:00
|
|
|
#[cfg(test)]
|
|
|
|
impl From<(u32, i32, u32)> for SystemdVersion {
|
|
|
|
fn from(value: (u32, i32, u32)) -> Self {
|
|
|
|
SystemdVersion {
|
|
|
|
major: value.0,
|
|
|
|
minor: value.1,
|
|
|
|
patch: value.2,
|
|
|
|
}
|
2023-01-25 17:39:46 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn parse_version_correctly() {
|
2023-11-22 13:20:38 -05:00
|
|
|
assert_eq!(parse_version("253"), (253, 0, 0).into());
|
|
|
|
assert_eq!(parse_version("252.4"), (252, 4, 0).into());
|
|
|
|
assert_eq!(parse_version("251.11"), (251, 11, 0).into());
|
2023-01-25 17:39:46 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn compare_version_correctly() {
|
|
|
|
assert!(parse_version("253") > parse_version("252"));
|
|
|
|
assert!(parse_version("253") > parse_version("252.4"));
|
|
|
|
assert!(parse_version("251.8") == parse_version("251.8"));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn fail_to_parse_version() {
|
|
|
|
parse_version_error("");
|
|
|
|
parse_version_error("213;k;13");
|
|
|
|
parse_version_error("-1.3.123");
|
|
|
|
parse_version_error("253-rc1");
|
|
|
|
}
|
|
|
|
|
|
|
|
fn parse_version(input: &str) -> SystemdVersion {
|
|
|
|
SystemdVersion::from_str(input).unwrap()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn parse_version_error(input: &str) {
|
|
|
|
assert!(SystemdVersion::from_str(input).is_err());
|
|
|
|
}
|
|
|
|
}
|