Skip to content

Commit 880879f

Browse files
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 <[email protected]>
1 parent 874682e commit 880879f

File tree

2 files changed

+203
-52
lines changed

2 files changed

+203
-52
lines changed

crates/lib/src/cli.rs

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -798,13 +798,29 @@ async fn upgrade_composefs(_opts: UpgradeOpts) -> Result<()> {
798798
};
799799

800800
let boot_type = BootType::from(&entry);
801+
let mut boot_digest = None;
801802

802803
match boot_type {
803-
BootType::Bls => setup_composefs_bls_boot(BootSetupType::Upgrade, repo, &id, entry),
804-
BootType::Uki => setup_composefs_uki_boot(BootSetupType::Upgrade, repo, &id, entry),
805-
}?;
804+
BootType::Bls => {
805+
boot_digest = Some(setup_composefs_bls_boot(
806+
BootSetupType::Upgrade,
807+
repo,
808+
&id,
809+
entry,
810+
)?)
811+
}
806812

807-
write_composefs_state(&Utf8PathBuf::from("/sysroot"), id, imgref, true, boot_type)?;
813+
BootType::Uki => setup_composefs_uki_boot(BootSetupType::Upgrade, repo, &id, entry)?,
814+
};
815+
816+
write_composefs_state(
817+
&Utf8PathBuf::from("/sysroot"),
818+
id,
819+
imgref,
820+
true,
821+
boot_type,
822+
boot_digest,
823+
)?;
808824

809825
Ok(())
810826
}
@@ -966,18 +982,27 @@ async fn switch_composefs(opts: SwitchOpts) -> Result<()> {
966982
};
967983

968984
let boot_type = BootType::from(&entry);
985+
let mut boot_digest = None;
969986

970987
match boot_type {
971-
BootType::Bls => setup_composefs_bls_boot(BootSetupType::Upgrade, repo, &id, entry),
972-
BootType::Uki => setup_composefs_uki_boot(BootSetupType::Upgrade, repo, &id, entry),
973-
}?;
988+
BootType::Bls => {
989+
boot_digest = Some(setup_composefs_bls_boot(
990+
BootSetupType::Upgrade,
991+
repo,
992+
&id,
993+
entry,
994+
)?)
995+
}
996+
BootType::Uki => setup_composefs_uki_boot(BootSetupType::Upgrade, repo, &id, entry)?,
997+
};
974998

975999
write_composefs_state(
9761000
&Utf8PathBuf::from("/sysroot"),
9771001
id,
9781002
&target_imgref,
9791003
true,
9801004
boot_type,
1005+
boot_digest,
9811006
)?;
9821007

9831008
Ok(())

crates/lib/src/install.rs

Lines changed: 171 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ use ostree_ext::composefs::{
4949
repository::Repository as ComposefsRepository,
5050
util::Sha256Digest,
5151
};
52+
use ostree_ext::composefs_boot::bootloader::UsrLibModulesVmlinuz;
5253
use ostree_ext::composefs_boot::{
5354
bootloader::BootEntry as ComposefsBootEntry, cmdline::get_cmdline_composefs, uki, BootOps,
5455
};
@@ -1596,14 +1597,141 @@ pub(crate) enum BootSetupType<'a> {
15961597
Upgrade,
15971598
}
15981599

1600+
/// Compute SHA256Sum of VMlinuz + Initrd
1601+
///
1602+
/// # Arguments
1603+
/// * entry - BootEntry containing VMlinuz and Initrd
1604+
/// * repo - The composefs repository
1605+
#[context("Computing boot digest")]
1606+
fn compute_boot_digest(
1607+
entry: &UsrLibModulesVmlinuz<Sha256HashValue>,
1608+
repo: &ComposefsRepository<Sha256HashValue>,
1609+
) -> Result<String> {
1610+
let vmlinuz = read_file(&entry.vmlinuz, &repo).context("Reading vmlinuz")?;
1611+
1612+
let Some(initramfs) = &entry.initramfs else {
1613+
anyhow::bail!("initramfs not found");
1614+
};
1615+
1616+
let initramfs = read_file(initramfs, &repo).context("Reading intird")?;
1617+
1618+
let mut hasher = openssl::hash::Hasher::new(openssl::hash::MessageDigest::sha256())
1619+
.context("Creating hasher")?;
1620+
1621+
hasher.update(&vmlinuz).context("hashing vmlinuz")?;
1622+
hasher.update(&initramfs).context("hashing initrd")?;
1623+
1624+
let digest: &[u8] = &hasher.finish().context("Finishing digest")?;
1625+
1626+
return Ok(hex::encode(digest));
1627+
}
1628+
1629+
/// Given the SHA256 sum of current VMlinuz + Initrd combo, find boot entry with the same SHA256Sum
1630+
///
1631+
/// # Returns
1632+
/// Returns the verity of the deployment that has a boot digest same as the one passed in
1633+
#[context("Checking boot entry duplicates")]
1634+
fn find_vmlinuz_initrd_duplicates(digest: &str) -> Result<Option<String>> {
1635+
let deployments =
1636+
cap_std::fs::Dir::open_ambient_dir(STATE_DIR_ABS, cap_std::ambient_authority());
1637+
1638+
let deployments = match deployments {
1639+
Ok(d) => d,
1640+
// The first ever deployment
1641+
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(None),
1642+
Err(e) => anyhow::bail!(e),
1643+
};
1644+
1645+
let mut symlink_to: Option<String> = None;
1646+
1647+
for depl in deployments.entries()? {
1648+
let depl = depl?;
1649+
1650+
let depl_file_name = depl.file_name();
1651+
let depl_file_name = depl_file_name.as_str()?;
1652+
1653+
let config = depl
1654+
.open_dir()
1655+
.with_context(|| format!("Opening {depl_file_name}"))?
1656+
.read_to_string(format!("{depl_file_name}.origin"))
1657+
.context("Reading origin file")?;
1658+
1659+
let ini = tini::Ini::from_string(&config)
1660+
.with_context(|| format!("Failed to parse file {depl_file_name}.origin as ini"))?;
1661+
1662+
match ini.get::<String>(ORIGIN_KEY_BOOT, ORIGIN_KEY_BOOT_DIGEST) {
1663+
Some(hash) => {
1664+
if hash == digest {
1665+
symlink_to = Some(depl_file_name.to_string());
1666+
break;
1667+
}
1668+
}
1669+
1670+
// No SHASum recorded in origin file
1671+
// `symlink_to` is already none, but being explicit here
1672+
None => symlink_to = None,
1673+
};
1674+
}
1675+
1676+
Ok(symlink_to)
1677+
}
1678+
1679+
#[context("Writing BLS entries to disk")]
1680+
fn write_bls_boot_entries_to_disk(
1681+
boot_dir: &Utf8PathBuf,
1682+
deployment_id: &Sha256HashValue,
1683+
entry: &UsrLibModulesVmlinuz<Sha256HashValue>,
1684+
repo: &ComposefsRepository<Sha256HashValue>,
1685+
) -> Result<()> {
1686+
let id_hex = deployment_id.to_hex();
1687+
1688+
// Write the initrd and vmlinuz at /boot/<id>/
1689+
let path = boot_dir.join(&id_hex);
1690+
create_dir_all(&path)?;
1691+
1692+
let entries_dir = cap_std::fs::Dir::open_ambient_dir(&path, cap_std::ambient_authority())
1693+
.with_context(|| format!("Opening {path}"))?;
1694+
1695+
entries_dir
1696+
.atomic_write(
1697+
"vmlinuz",
1698+
read_file(&entry.vmlinuz, &repo).context("Reading vmlinuz")?,
1699+
)
1700+
.context("Writing vmlinuz to path")?;
1701+
1702+
let Some(initramfs) = &entry.initramfs else {
1703+
anyhow::bail!("initramfs not found");
1704+
};
1705+
1706+
entries_dir
1707+
.atomic_write(
1708+
"initrd",
1709+
read_file(initramfs, &repo).context("Reading initrd")?,
1710+
)
1711+
.context("Writing initrd to path")?;
1712+
1713+
// Can't call fsync on O_PATH fds, so re-open it as a non O_PATH fd
1714+
let owned_fd = entries_dir
1715+
.reopen_as_ownedfd()
1716+
.context("Reopen as owned fd")?;
1717+
1718+
rustix::fs::fsync(owned_fd).context("fsync")?;
1719+
1720+
Ok(())
1721+
}
1722+
1723+
/// Sets up and writes BLS entries and binaries (VMLinuz + Initrd) to disk
1724+
///
1725+
/// # Returns
1726+
/// Returns the SHA256Sum of VMLinuz + Initrd combo. Error if any
15991727
#[context("Setting up BLS boot")]
16001728
pub(crate) fn setup_composefs_bls_boot(
16011729
setup_type: BootSetupType,
16021730
// TODO: Make this generic
16031731
repo: ComposefsRepository<Sha256HashValue>,
16041732
id: &Sha256HashValue,
16051733
entry: ComposefsBootEntry<Sha256HashValue>,
1606-
) -> Result<()> {
1734+
) -> Result<String> {
16071735
let id_hex = id.to_hex();
16081736

16091737
let (root_path, cmdline_refs) = match setup_type {
@@ -1636,58 +1764,38 @@ pub(crate) fn setup_composefs_bls_boot(
16361764

16371765
let boot_dir = root_path.join("boot");
16381766

1639-
let bls_config = match &entry {
1767+
let is_upgrade = matches!(setup_type, BootSetupType::Upgrade);
1768+
1769+
let (bls_config, boot_digest) = match &entry {
16401770
ComposefsBootEntry::Type1(..) => todo!(),
16411771
ComposefsBootEntry::Type2(..) => todo!(),
16421772
ComposefsBootEntry::UsrLibModulesUki(..) => todo!(),
16431773

16441774
ComposefsBootEntry::UsrLibModulesVmLinuz(usr_lib_modules_vmlinuz) => {
1645-
// Write the initrd and vmlinuz at /boot/<id>/
1646-
let path = boot_dir.join(&id_hex);
1647-
create_dir_all(&path)?;
1648-
1649-
let entries_dir =
1650-
cap_std::fs::Dir::open_ambient_dir(&path, cap_std::ambient_authority())
1651-
.with_context(|| format!("Opening {path}"))?;
1652-
1653-
entries_dir
1654-
.atomic_write(
1655-
"vmlinuz",
1656-
read_file(&usr_lib_modules_vmlinuz.vmlinuz, &repo)
1657-
.context("Reading vmlinuz")?,
1658-
)
1659-
.context("Writing vmlinuz to path")?;
1660-
1661-
if let Some(initramfs) = &usr_lib_modules_vmlinuz.initramfs {
1662-
entries_dir
1663-
.atomic_write(
1664-
"initrd",
1665-
read_file(initramfs, &repo).context("Reading initrd")?,
1666-
)
1667-
.context("Writing initrd to path")?;
1668-
} else {
1669-
anyhow::bail!("initramfs not found");
1670-
};
1775+
let boot_digest = compute_boot_digest(usr_lib_modules_vmlinuz, &repo)
1776+
.context("Computing boot digest")?;
16711777

1672-
// Can't call fsync on O_PATH fds, so re-open it as a non O_PATH fd
1673-
let owned_fd = entries_dir
1674-
.reopen_as_ownedfd()
1675-
.context("Reopen as owned fd")?;
1676-
1677-
rustix::fs::fsync(owned_fd).context("fsync")?;
1678-
1679-
BLSConfig {
1778+
let mut bls_config = BLSConfig {
16801779
title: Some(id_hex.clone()),
16811780
version: 1,
16821781
linux: format!("/boot/{id_hex}/vmlinuz"),
16831782
initrd: format!("/boot/{id_hex}/initrd"),
16841783
options: cmdline_refs,
16851784
extra: HashMap::new(),
1785+
};
1786+
1787+
if let Some(symlink_to) = find_vmlinuz_initrd_duplicates(&boot_digest)? {
1788+
bls_config.linux = format!("/boot/{symlink_to}/vmlinuz");
1789+
bls_config.initrd = format!("/boot/{symlink_to}/initrd");
1790+
} else {
1791+
write_bls_boot_entries_to_disk(&boot_dir, id, usr_lib_modules_vmlinuz, &repo)?;
16861792
}
1793+
1794+
(bls_config, boot_digest)
16871795
}
16881796
};
16891797

1690-
let (entries_path, booted_bls) = if matches!(setup_type, BootSetupType::Upgrade) {
1798+
let (entries_path, booted_bls) = if is_upgrade {
16911799
let mut booted_bls = get_booted_bls()?;
16921800
booted_bls.version = 0; // entries are sorted by their filename in reverse order
16931801

@@ -1720,7 +1828,7 @@ pub(crate) fn setup_composefs_bls_boot(
17201828
.context("Reopening as owned fd")?;
17211829
rustix::fs::fsync(owned_loader_entries_fd).context("fsync")?;
17221830

1723-
Ok(())
1831+
Ok(boot_digest)
17241832
}
17251833

17261834
pub fn get_esp_partition(device: &str) -> Result<(String, Option<String>)> {
@@ -2017,14 +2125,19 @@ fn setup_composefs_boot(root_setup: &RootSetup, state: &State, image_id: &str) -
20172125
};
20182126

20192127
let boot_type = BootType::from(&entry);
2128+
let mut boot_digest: Option<String> = None;
20202129

20212130
match boot_type {
2022-
BootType::Bls => setup_composefs_bls_boot(
2023-
BootSetupType::Setup((&root_setup, &state)),
2024-
repo,
2025-
&id,
2026-
entry,
2027-
)?,
2131+
BootType::Bls => {
2132+
let digest = setup_composefs_bls_boot(
2133+
BootSetupType::Setup((&root_setup, &state)),
2134+
repo,
2135+
&id,
2136+
entry,
2137+
)?;
2138+
2139+
boot_digest = Some(digest);
2140+
}
20282141
BootType::Uki => setup_composefs_uki_boot(
20292142
BootSetupType::Setup((&root_setup, &state)),
20302143
repo,
@@ -2043,6 +2156,7 @@ fn setup_composefs_boot(root_setup: &RootSetup, state: &State, image_id: &str) -
20432156
},
20442157
false,
20452158
boot_type,
2159+
boot_digest,
20462160
)?;
20472161

20482162
Ok(())
@@ -2051,11 +2165,16 @@ fn setup_composefs_boot(root_setup: &RootSetup, state: &State, image_id: &str) -
20512165
pub(crate) const COMPOSEFS_TRANSIENT_STATE_DIR: &str = "/run/composefs";
20522166
/// File created in /run/composefs to record a staged-deployment
20532167
pub(crate) const COMPOSEFS_STAGED_DEPLOYMENT_FNAME: &str = "staged-deployment";
2054-
/// Relative to /sysroot
2168+
2169+
/// Absolute path to composefs-native state directory
2170+
pub(crate) const STATE_DIR_ABS: &str = "/sysroot/state/deploy";
2171+
/// Relative path to composefs-native state directory. Relative to /sysroot
20552172
pub(crate) const STATE_DIR_RELATIVE: &str = "state/deploy";
20562173

20572174
pub(crate) const ORIGIN_KEY_BOOT: &str = "boot";
20582175
pub(crate) const ORIGIN_KEY_BOOT_TYPE: &str = "boot_type";
2176+
/// Key to store the SHA256 sum of vmlinuz + initrd for a deployment
2177+
pub(crate) const ORIGIN_KEY_BOOT_DIGEST: &str = "digest";
20592178

20602179
/// Creates and populates /sysroot/state/deploy/image_id
20612180
#[context("Writing composefs state")]
@@ -2065,6 +2184,7 @@ pub(crate) fn write_composefs_state(
20652184
imgref: &ImageReference,
20662185
staged: bool,
20672186
boot_type: BootType,
2187+
boot_digest: Option<String>,
20682188
) -> Result<()> {
20692189
let state_path = root_path.join(format!("{STATE_DIR_RELATIVE}/{}", deployment_id.to_hex()));
20702190

@@ -2092,6 +2212,12 @@ pub(crate) fn write_composefs_state(
20922212
.section(ORIGIN_KEY_BOOT)
20932213
.item(ORIGIN_KEY_BOOT_TYPE, boot_type);
20942214

2215+
if let Some(boot_digest) = boot_digest {
2216+
config = config
2217+
.section(ORIGIN_KEY_BOOT)
2218+
.item(ORIGIN_KEY_BOOT_DIGEST, boot_digest);
2219+
}
2220+
20952221
let state_dir = cap_std::fs::Dir::open_ambient_dir(&state_path, cap_std::ambient_authority())
20962222
.context("Opening state dir")?;
20972223

0 commit comments

Comments
 (0)