Skip to content

Commit 8e410f1

Browse files
Johan-Liebert1cgwalters
authored andcommitted
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 d3e8d36 commit 8e410f1

File tree

2 files changed

+202
-52
lines changed

2 files changed

+202
-52
lines changed

crates/lib/src/cli.rs

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

821821
let boot_type = BootType::from(&entry);
822+
let mut boot_digest = None;
822823

823824
match boot_type {
824-
BootType::Bls => setup_composefs_bls_boot(BootSetupType::Upgrade, repo, &id, entry),
825-
BootType::Uki => setup_composefs_uki_boot(BootSetupType::Upgrade, repo, &id, entry),
826-
}?;
825+
BootType::Bls => {
826+
boot_digest = Some(setup_composefs_bls_boot(
827+
BootSetupType::Upgrade,
828+
repo,
829+
&id,
830+
entry,
831+
)?)
832+
}
827833

828-
write_composefs_state(&Utf8PathBuf::from("/sysroot"), id, imgref, true, boot_type)?;
834+
BootType::Uki => setup_composefs_uki_boot(BootSetupType::Upgrade, repo, &id, entry)?,
835+
};
836+
837+
write_composefs_state(
838+
&Utf8PathBuf::from("/sysroot"),
839+
id,
840+
imgref,
841+
true,
842+
boot_type,
843+
boot_digest,
844+
)?;
829845

830846
Ok(())
831847
}
@@ -987,18 +1003,27 @@ async fn switch_composefs(opts: SwitchOpts) -> Result<()> {
9871003
};
9881004

9891005
let boot_type = BootType::from(&entry);
1006+
let mut boot_digest = None;
9901007

9911008
match boot_type {
992-
BootType::Bls => setup_composefs_bls_boot(BootSetupType::Upgrade, repo, &id, entry),
993-
BootType::Uki => setup_composefs_uki_boot(BootSetupType::Upgrade, repo, &id, entry),
994-
}?;
1009+
BootType::Bls => {
1010+
boot_digest = Some(setup_composefs_bls_boot(
1011+
BootSetupType::Upgrade,
1012+
repo,
1013+
&id,
1014+
entry,
1015+
)?)
1016+
}
1017+
BootType::Uki => setup_composefs_uki_boot(BootSetupType::Upgrade, repo, &id, entry)?,
1018+
};
9951019

9961020
write_composefs_state(
9971021
&Utf8PathBuf::from("/sysroot"),
9981022
id,
9991023
&target_imgref,
10001024
true,
10011025
boot_type,
1026+
boot_digest,
10021027
)?;
10031028

10041029
Ok(())

crates/lib/src/install.rs

Lines changed: 170 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ use ostree_ext::composefs::{
4848
repository::Repository as ComposefsRepository,
4949
util::Sha256Digest,
5050
};
51+
use ostree_ext::composefs_boot::bootloader::UsrLibModulesVmlinuz;
5152
use ostree_ext::composefs_boot::{
5253
bootloader::BootEntry as ComposefsBootEntry, cmdline::get_cmdline_composefs, uki, BootOps,
5354
};
@@ -1609,14 +1610,141 @@ pub(crate) enum BootSetupType<'a> {
16091610
Upgrade,
16101611
}
16111612

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

16221750
let (root_path, cmdline_refs) = match setup_type {
@@ -1648,59 +1776,38 @@ pub(crate) fn setup_composefs_bls_boot(
16481776
};
16491777

16501778
let boot_dir = root_path.join("boot");
1779+
let is_upgrade = matches!(setup_type, BootSetupType::Upgrade);
16511780

1652-
let bls_config = match &entry {
1781+
let (bls_config, boot_digest) = match &entry {
16531782
ComposefsBootEntry::Type1(..) => unimplemented!(),
16541783
ComposefsBootEntry::Type2(..) => unimplemented!(),
16551784
ComposefsBootEntry::UsrLibModulesUki(..) => unimplemented!(),
16561785

16571786
ComposefsBootEntry::UsrLibModulesVmLinuz(usr_lib_modules_vmlinuz) => {
1658-
// Write the initrd and vmlinuz at /boot/<id>/
1659-
let path = boot_dir.join(&id_hex);
1660-
create_dir_all(&path)?;
1661-
1662-
let entries_dir =
1663-
cap_std::fs::Dir::open_ambient_dir(&path, cap_std::ambient_authority())
1664-
.with_context(|| format!("Opening {path}"))?;
1665-
1666-
entries_dir
1667-
.atomic_write(
1668-
"vmlinuz",
1669-
read_file(&usr_lib_modules_vmlinuz.vmlinuz, &repo)
1670-
.context("Reading vmlinuz")?,
1671-
)
1672-
.context("Writing vmlinuz to path")?;
1673-
1674-
if let Some(initramfs) = &usr_lib_modules_vmlinuz.initramfs {
1675-
entries_dir
1676-
.atomic_write(
1677-
"initrd",
1678-
read_file(initramfs, &repo).context("Reading initrd")?,
1679-
)
1680-
.context("Writing initrd to path")?;
1681-
} else {
1682-
anyhow::bail!("initramfs not found");
1683-
};
1787+
let boot_digest = compute_boot_digest(usr_lib_modules_vmlinuz, &repo)
1788+
.context("Computing boot digest")?;
16841789

1685-
// Can't call fsync on O_PATH fds, so re-open it as a non O_PATH fd
1686-
let owned_fd = entries_dir
1687-
.reopen_as_ownedfd()
1688-
.context("Reopen as owned fd")?;
1689-
1690-
rustix::fs::fsync(owned_fd).context("fsync")?;
1691-
1692-
BLSConfig {
1790+
let mut bls_config = BLSConfig {
16931791
title: Some(id_hex.clone()),
16941792
version: 1,
16951793
linux: format!("/boot/{id_hex}/vmlinuz"),
16961794
initrd: format!("/boot/{id_hex}/initrd"),
16971795
options: cmdline_refs,
16981796
extra: HashMap::new(),
1797+
};
1798+
1799+
if let Some(symlink_to) = find_vmlinuz_initrd_duplicates(&boot_digest)? {
1800+
bls_config.linux = format!("/boot/{symlink_to}/vmlinuz");
1801+
bls_config.initrd = format!("/boot/{symlink_to}/initrd");
1802+
} else {
1803+
write_bls_boot_entries_to_disk(&boot_dir, id, usr_lib_modules_vmlinuz, &repo)?;
16991804
}
1805+
1806+
(bls_config, boot_digest)
17001807
}
17011808
};
17021809

1703-
let (entries_path, booted_bls) = if matches!(setup_type, BootSetupType::Upgrade) {
1810+
let (entries_path, booted_bls) = if is_upgrade {
17041811
let mut booted_bls = get_booted_bls()?;
17051812
booted_bls.version = 0; // entries are sorted by their filename in reverse order
17061813

@@ -1733,7 +1840,7 @@ pub(crate) fn setup_composefs_bls_boot(
17331840
.context("Reopening as owned fd")?;
17341841
rustix::fs::fsync(owned_loader_entries_fd).context("fsync")?;
17351842

1736-
Ok(())
1843+
Ok(boot_digest)
17371844
}
17381845

17391846
pub fn get_esp_partition(device: &str) -> Result<(String, Option<String>)> {
@@ -2031,14 +2138,19 @@ fn setup_composefs_boot(root_setup: &RootSetup, state: &State, image_id: &str) -
20312138
};
20322139

20332140
let boot_type = BootType::from(&entry);
2141+
let mut boot_digest: Option<String> = None;
20342142

20352143
match boot_type {
2036-
BootType::Bls => setup_composefs_bls_boot(
2037-
BootSetupType::Setup((&root_setup, &state)),
2038-
repo,
2039-
&id,
2040-
entry,
2041-
)?,
2144+
BootType::Bls => {
2145+
let digest = setup_composefs_bls_boot(
2146+
BootSetupType::Setup((&root_setup, &state)),
2147+
repo,
2148+
&id,
2149+
entry,
2150+
)?;
2151+
2152+
boot_digest = Some(digest);
2153+
}
20422154
BootType::Uki => setup_composefs_uki_boot(
20432155
BootSetupType::Setup((&root_setup, &state)),
20442156
repo,
@@ -2057,6 +2169,7 @@ fn setup_composefs_boot(root_setup: &RootSetup, state: &State, image_id: &str) -
20572169
},
20582170
false,
20592171
boot_type,
2172+
boot_digest,
20602173
)?;
20612174

20622175
Ok(())
@@ -2065,11 +2178,16 @@ fn setup_composefs_boot(root_setup: &RootSetup, state: &State, image_id: &str) -
20652178
pub(crate) const COMPOSEFS_TRANSIENT_STATE_DIR: &str = "/run/composefs";
20662179
/// File created in /run/composefs to record a staged-deployment
20672180
pub(crate) const COMPOSEFS_STAGED_DEPLOYMENT_FNAME: &str = "staged-deployment";
2068-
/// Relative to /sysroot
2181+
2182+
/// Absolute path to composefs-native state directory
2183+
pub(crate) const STATE_DIR_ABS: &str = "/sysroot/state/deploy";
2184+
/// Relative path to composefs-native state directory. Relative to /sysroot
20692185
pub(crate) const STATE_DIR_RELATIVE: &str = "state/deploy";
20702186

20712187
pub(crate) const ORIGIN_KEY_BOOT: &str = "boot";
20722188
pub(crate) const ORIGIN_KEY_BOOT_TYPE: &str = "boot_type";
2189+
/// Key to store the SHA256 sum of vmlinuz + initrd for a deployment
2190+
pub(crate) const ORIGIN_KEY_BOOT_DIGEST: &str = "digest";
20732191

20742192
/// Creates and populates /sysroot/state/deploy/image_id
20752193
#[context("Writing composefs state")]
@@ -2079,6 +2197,7 @@ pub(crate) fn write_composefs_state(
20792197
imgref: &ImageReference,
20802198
staged: bool,
20812199
boot_type: BootType,
2200+
boot_digest: Option<String>,
20822201
) -> Result<()> {
20832202
let state_path = root_path.join(format!("{STATE_DIR_RELATIVE}/{}", deployment_id.to_hex()));
20842203

@@ -2106,6 +2225,12 @@ pub(crate) fn write_composefs_state(
21062225
.section(ORIGIN_KEY_BOOT)
21072226
.item(ORIGIN_KEY_BOOT_TYPE, boot_type);
21082227

2228+
if let Some(boot_digest) = boot_digest {
2229+
config = config
2230+
.section(ORIGIN_KEY_BOOT)
2231+
.item(ORIGIN_KEY_BOOT_DIGEST, boot_digest);
2232+
}
2233+
21092234
let state_dir = cap_std::fs::Dir::open_ambient_dir(&state_path, cap_std::ambient_authority())
21102235
.context("Opening state dir")?;
21112236

0 commit comments

Comments
 (0)