1
0
mirror of https://github.com/containers/bootc.git synced 2026-02-05 15:45:53 +01:00

composefs/boot/bls: Handle duplicate VMLinuz + Initrd

If two deployments have the same VMLinuz + Initrd then, we can use the
same binaries for both the deployments.

Before writing the BLS entries to disk we calculate the SHA256Sum
of VMLinuz + Initrd combo, then test if any other deployment has the
same SHA256Sum for the binaries. Store the hash in the origin file under
`boot -> hash` for future lookups.

Signed-off-by: Johan-Liebert1 <pragyanpoudyal41999@gmail.com>
This commit is contained in:
Johan-Liebert1
2025-07-28 15:04:07 +05:30
committed by Pragyan Poudyal
parent 0ca770caf2
commit 4e62bdf015
2 changed files with 202 additions and 52 deletions

View File

@@ -957,13 +957,29 @@ async fn upgrade_composefs(_opts: UpgradeOpts) -> Result<()> {
};
let boot_type = BootType::from(&entry);
let mut boot_digest = None;
match boot_type {
BootType::Bls => setup_composefs_bls_boot(BootSetupType::Upgrade, repo, &id, entry),
BootType::Uki => setup_composefs_uki_boot(BootSetupType::Upgrade, repo, &id, entry),
}?;
BootType::Bls => {
boot_digest = Some(setup_composefs_bls_boot(
BootSetupType::Upgrade,
repo,
&id,
entry,
)?)
}
write_composefs_state(&Utf8PathBuf::from("/sysroot"), id, imgref, true, boot_type)?;
BootType::Uki => setup_composefs_uki_boot(BootSetupType::Upgrade, repo, &id, entry)?,
};
write_composefs_state(
&Utf8PathBuf::from("/sysroot"),
id,
imgref,
true,
boot_type,
boot_digest,
)?;
Ok(())
}
@@ -1125,11 +1141,19 @@ async fn switch_composefs(opts: SwitchOpts) -> Result<()> {
};
let boot_type = BootType::from(&entry);
let mut boot_digest = None;
match boot_type {
BootType::Bls => setup_composefs_bls_boot(BootSetupType::Upgrade, repo, &id, entry),
BootType::Uki => setup_composefs_uki_boot(BootSetupType::Upgrade, repo, &id, entry),
}?;
BootType::Bls => {
boot_digest = Some(setup_composefs_bls_boot(
BootSetupType::Upgrade,
repo,
&id,
entry,
)?)
}
BootType::Uki => setup_composefs_uki_boot(BootSetupType::Upgrade, repo, &id, entry)?,
};
write_composefs_state(
&Utf8PathBuf::from("/sysroot"),
@@ -1137,6 +1161,7 @@ async fn switch_composefs(opts: SwitchOpts) -> Result<()> {
&target_imgref,
true,
boot_type,
boot_digest,
)?;
Ok(())

View File

@@ -50,6 +50,7 @@ use ostree_ext::composefs::{
repository::Repository as ComposefsRepository,
util::Sha256Digest,
};
use ostree_ext::composefs_boot::bootloader::UsrLibModulesVmlinuz;
use ostree_ext::composefs_boot::{
bootloader::BootEntry as ComposefsBootEntry, cmdline::get_cmdline_composefs, uki, BootOps,
};
@@ -1586,6 +1587,133 @@ pub(crate) enum BootSetupType<'a> {
Upgrade,
}
/// Compute SHA256Sum of VMlinuz + Initrd
///
/// # Arguments
/// * entry - BootEntry containing VMlinuz and Initrd
/// * repo - The composefs repository
#[context("Computing boot digest")]
fn compute_boot_digest(
entry: &UsrLibModulesVmlinuz<Sha256HashValue>,
repo: &ComposefsRepository<Sha256HashValue>,
) -> Result<String> {
let vmlinuz = read_file(&entry.vmlinuz, &repo).context("Reading vmlinuz")?;
let Some(initramfs) = &entry.initramfs else {
anyhow::bail!("initramfs not found");
};
let initramfs = read_file(initramfs, &repo).context("Reading intird")?;
let mut hasher = openssl::hash::Hasher::new(openssl::hash::MessageDigest::sha256())
.context("Creating hasher")?;
hasher.update(&vmlinuz).context("hashing vmlinuz")?;
hasher.update(&initramfs).context("hashing initrd")?;
let digest: &[u8] = &hasher.finish().context("Finishing digest")?;
return Ok(hex::encode(digest));
}
/// Given the SHA256 sum of current VMlinuz + Initrd combo, find boot entry with the same SHA256Sum
///
/// # Returns
/// Returns the verity of the deployment that has a boot digest same as the one passed in
#[context("Checking boot entry duplicates")]
fn find_vmlinuz_initrd_duplicates(digest: &str) -> Result<Option<String>> {
let deployments =
cap_std::fs::Dir::open_ambient_dir(STATE_DIR_ABS, cap_std::ambient_authority());
let deployments = match deployments {
Ok(d) => d,
// The first ever deployment
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(None),
Err(e) => anyhow::bail!(e),
};
let mut symlink_to: Option<String> = None;
for depl in deployments.entries()? {
let depl = depl?;
let depl_file_name = depl.file_name();
let depl_file_name = depl_file_name.as_str()?;
let config = depl
.open_dir()
.with_context(|| format!("Opening {depl_file_name}"))?
.read_to_string(format!("{depl_file_name}.origin"))
.context("Reading origin file")?;
let ini = tini::Ini::from_string(&config)
.with_context(|| format!("Failed to parse file {depl_file_name}.origin as ini"))?;
match ini.get::<String>(ORIGIN_KEY_BOOT, ORIGIN_KEY_BOOT_DIGEST) {
Some(hash) => {
if hash == digest {
symlink_to = Some(depl_file_name.to_string());
break;
}
}
// No SHASum recorded in origin file
// `symlink_to` is already none, but being explicit here
None => symlink_to = None,
};
}
Ok(symlink_to)
}
#[context("Writing BLS entries to disk")]
fn write_bls_boot_entries_to_disk(
boot_dir: &Utf8PathBuf,
deployment_id: &Sha256HashValue,
entry: &UsrLibModulesVmlinuz<Sha256HashValue>,
repo: &ComposefsRepository<Sha256HashValue>,
) -> Result<()> {
let id_hex = deployment_id.to_hex();
// Write the initrd and vmlinuz at /boot/<id>/
let path = boot_dir.join(&id_hex);
create_dir_all(&path)?;
let entries_dir = cap_std::fs::Dir::open_ambient_dir(&path, cap_std::ambient_authority())
.with_context(|| format!("Opening {path}"))?;
entries_dir
.atomic_write(
"vmlinuz",
read_file(&entry.vmlinuz, &repo).context("Reading vmlinuz")?,
)
.context("Writing vmlinuz to path")?;
let Some(initramfs) = &entry.initramfs else {
anyhow::bail!("initramfs not found");
};
entries_dir
.atomic_write(
"initrd",
read_file(initramfs, &repo).context("Reading initrd")?,
)
.context("Writing initrd to path")?;
// Can't call fsync on O_PATH fds, so re-open it as a non O_PATH fd
let owned_fd = entries_dir
.reopen_as_ownedfd()
.context("Reopen as owned fd")?;
rustix::fs::fsync(owned_fd).context("fsync")?;
Ok(())
}
/// Sets up and writes BLS entries and binaries (VMLinuz + Initrd) to disk
///
/// # Returns
/// Returns the SHA256Sum of VMLinuz + Initrd combo. Error if any
#[context("Setting up BLS boot")]
pub(crate) fn setup_composefs_bls_boot(
setup_type: BootSetupType,
@@ -1593,7 +1721,7 @@ pub(crate) fn setup_composefs_bls_boot(
repo: ComposefsRepository<Sha256HashValue>,
id: &Sha256HashValue,
entry: ComposefsBootEntry<Sha256HashValue>,
) -> Result<()> {
) -> Result<String> {
let id_hex = id.to_hex();
let (root_path, cmdline_refs) = match setup_type {
@@ -1625,59 +1753,38 @@ pub(crate) fn setup_composefs_bls_boot(
};
let boot_dir = root_path.join("boot");
let is_upgrade = matches!(setup_type, BootSetupType::Upgrade);
let bls_config = match &entry {
let (bls_config, boot_digest) = match &entry {
ComposefsBootEntry::Type1(..) => unimplemented!(),
ComposefsBootEntry::Type2(..) => unimplemented!(),
ComposefsBootEntry::UsrLibModulesUki(..) => unimplemented!(),
ComposefsBootEntry::UsrLibModulesVmLinuz(usr_lib_modules_vmlinuz) => {
// Write the initrd and vmlinuz at /boot/<id>/
let path = boot_dir.join(&id_hex);
create_dir_all(&path)?;
let boot_digest = compute_boot_digest(usr_lib_modules_vmlinuz, &repo)
.context("Computing boot digest")?;
let entries_dir =
cap_std::fs::Dir::open_ambient_dir(&path, cap_std::ambient_authority())
.with_context(|| format!("Opening {path}"))?;
entries_dir
.atomic_write(
"vmlinuz",
read_file(&usr_lib_modules_vmlinuz.vmlinuz, &repo)
.context("Reading vmlinuz")?,
)
.context("Writing vmlinuz to path")?;
if let Some(initramfs) = &usr_lib_modules_vmlinuz.initramfs {
entries_dir
.atomic_write(
"initrd",
read_file(initramfs, &repo).context("Reading initrd")?,
)
.context("Writing initrd to path")?;
} else {
anyhow::bail!("initramfs not found");
};
// Can't call fsync on O_PATH fds, so re-open it as a non O_PATH fd
let owned_fd = entries_dir
.reopen_as_ownedfd()
.context("Reopen as owned fd")?;
rustix::fs::fsync(owned_fd).context("fsync")?;
BLSConfig {
let mut bls_config = BLSConfig {
title: Some(id_hex.clone()),
version: 1,
linux: format!("/boot/{id_hex}/vmlinuz"),
initrd: format!("/boot/{id_hex}/initrd"),
options: cmdline_refs,
extra: HashMap::new(),
};
if let Some(symlink_to) = find_vmlinuz_initrd_duplicates(&boot_digest)? {
bls_config.linux = format!("/boot/{symlink_to}/vmlinuz");
bls_config.initrd = format!("/boot/{symlink_to}/initrd");
} else {
write_bls_boot_entries_to_disk(&boot_dir, id, usr_lib_modules_vmlinuz, &repo)?;
}
(bls_config, boot_digest)
}
};
let (entries_path, booted_bls) = if matches!(setup_type, BootSetupType::Upgrade) {
let (entries_path, booted_bls) = if is_upgrade {
let mut booted_bls = get_booted_bls()?;
booted_bls.version = 0; // entries are sorted by their filename in reverse order
@@ -1710,7 +1817,7 @@ pub(crate) fn setup_composefs_bls_boot(
.context("Reopening as owned fd")?;
rustix::fs::fsync(owned_loader_entries_fd).context("fsync")?;
Ok(())
Ok(boot_digest)
}
pub fn get_esp_partition(device: &str) -> Result<(String, Option<String>)> {
@@ -2009,14 +2116,19 @@ fn setup_composefs_boot(root_setup: &RootSetup, state: &State, image_id: &str) -
};
let boot_type = BootType::from(&entry);
let mut boot_digest: Option<String> = None;
match boot_type {
BootType::Bls => setup_composefs_bls_boot(
BootSetupType::Setup((&root_setup, &state)),
repo,
&id,
entry,
)?,
BootType::Bls => {
let digest = setup_composefs_bls_boot(
BootSetupType::Setup((&root_setup, &state)),
repo,
&id,
entry,
)?;
boot_digest = Some(digest);
}
BootType::Uki => setup_composefs_uki_boot(
BootSetupType::Setup((&root_setup, &state)),
repo,
@@ -2035,6 +2147,7 @@ fn setup_composefs_boot(root_setup: &RootSetup, state: &State, image_id: &str) -
},
false,
boot_type,
boot_digest,
)?;
Ok(())
@@ -2043,11 +2156,16 @@ fn setup_composefs_boot(root_setup: &RootSetup, state: &State, image_id: &str) -
pub(crate) const COMPOSEFS_TRANSIENT_STATE_DIR: &str = "/run/composefs";
/// File created in /run/composefs to record a staged-deployment
pub(crate) const COMPOSEFS_STAGED_DEPLOYMENT_FNAME: &str = "staged-deployment";
/// Relative to /sysroot
/// Absolute path to composefs-native state directory
pub(crate) const STATE_DIR_ABS: &str = "/sysroot/state/deploy";
/// Relative path to composefs-native state directory. Relative to /sysroot
pub(crate) const STATE_DIR_RELATIVE: &str = "state/deploy";
pub(crate) const ORIGIN_KEY_BOOT: &str = "boot";
pub(crate) const ORIGIN_KEY_BOOT_TYPE: &str = "boot_type";
/// Key to store the SHA256 sum of vmlinuz + initrd for a deployment
pub(crate) const ORIGIN_KEY_BOOT_DIGEST: &str = "digest";
/// Creates and populates /sysroot/state/deploy/image_id
#[context("Writing composefs state")]
@@ -2057,6 +2175,7 @@ pub(crate) fn write_composefs_state(
imgref: &ImageReference,
staged: bool,
boot_type: BootType,
boot_digest: Option<String>,
) -> Result<()> {
let state_path = root_path.join(format!("{STATE_DIR_RELATIVE}/{}", deployment_id.to_hex()));
@@ -2084,6 +2203,12 @@ pub(crate) fn write_composefs_state(
.section(ORIGIN_KEY_BOOT)
.item(ORIGIN_KEY_BOOT_TYPE, boot_type);
if let Some(boot_digest) = boot_digest {
config = config
.section(ORIGIN_KEY_BOOT)
.item(ORIGIN_KEY_BOOT_DIGEST, boot_digest);
}
let state_dir = cap_std::fs::Dir::open_ambient_dir(&state_path, cap_std::ambient_authority())
.context("Opening state dir")?;