@@ -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 ;
5253use 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" ) ]
16001728pub ( 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
17261834pub 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) -
20512165pub ( crate ) const COMPOSEFS_TRANSIENT_STATE_DIR : & str = "/run/composefs" ;
20522166/// File created in /run/composefs to record a staged-deployment
20532167pub ( 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
20552172pub ( crate ) const STATE_DIR_RELATIVE : & str = "state/deploy" ;
20562173
20572174pub ( crate ) const ORIGIN_KEY_BOOT : & str = "boot" ;
20582175pub ( 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