@@ -48,6 +48,7 @@ use ostree_ext::composefs::{
48
48
repository:: Repository as ComposefsRepository ,
49
49
util:: Sha256Digest ,
50
50
} ;
51
+ use ostree_ext:: composefs_boot:: bootloader:: UsrLibModulesVmlinuz ;
51
52
use ostree_ext:: composefs_boot:: {
52
53
bootloader:: BootEntry as ComposefsBootEntry , cmdline:: get_cmdline_composefs, uki, BootOps ,
53
54
} ;
@@ -1609,14 +1610,141 @@ pub(crate) enum BootSetupType<'a> {
1609
1610
Upgrade ,
1610
1611
}
1611
1612
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
1612
1740
#[ context( "Setting up BLS boot" ) ]
1613
1741
pub ( crate ) fn setup_composefs_bls_boot (
1614
1742
setup_type : BootSetupType ,
1615
1743
// TODO: Make this generic
1616
1744
repo : ComposefsRepository < Sha256HashValue > ,
1617
1745
id : & Sha256HashValue ,
1618
1746
entry : ComposefsBootEntry < Sha256HashValue > ,
1619
- ) -> Result < ( ) > {
1747
+ ) -> Result < String > {
1620
1748
let id_hex = id. to_hex ( ) ;
1621
1749
1622
1750
let ( root_path, cmdline_refs) = match setup_type {
@@ -1648,59 +1776,38 @@ pub(crate) fn setup_composefs_bls_boot(
1648
1776
} ;
1649
1777
1650
1778
let boot_dir = root_path. join ( "boot" ) ;
1779
+ let is_upgrade = matches ! ( setup_type, BootSetupType :: Upgrade ) ;
1651
1780
1652
- let bls_config = match & entry {
1781
+ let ( bls_config, boot_digest ) = match & entry {
1653
1782
ComposefsBootEntry :: Type1 ( ..) => unimplemented ! ( ) ,
1654
1783
ComposefsBootEntry :: Type2 ( ..) => unimplemented ! ( ) ,
1655
1784
ComposefsBootEntry :: UsrLibModulesUki ( ..) => unimplemented ! ( ) ,
1656
1785
1657
1786
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" ) ?;
1684
1789
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 {
1693
1791
title : Some ( id_hex. clone ( ) ) ,
1694
1792
version : 1 ,
1695
1793
linux : format ! ( "/boot/{id_hex}/vmlinuz" ) ,
1696
1794
initrd : format ! ( "/boot/{id_hex}/initrd" ) ,
1697
1795
options : cmdline_refs,
1698
1796
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) ?;
1699
1804
}
1805
+
1806
+ ( bls_config, boot_digest)
1700
1807
}
1701
1808
} ;
1702
1809
1703
- let ( entries_path, booted_bls) = if matches ! ( setup_type , BootSetupType :: Upgrade ) {
1810
+ let ( entries_path, booted_bls) = if is_upgrade {
1704
1811
let mut booted_bls = get_booted_bls ( ) ?;
1705
1812
booted_bls. version = 0 ; // entries are sorted by their filename in reverse order
1706
1813
@@ -1733,7 +1840,7 @@ pub(crate) fn setup_composefs_bls_boot(
1733
1840
. context ( "Reopening as owned fd" ) ?;
1734
1841
rustix:: fs:: fsync ( owned_loader_entries_fd) . context ( "fsync" ) ?;
1735
1842
1736
- Ok ( ( ) )
1843
+ Ok ( boot_digest )
1737
1844
}
1738
1845
1739
1846
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) -
2031
2138
} ;
2032
2139
2033
2140
let boot_type = BootType :: from ( & entry) ;
2141
+ let mut boot_digest: Option < String > = None ;
2034
2142
2035
2143
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
+ }
2042
2154
BootType :: Uki => setup_composefs_uki_boot (
2043
2155
BootSetupType :: Setup ( ( & root_setup, & state) ) ,
2044
2156
repo,
@@ -2057,6 +2169,7 @@ fn setup_composefs_boot(root_setup: &RootSetup, state: &State, image_id: &str) -
2057
2169
} ,
2058
2170
false ,
2059
2171
boot_type,
2172
+ boot_digest,
2060
2173
) ?;
2061
2174
2062
2175
Ok ( ( ) )
@@ -2065,11 +2178,16 @@ fn setup_composefs_boot(root_setup: &RootSetup, state: &State, image_id: &str) -
2065
2178
pub ( crate ) const COMPOSEFS_TRANSIENT_STATE_DIR : & str = "/run/composefs" ;
2066
2179
/// File created in /run/composefs to record a staged-deployment
2067
2180
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
2069
2185
pub ( crate ) const STATE_DIR_RELATIVE : & str = "state/deploy" ;
2070
2186
2071
2187
pub ( crate ) const ORIGIN_KEY_BOOT : & str = "boot" ;
2072
2188
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" ;
2073
2191
2074
2192
/// Creates and populates /sysroot/state/deploy/image_id
2075
2193
#[ context( "Writing composefs state" ) ]
@@ -2079,6 +2197,7 @@ pub(crate) fn write_composefs_state(
2079
2197
imgref : & ImageReference ,
2080
2198
staged : bool ,
2081
2199
boot_type : BootType ,
2200
+ boot_digest : Option < String > ,
2082
2201
) -> Result < ( ) > {
2083
2202
let state_path = root_path. join ( format ! ( "{STATE_DIR_RELATIVE}/{}" , deployment_id. to_hex( ) ) ) ;
2084
2203
@@ -2106,6 +2225,12 @@ pub(crate) fn write_composefs_state(
2106
2225
. section ( ORIGIN_KEY_BOOT )
2107
2226
. item ( ORIGIN_KEY_BOOT_TYPE , boot_type) ;
2108
2227
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
+
2109
2234
let state_dir = cap_std:: fs:: Dir :: open_ambient_dir ( & state_path, cap_std:: ambient_authority ( ) )
2110
2235
. context ( "Opening state dir" ) ?;
2111
2236
0 commit comments