@@ -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
} ;
@@ -1599,14 +1600,141 @@ pub(crate) enum BootSetupType<'a> {
1599
1600
Upgrade ,
1600
1601
}
1601
1602
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
1602
1730
#[ context( "Setting up BLS boot" ) ]
1603
1731
pub ( crate ) fn setup_composefs_bls_boot (
1604
1732
setup_type : BootSetupType ,
1605
1733
// TODO: Make this generic
1606
1734
repo : ComposefsRepository < Sha256HashValue > ,
1607
1735
id : & Sha256HashValue ,
1608
1736
entry : ComposefsBootEntry < Sha256HashValue > ,
1609
- ) -> Result < ( ) > {
1737
+ ) -> Result < String > {
1610
1738
let id_hex = id. to_hex ( ) ;
1611
1739
1612
1740
let ( root_path, cmdline_refs) = match setup_type {
@@ -1639,58 +1767,38 @@ pub(crate) fn setup_composefs_bls_boot(
1639
1767
1640
1768
let boot_dir = root_path. join ( "boot" ) ;
1641
1769
1642
- let bls_config = match & entry {
1770
+ let is_upgrade = matches ! ( setup_type, BootSetupType :: Upgrade ) ;
1771
+
1772
+ let ( bls_config, boot_digest) = match & entry {
1643
1773
ComposefsBootEntry :: Type1 ( ..) => todo ! ( ) ,
1644
1774
ComposefsBootEntry :: Type2 ( ..) => todo ! ( ) ,
1645
1775
ComposefsBootEntry :: UsrLibModulesUki ( ..) => todo ! ( ) ,
1646
1776
1647
1777
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" ) ?;
1674
1780
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 {
1683
1782
title : Some ( id_hex. clone ( ) ) ,
1684
1783
version : 1 ,
1685
1784
linux : format ! ( "/boot/{id_hex}/vmlinuz" ) ,
1686
1785
initrd : format ! ( "/boot/{id_hex}/initrd" ) ,
1687
1786
options : cmdline_refs,
1688
1787
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) ?;
1689
1795
}
1796
+
1797
+ ( bls_config, boot_digest)
1690
1798
}
1691
1799
} ;
1692
1800
1693
- let ( entries_path, booted_bls) = if matches ! ( setup_type , BootSetupType :: Upgrade ) {
1801
+ let ( entries_path, booted_bls) = if is_upgrade {
1694
1802
let mut booted_bls = get_booted_bls ( ) ?;
1695
1803
booted_bls. version = 0 ; // entries are sorted by their filename in reverse order
1696
1804
@@ -1723,7 +1831,7 @@ pub(crate) fn setup_composefs_bls_boot(
1723
1831
. context ( "Reopening as owned fd" ) ?;
1724
1832
rustix:: fs:: fsync ( owned_loader_entries_fd) . context ( "fsync" ) ?;
1725
1833
1726
- Ok ( ( ) )
1834
+ Ok ( boot_digest )
1727
1835
}
1728
1836
1729
1837
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) -
2020
2128
} ;
2021
2129
2022
2130
let boot_type = BootType :: from ( & entry) ;
2131
+ let mut boot_digest: Option < String > = None ;
2023
2132
2024
2133
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
+ }
2031
2144
BootType :: Uki => setup_composefs_uki_boot (
2032
2145
BootSetupType :: Setup ( ( & root_setup, & state) ) ,
2033
2146
repo,
@@ -2046,6 +2159,7 @@ fn setup_composefs_boot(root_setup: &RootSetup, state: &State, image_id: &str) -
2046
2159
} ,
2047
2160
false ,
2048
2161
boot_type,
2162
+ boot_digest,
2049
2163
) ?;
2050
2164
2051
2165
Ok ( ( ) )
@@ -2054,11 +2168,16 @@ fn setup_composefs_boot(root_setup: &RootSetup, state: &State, image_id: &str) -
2054
2168
pub ( crate ) const COMPOSEFS_TRANSIENT_STATE_DIR : & str = "/run/composefs" ;
2055
2169
/// File created in /run/composefs to record a staged-deployment
2056
2170
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
2058
2175
pub ( crate ) const STATE_DIR_RELATIVE : & str = "state/deploy" ;
2059
2176
2060
2177
pub ( crate ) const ORIGIN_KEY_BOOT : & str = "boot" ;
2061
2178
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" ;
2062
2181
2063
2182
/// Creates and populates /sysroot/state/deploy/image_id
2064
2183
#[ context( "Writing composefs state" ) ]
@@ -2068,6 +2187,7 @@ pub(crate) fn write_composefs_state(
2068
2187
imgref : & ImageReference ,
2069
2188
staged : bool ,
2070
2189
boot_type : BootType ,
2190
+ boot_digest : Option < String > ,
2071
2191
) -> Result < ( ) > {
2072
2192
let state_path = root_path. join ( format ! ( "{STATE_DIR_RELATIVE}/{}" , deployment_id. to_hex( ) ) ) ;
2073
2193
@@ -2095,6 +2215,12 @@ pub(crate) fn write_composefs_state(
2095
2215
. section ( ORIGIN_KEY_BOOT )
2096
2216
. item ( ORIGIN_KEY_BOOT_TYPE , boot_type) ;
2097
2217
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
+
2098
2224
let state_dir = cap_std:: fs:: Dir :: open_ambient_dir ( & state_path, cap_std:: ambient_authority ( ) )
2099
2225
. context ( "Opening state dir" ) ?;
2100
2226
0 commit comments