Skip to content

Commit 425b439

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 2c63434 commit 425b439

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
};
@@ -1599,14 +1600,141 @@ pub(crate) enum BootSetupType<'a> {
15991600
Upgrade,
16001601
}
16011602

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

16121740
let (root_path, cmdline_refs) = match setup_type {
@@ -1639,58 +1767,38 @@ pub(crate) fn setup_composefs_bls_boot(
16391767

16401768
let boot_dir = root_path.join("boot");
16411769

1642-
let bls_config = match &entry {
1770+
let is_upgrade = matches!(setup_type, BootSetupType::Upgrade);
1771+
1772+
let (bls_config, boot_digest) = match &entry {
16431773
ComposefsBootEntry::Type1(..) => todo!(),
16441774
ComposefsBootEntry::Type2(..) => todo!(),
16451775
ComposefsBootEntry::UsrLibModulesUki(..) => todo!(),
16461776

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

1675-
// Can't call fsync on O_PATH fds, so re-open it as a non O_PATH fd
1676-
let owned_fd = entries_dir
1677-
.reopen_as_ownedfd()
1678-
.context("Reopen as owned fd")?;
1679-
1680-
rustix::fs::fsync(owned_fd).context("fsync")?;
1681-
1682-
BLSConfig {
1781+
let mut bls_config = BLSConfig {
16831782
title: Some(id_hex.clone()),
16841783
version: 1,
16851784
linux: format!("/boot/{id_hex}/vmlinuz"),
16861785
initrd: format!("/boot/{id_hex}/initrd"),
16871786
options: cmdline_refs,
16881787
extra: HashMap::new(),
1788+
};
1789+
1790+
if let Some(symlink_to) = find_vmlinuz_initrd_duplicates(&boot_digest)? {
1791+
bls_config.linux = format!("/boot/{symlink_to}/vmlinuz");
1792+
bls_config.initrd = format!("/boot/{symlink_to}/initrd");
1793+
} else {
1794+
write_bls_boot_entries_to_disk(&boot_dir, id, usr_lib_modules_vmlinuz, &repo)?;
16891795
}
1796+
1797+
(bls_config, boot_digest)
16901798
}
16911799
};
16921800

1693-
let (entries_path, booted_bls) = if matches!(setup_type, BootSetupType::Upgrade) {
1801+
let (entries_path, booted_bls) = if is_upgrade {
16941802
let mut booted_bls = get_booted_bls()?;
16951803
booted_bls.version = 0; // entries are sorted by their filename in reverse order
16961804

@@ -1723,7 +1831,7 @@ pub(crate) fn setup_composefs_bls_boot(
17231831
.context("Reopening as owned fd")?;
17241832
rustix::fs::fsync(owned_loader_entries_fd).context("fsync")?;
17251833

1726-
Ok(())
1834+
Ok(boot_digest)
17271835
}
17281836

17291837
pub fn get_esp_partition(device: &str) -> Result<(String, Option<String>)> {
@@ -2020,14 +2128,19 @@ fn setup_composefs_boot(root_setup: &RootSetup, state: &State, image_id: &str) -
20202128
};
20212129

20222130
let boot_type = BootType::from(&entry);
2131+
let mut boot_digest: Option<String> = None;
20232132

20242133
match boot_type {
2025-
BootType::Bls => setup_composefs_bls_boot(
2026-
BootSetupType::Setup((&root_setup, &state)),
2027-
repo,
2028-
&id,
2029-
entry,
2030-
)?,
2134+
BootType::Bls => {
2135+
let digest = setup_composefs_bls_boot(
2136+
BootSetupType::Setup((&root_setup, &state)),
2137+
repo,
2138+
&id,
2139+
entry,
2140+
)?;
2141+
2142+
boot_digest = Some(digest);
2143+
}
20312144
BootType::Uki => setup_composefs_uki_boot(
20322145
BootSetupType::Setup((&root_setup, &state)),
20332146
repo,
@@ -2046,6 +2159,7 @@ fn setup_composefs_boot(root_setup: &RootSetup, state: &State, image_id: &str) -
20462159
},
20472160
false,
20482161
boot_type,
2162+
boot_digest,
20492163
)?;
20502164

20512165
Ok(())
@@ -2054,11 +2168,16 @@ fn setup_composefs_boot(root_setup: &RootSetup, state: &State, image_id: &str) -
20542168
pub(crate) const COMPOSEFS_TRANSIENT_STATE_DIR: &str = "/run/composefs";
20552169
/// File created in /run/composefs to record a staged-deployment
20562170
pub(crate) const COMPOSEFS_STAGED_DEPLOYMENT_FNAME: &str = "staged-deployment";
2057-
/// Relative to /sysroot
2171+
2172+
/// Absolute path to composefs-native state directory
2173+
pub(crate) const STATE_DIR_ABS: &str = "/sysroot/state/deploy";
2174+
/// Relative path to composefs-native state directory. Relative to /sysroot
20582175
pub(crate) const STATE_DIR_RELATIVE: &str = "state/deploy";
20592176

20602177
pub(crate) const ORIGIN_KEY_BOOT: &str = "boot";
20612178
pub(crate) const ORIGIN_KEY_BOOT_TYPE: &str = "boot_type";
2179+
/// Key to store the SHA256 sum of vmlinuz + initrd for a deployment
2180+
pub(crate) const ORIGIN_KEY_BOOT_DIGEST: &str = "digest";
20622181

20632182
/// Creates and populates /sysroot/state/deploy/image_id
20642183
#[context("Writing composefs state")]
@@ -2068,6 +2187,7 @@ pub(crate) fn write_composefs_state(
20682187
imgref: &ImageReference,
20692188
staged: bool,
20702189
boot_type: BootType,
2190+
boot_digest: Option<String>,
20712191
) -> Result<()> {
20722192
let state_path = root_path.join(format!("{STATE_DIR_RELATIVE}/{}", deployment_id.to_hex()));
20732193

@@ -2095,6 +2215,12 @@ pub(crate) fn write_composefs_state(
20952215
.section(ORIGIN_KEY_BOOT)
20962216
.item(ORIGIN_KEY_BOOT_TYPE, boot_type);
20972217

2218+
if let Some(boot_digest) = boot_digest {
2219+
config = config
2220+
.section(ORIGIN_KEY_BOOT)
2221+
.item(ORIGIN_KEY_BOOT_DIGEST, boot_digest);
2222+
}
2223+
20982224
let state_dir = cap_std::fs::Dir::open_ambient_dir(&state_path, cap_std::ambient_authority())
20992225
.context("Opening state dir")?;
21002226

0 commit comments

Comments
 (0)