@@ -49,6 +49,7 @@ use ostree_ext::composefs::{
49
49
repository:: Repository as ComposefsRepository ,
50
50
util:: Sha256Digest ,
51
51
} ;
52
+ use ostree_ext:: composefs_boot:: bootloader:: UsrLibModulesVmlinuz ;
52
53
use ostree_ext:: composefs_boot:: {
53
54
bootloader:: BootEntry as ComposefsBootEntry , cmdline:: get_cmdline_composefs, uki, BootOps ,
54
55
} ;
@@ -1596,14 +1597,141 @@ pub(crate) enum BootSetupType<'a> {
1596
1597
Upgrade ,
1597
1598
}
1598
1599
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
1599
1727
#[ context( "Setting up BLS boot" ) ]
1600
1728
pub ( crate ) fn setup_composefs_bls_boot (
1601
1729
setup_type : BootSetupType ,
1602
1730
// TODO: Make this generic
1603
1731
repo : ComposefsRepository < Sha256HashValue > ,
1604
1732
id : & Sha256HashValue ,
1605
1733
entry : ComposefsBootEntry < Sha256HashValue > ,
1606
- ) -> Result < ( ) > {
1734
+ ) -> Result < String > {
1607
1735
let id_hex = id. to_hex ( ) ;
1608
1736
1609
1737
let ( root_path, cmdline_refs) = match setup_type {
@@ -1636,58 +1764,38 @@ pub(crate) fn setup_composefs_bls_boot(
1636
1764
1637
1765
let boot_dir = root_path. join ( "boot" ) ;
1638
1766
1639
- let bls_config = match & entry {
1767
+ let is_upgrade = matches ! ( setup_type, BootSetupType :: Upgrade ) ;
1768
+
1769
+ let ( bls_config, boot_digest) = match & entry {
1640
1770
ComposefsBootEntry :: Type1 ( ..) => todo ! ( ) ,
1641
1771
ComposefsBootEntry :: Type2 ( ..) => todo ! ( ) ,
1642
1772
ComposefsBootEntry :: UsrLibModulesUki ( ..) => todo ! ( ) ,
1643
1773
1644
1774
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" ) ?;
1671
1777
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 {
1680
1779
title : Some ( id_hex. clone ( ) ) ,
1681
1780
version : 1 ,
1682
1781
linux : format ! ( "/boot/{id_hex}/vmlinuz" ) ,
1683
1782
initrd : format ! ( "/boot/{id_hex}/initrd" ) ,
1684
1783
options : cmdline_refs,
1685
1784
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) ?;
1686
1792
}
1793
+
1794
+ ( bls_config, boot_digest)
1687
1795
}
1688
1796
} ;
1689
1797
1690
- let ( entries_path, booted_bls) = if matches ! ( setup_type , BootSetupType :: Upgrade ) {
1798
+ let ( entries_path, booted_bls) = if is_upgrade {
1691
1799
let mut booted_bls = get_booted_bls ( ) ?;
1692
1800
booted_bls. version = 0 ; // entries are sorted by their filename in reverse order
1693
1801
@@ -1720,7 +1828,7 @@ pub(crate) fn setup_composefs_bls_boot(
1720
1828
. context ( "Reopening as owned fd" ) ?;
1721
1829
rustix:: fs:: fsync ( owned_loader_entries_fd) . context ( "fsync" ) ?;
1722
1830
1723
- Ok ( ( ) )
1831
+ Ok ( boot_digest )
1724
1832
}
1725
1833
1726
1834
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) -
2017
2125
} ;
2018
2126
2019
2127
let boot_type = BootType :: from ( & entry) ;
2128
+ let mut boot_digest: Option < String > = None ;
2020
2129
2021
2130
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
+ }
2028
2141
BootType :: Uki => setup_composefs_uki_boot (
2029
2142
BootSetupType :: Setup ( ( & root_setup, & state) ) ,
2030
2143
repo,
@@ -2043,6 +2156,7 @@ fn setup_composefs_boot(root_setup: &RootSetup, state: &State, image_id: &str) -
2043
2156
} ,
2044
2157
false ,
2045
2158
boot_type,
2159
+ boot_digest,
2046
2160
) ?;
2047
2161
2048
2162
Ok ( ( ) )
@@ -2051,11 +2165,16 @@ fn setup_composefs_boot(root_setup: &RootSetup, state: &State, image_id: &str) -
2051
2165
pub ( crate ) const COMPOSEFS_TRANSIENT_STATE_DIR : & str = "/run/composefs" ;
2052
2166
/// File created in /run/composefs to record a staged-deployment
2053
2167
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
2055
2172
pub ( crate ) const STATE_DIR_RELATIVE : & str = "state/deploy" ;
2056
2173
2057
2174
pub ( crate ) const ORIGIN_KEY_BOOT : & str = "boot" ;
2058
2175
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" ;
2059
2178
2060
2179
/// Creates and populates /sysroot/state/deploy/image_id
2061
2180
#[ context( "Writing composefs state" ) ]
@@ -2065,6 +2184,7 @@ pub(crate) fn write_composefs_state(
2065
2184
imgref : & ImageReference ,
2066
2185
staged : bool ,
2067
2186
boot_type : BootType ,
2187
+ boot_digest : Option < String > ,
2068
2188
) -> Result < ( ) > {
2069
2189
let state_path = root_path. join ( format ! ( "{STATE_DIR_RELATIVE}/{}" , deployment_id. to_hex( ) ) ) ;
2070
2190
@@ -2092,6 +2212,12 @@ pub(crate) fn write_composefs_state(
2092
2212
. section ( ORIGIN_KEY_BOOT )
2093
2213
. item ( ORIGIN_KEY_BOOT_TYPE , boot_type) ;
2094
2214
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
+
2095
2221
let state_dir = cap_std:: fs:: Dir :: open_ambient_dir ( & state_path, cap_std:: ambient_authority ( ) )
2096
2222
. context ( "Opening state dir" ) ?;
2097
2223
0 commit comments