1+ use std:: fmt:: Write ;
12use std:: path:: PathBuf ;
2- use std:: { fmt:: Write , fs:: create_dir_all} ;
33
44use anyhow:: { anyhow, Context , Result } ;
5+ use bootc_mount:: tempmount:: TempMount ;
6+ use cap_std_ext:: cap_std:: ambient_authority;
57use cap_std_ext:: cap_std:: fs:: Dir ;
68use cap_std_ext:: { cap_std, dirext:: CapStdExtDirExt } ;
79use fn_error_context:: context;
810use rustix:: fs:: { fsync, renameat_with, AtFlags , RenameFlags } ;
911
10- use crate :: bootc_composefs:: boot:: BootType ;
12+ use crate :: bootc_composefs:: boot:: {
13+ get_esp_partition, get_sysroot_parent_dev, type1_entry_conf_file_name, BootType ,
14+ } ;
1115use crate :: bootc_composefs:: status:: { composefs_deployment_status, get_sorted_type1_boot_entries} ;
16+ use crate :: composefs_consts:: TYPE1_ENT_PATH_STAGED ;
17+ use crate :: spec:: Bootloader ;
1218use crate :: {
13- bootc_composefs:: { boot:: get_efi_uuid_source, status:: get_sorted_uki_boot_entries } ,
19+ bootc_composefs:: { boot:: get_efi_uuid_source, status:: get_sorted_grub_uki_boot_entries } ,
1420 composefs_consts:: {
1521 BOOT_LOADER_ENTRIES , STAGED_BOOT_LOADER_ENTRIES , USER_CFG , USER_CFG_STAGED ,
1622 } ,
1723 spec:: BootOrder ,
1824} ;
1925
26+ /// Atomically rename exchange grub user.cfg with the staged version
27+ /// Performed as the last step in rollback/update/switch operation
28+ #[ context( "Atomically exchanging user.cfg" ) ]
2029pub ( crate ) fn rename_exchange_user_cfg ( entries_dir : & Dir ) -> Result < ( ) > {
2130 tracing:: debug!( "Atomically exchanging {USER_CFG_STAGED} and {USER_CFG}" ) ;
2231 renameat_with (
@@ -34,13 +43,19 @@ pub(crate) fn rename_exchange_user_cfg(entries_dir: &Dir) -> Result<()> {
3443 tracing:: debug!( "Syncing to disk" ) ;
3544 let entries_dir = entries_dir
3645 . reopen_as_ownedfd ( )
37- . context ( format ! ( "Reopening entries dir as owned fd" ) ) ?;
46+ . context ( "Reopening entries dir as owned fd" ) ?;
3847
39- fsync ( entries_dir) . context ( format ! ( "fsync entries dir" ) ) ?;
48+ fsync ( entries_dir) . context ( "fsync entries dir" ) ?;
4049
4150 Ok ( ( ) )
4251}
4352
53+ /// Atomically rename exchange "entries" <-> "entries.staged"
54+ /// Performed as the last step in rollback/update/switch operation
55+ ///
56+ /// `entries_dir` is the directory that contains the BLS entries directories
57+ /// Ex: entries_dir = ESP/loader or boot/loader
58+ #[ context( "Atomically exchanging BLS entries" ) ]
4459pub ( crate ) fn rename_exchange_bls_entries ( entries_dir : & Dir ) -> Result < ( ) > {
4560 tracing:: debug!( "Atomically exchanging {STAGED_BOOT_LOADER_ENTRIES} and {BOOT_LOADER_ENTRIES}" ) ;
4661 renameat_with (
@@ -60,23 +75,22 @@ pub(crate) fn rename_exchange_bls_entries(entries_dir: &Dir) -> Result<()> {
6075 tracing:: debug!( "Syncing to disk" ) ;
6176 let entries_dir = entries_dir
6277 . reopen_as_ownedfd ( )
63- . with_context ( || format ! ( "Reopening /sysroot/boot/loader as owned fd" ) ) ?;
78+ . context ( "Reopening as owned fd" ) ?;
6479
6580 fsync ( entries_dir) . context ( "fsync" ) ?;
6681
6782 Ok ( ( ) )
6883}
6984
70- #[ context( "Rolling back UKI" ) ]
71- pub ( crate ) fn rollback_composefs_uki ( ) -> Result < ( ) > {
85+ #[ context( "Rolling back Grub UKI" ) ]
86+ fn rollback_grub_uki_entries ( ) -> Result < ( ) > {
7287 let user_cfg_path = PathBuf :: from ( "/sysroot/boot/grub2" ) ;
7388
7489 let mut str = String :: new ( ) ;
7590 let boot_dir =
76- cap_std:: fs:: Dir :: open_ambient_dir ( "/sysroot/boot" , cap_std:: ambient_authority ( ) )
77- . context ( "Opening boot dir" ) ?;
78- let mut menuentries =
79- get_sorted_uki_boot_entries ( & boot_dir, & mut str) . context ( "Getting UKI boot entries" ) ?;
91+ Dir :: open_ambient_dir ( "/sysroot/boot" , ambient_authority ( ) ) . context ( "Opening boot dir" ) ?;
92+ let mut menuentries = get_sorted_grub_uki_boot_entries ( & boot_dir, & mut str)
93+ . context ( "Getting UKI boot entries" ) ?;
8094
8195 // TODO(Johan-Liebert): Currently assuming there are only two deployments
8296 assert ! ( menuentries. len( ) == 2 ) ;
@@ -101,12 +115,14 @@ pub(crate) fn rollback_composefs_uki() -> Result<()> {
101115 rename_exchange_user_cfg ( & entries_dir)
102116}
103117
104- #[ context( "Rolling back BLS" ) ]
105- pub ( crate ) fn rollback_composefs_bls ( ) -> Result < ( ) > {
106- let boot_dir =
107- cap_std:: fs:: Dir :: open_ambient_dir ( "/sysroot/boot" , cap_std:: ambient_authority ( ) )
108- . context ( "Opening boot dir" ) ?;
109-
118+ /// Performs rollback for
119+ /// - Grub Type1 boot entries
120+ /// - Systemd Typ1 boot entries
121+ /// - Systemd UKI (Type2) boot entries [since we use BLS entries for systemd boot]
122+ ///
123+ /// The bootloader parameter is only for logging purposes
124+ #[ context( "Rolling back {bootloader} entries" ) ]
125+ fn rollback_composefs_entries ( boot_dir : & Dir , bootloader : Bootloader ) -> Result < ( ) > {
110126 // Sort in descending order as that's the order they're shown on the boot screen
111127 // After this:
112128 // all_configs[0] -> booted depl
@@ -122,34 +138,33 @@ pub(crate) fn rollback_composefs_bls() -> Result<()> {
122138 assert ! ( all_configs. len( ) == 2 ) ;
123139
124140 // Write these
125- let dir_path = PathBuf :: from ( format ! ( "/sysroot/boot/loader/{STAGED_BOOT_LOADER_ENTRIES}" , ) ) ;
126- create_dir_all ( & dir_path) . with_context ( || format ! ( "Failed to create dir: {dir_path:?}" ) ) ?;
141+ boot_dir
142+ . create_dir_all ( TYPE1_ENT_PATH_STAGED )
143+ . context ( "Creating staged dir" ) ?;
127144
128- let rollback_entries_dir =
129- cap_std :: fs :: Dir :: open_ambient_dir ( & dir_path , cap_std :: ambient_authority ( ) )
130- . with_context ( || format ! ( "Opening {dir_path:?}" ) ) ?;
145+ let rollback_entries_dir = boot_dir
146+ . open_dir ( TYPE1_ENT_PATH_STAGED )
147+ . context ( "Opening staged entries dir" ) ?;
131148
132149 // Write the BLS configs in there
133150 for cfg in all_configs {
134151 // SAFETY: We set sort_key above
135- let file_name = format ! ( "bootc-composefs-{}.conf" , cfg. sort_key. as_ref( ) . unwrap( ) ) ;
152+ let file_name = type1_entry_conf_file_name ( cfg. sort_key . as_ref ( ) . unwrap ( ) ) ;
136153
137154 rollback_entries_dir
138155 . atomic_write ( & file_name, cfg. to_string ( ) )
139156 . with_context ( || format ! ( "Writing to {file_name}" ) ) ?;
140157 }
141158
159+ let rollback_entries_dir = rollback_entries_dir
160+ . reopen_as_ownedfd ( )
161+ . context ( "Reopening as owned fd" ) ?;
162+
142163 // Should we sync after every write?
143- fsync (
144- rollback_entries_dir
145- . reopen_as_ownedfd ( )
146- . with_context ( || format ! ( "Reopening {dir_path:?} as owned fd" ) ) ?,
147- )
148- . with_context ( || format ! ( "fsync {dir_path:?}" ) ) ?;
164+ fsync ( rollback_entries_dir) . context ( "fsync" ) ?;
149165
150166 // Atomically exchange "entries" <-> "entries.rollback"
151- let dir = Dir :: open_ambient_dir ( "/sysroot/boot/loader" , cap_std:: ambient_authority ( ) )
152- . context ( "Opening loader dir" ) ?;
167+ let dir = boot_dir. open_dir ( "loader" ) . context ( "Opening loader dir" ) ?;
153168
154169 rename_exchange_bls_entries ( & dir)
155170}
@@ -180,14 +195,33 @@ pub(crate) async fn composefs_rollback() -> Result<()> {
180195 // TODO: Handle staged deployment
181196 // Ostree will drop any staged deployment on rollback but will keep it if it is the first item
182197 // in the new deployment list
183- let Some ( rollback_composefs_entry ) = & rollback_status. composefs else {
198+ let Some ( rollback_entry ) = & rollback_status. composefs else {
184199 anyhow:: bail!( "Rollback deployment not a composefs deployment" )
185200 } ;
186201
187- match rollback_composefs_entry. boot_type {
188- BootType :: Bls => rollback_composefs_bls ( ) ,
189- BootType :: Uki => rollback_composefs_uki ( ) ,
190- } ?;
202+ match & rollback_entry. bootloader {
203+ Bootloader :: Grub => match rollback_entry. boot_type {
204+ BootType :: Bls => {
205+ let boot_dir = Dir :: open_ambient_dir ( "/sysroot/boot" , ambient_authority ( ) )
206+ . context ( "Opening boot dir" ) ?;
207+
208+ rollback_composefs_entries ( & boot_dir, rollback_entry. bootloader . clone ( ) ) ?;
209+ }
210+
211+ BootType :: Uki => {
212+ rollback_grub_uki_entries ( ) ?;
213+ }
214+ } ,
215+
216+ Bootloader :: Systemd => {
217+ let parent = get_sysroot_parent_dev ( ) ?;
218+ let ( esp_part, ..) = get_esp_partition ( & parent) ?;
219+ let esp_mount = TempMount :: mount_dev ( & esp_part) ?;
220+
221+ // We use BLS entries for systemd UKI as well
222+ rollback_composefs_entries ( & esp_mount. fd , rollback_entry. bootloader . clone ( ) ) ?;
223+ }
224+ }
191225
192226 if reverting {
193227 println ! ( "Next boot: current deployment" ) ;
0 commit comments