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 cap_std_ext:: cap_std:: ambient_authority;
56use cap_std_ext:: cap_std:: fs:: Dir ;
67use cap_std_ext:: { cap_std, dirext:: CapStdExtDirExt } ;
78use fn_error_context:: context;
89use rustix:: fs:: { fsync, renameat_with, AtFlags , RenameFlags } ;
910
10- use crate :: bootc_composefs:: boot:: BootType ;
11+ use crate :: bootc_composefs:: boot:: {
12+ get_esp_partition, get_sysroot_parent_dev, mount_esp, type1_entry_conf_file_name, BootType ,
13+ } ;
1114use crate :: bootc_composefs:: status:: { composefs_deployment_status, get_sorted_type1_boot_entries} ;
15+ use crate :: composefs_consts:: TYPE1_ENT_PATH_STAGED ;
16+ use crate :: spec:: Bootloader ;
1217use crate :: {
13- bootc_composefs:: { boot:: get_efi_uuid_source, status:: get_sorted_uki_boot_entries } ,
18+ bootc_composefs:: { boot:: get_efi_uuid_source, status:: get_sorted_grub_uki_boot_entries } ,
1419 composefs_consts:: {
1520 BOOT_LOADER_ENTRIES , STAGED_BOOT_LOADER_ENTRIES , USER_CFG , USER_CFG_STAGED ,
1621 } ,
1722 spec:: BootOrder ,
1823} ;
1924
25+ /// Atomically rename exchange grub user.cfg with the staged version
26+ /// Performed as the last step in rollback/update/switch operation
27+ #[ context( "Atomically exchanging user.cfg" ) ]
2028pub ( crate ) fn rename_exchange_user_cfg ( entries_dir : & Dir ) -> Result < ( ) > {
2129 tracing:: debug!( "Atomically exchanging {USER_CFG_STAGED} and {USER_CFG}" ) ;
2230 renameat_with (
@@ -34,13 +42,19 @@ pub(crate) fn rename_exchange_user_cfg(entries_dir: &Dir) -> Result<()> {
3442 tracing:: debug!( "Syncing to disk" ) ;
3543 let entries_dir = entries_dir
3644 . reopen_as_ownedfd ( )
37- . context ( format ! ( "Reopening entries dir as owned fd" ) ) ?;
45+ . context ( "Reopening entries dir as owned fd" ) ?;
3846
39- fsync ( entries_dir) . context ( format ! ( "fsync entries dir" ) ) ?;
47+ fsync ( entries_dir) . context ( "fsync entries dir" ) ?;
4048
4149 Ok ( ( ) )
4250}
4351
52+ /// Atomically rename exchange "entries" <-> "entries.staged"
53+ /// Performed as the last step in rollback/update/switch operation
54+ ///
55+ /// `entries_dir` is the directory that contains the BLS entries directories
56+ /// Ex: entries_dir = ESP/loader or boot/loader
57+ #[ context( "Atomically exchanging BLS entries" ) ]
4458pub ( crate ) fn rename_exchange_bls_entries ( entries_dir : & Dir ) -> Result < ( ) > {
4559 tracing:: debug!( "Atomically exchanging {STAGED_BOOT_LOADER_ENTRIES} and {BOOT_LOADER_ENTRIES}" ) ;
4660 renameat_with (
@@ -60,23 +74,22 @@ pub(crate) fn rename_exchange_bls_entries(entries_dir: &Dir) -> Result<()> {
6074 tracing:: debug!( "Syncing to disk" ) ;
6175 let entries_dir = entries_dir
6276 . reopen_as_ownedfd ( )
63- . with_context ( || format ! ( "Reopening /sysroot/boot/loader as owned fd" ) ) ?;
77+ . context ( "Reopening as owned fd" ) ?;
6478
6579 fsync ( entries_dir) . context ( "fsync" ) ?;
6680
6781 Ok ( ( ) )
6882}
6983
70- #[ context( "Rolling back UKI" ) ]
71- pub ( crate ) fn rollback_composefs_uki ( ) -> Result < ( ) > {
84+ #[ context( "Rolling back Grub UKI" ) ]
85+ fn rollback_grub_uki_entries ( ) -> Result < ( ) > {
7286 let user_cfg_path = PathBuf :: from ( "/sysroot/boot/grub2" ) ;
7387
7488 let mut str = String :: new ( ) ;
7589 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" ) ?;
90+ Dir :: open_ambient_dir ( "/sysroot/boot" , ambient_authority ( ) ) . context ( "Opening boot dir" ) ?;
91+ let mut menuentries = get_sorted_grub_uki_boot_entries ( & boot_dir, & mut str)
92+ . context ( "Getting UKI boot entries" ) ?;
8093
8194 // TODO(Johan-Liebert): Currently assuming there are only two deployments
8295 assert ! ( menuentries. len( ) == 2 ) ;
@@ -101,12 +114,14 @@ pub(crate) fn rollback_composefs_uki() -> Result<()> {
101114 rename_exchange_user_cfg ( & entries_dir)
102115}
103116
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-
117+ /// Performs rollback for
118+ /// - Grub Type1 boot entries
119+ /// - Systemd Typ1 boot entries
120+ /// - Systemd UKI (Type2) boot entries [since we use BLS entries for systemd boot]
121+ ///
122+ /// The bootloader parameter is only for logging purposes
123+ #[ context( "Rolling back {bootloader} entries" ) ]
124+ fn rollback_composefs_entries ( boot_dir : & Dir , bootloader : Bootloader ) -> Result < ( ) > {
110125 // Sort in descending order as that's the order they're shown on the boot screen
111126 // After this:
112127 // all_configs[0] -> booted depl
@@ -122,34 +137,33 @@ pub(crate) fn rollback_composefs_bls() -> Result<()> {
122137 assert ! ( all_configs. len( ) == 2 ) ;
123138
124139 // 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:?}" ) ) ?;
140+ boot_dir
141+ . create_dir_all ( TYPE1_ENT_PATH_STAGED )
142+ . context ( "Creating staged dir" ) ?;
127143
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:?}" ) ) ?;
144+ let rollback_entries_dir = boot_dir
145+ . open_dir ( TYPE1_ENT_PATH_STAGED )
146+ . context ( "Opening staged entries dir" ) ?;
131147
132148 // Write the BLS configs in there
133149 for cfg in all_configs {
134150 // SAFETY: We set sort_key above
135- let file_name = format ! ( "bootc-composefs-{}.conf" , cfg. sort_key. as_ref( ) . unwrap( ) ) ;
151+ let file_name = type1_entry_conf_file_name ( cfg. sort_key . as_ref ( ) . unwrap ( ) ) ;
136152
137153 rollback_entries_dir
138154 . atomic_write ( & file_name, cfg. to_string ( ) )
139155 . with_context ( || format ! ( "Writing to {file_name}" ) ) ?;
140156 }
141157
158+ let rollback_entries_dir = rollback_entries_dir
159+ . reopen_as_ownedfd ( )
160+ . context ( "Reopening as owned fd" ) ?;
161+
142162 // 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:?}" ) ) ?;
163+ fsync ( rollback_entries_dir) . context ( "fsync" ) ?;
149164
150165 // Atomically exchange "entries" <-> "entries.rollback"
151- let dir = Dir :: open_ambient_dir ( "/sysroot/boot/loader" , cap_std:: ambient_authority ( ) )
152- . context ( "Opening loader dir" ) ?;
166+ let dir = boot_dir. open_dir ( "loader" ) . context ( "Opening loader dir" ) ?;
153167
154168 rename_exchange_bls_entries ( & dir)
155169}
@@ -180,14 +194,33 @@ pub(crate) async fn composefs_rollback() -> Result<()> {
180194 // TODO: Handle staged deployment
181195 // Ostree will drop any staged deployment on rollback but will keep it if it is the first item
182196 // in the new deployment list
183- let Some ( rollback_composefs_entry ) = & rollback_status. composefs else {
197+ let Some ( rollback_entry ) = & rollback_status. composefs else {
184198 anyhow:: bail!( "Rollback deployment not a composefs deployment" )
185199 } ;
186200
187- match rollback_composefs_entry. boot_type {
188- BootType :: Bls => rollback_composefs_bls ( ) ,
189- BootType :: Uki => rollback_composefs_uki ( ) ,
190- } ?;
201+ match & rollback_entry. bootloader {
202+ Bootloader :: Grub => match rollback_entry. boot_type {
203+ BootType :: Bls => {
204+ let boot_dir = Dir :: open_ambient_dir ( "/sysroot/boot" , ambient_authority ( ) )
205+ . context ( "Opening boot dir" ) ?;
206+
207+ rollback_composefs_entries ( & boot_dir, rollback_entry. bootloader . clone ( ) ) ?;
208+ }
209+
210+ BootType :: Uki => {
211+ rollback_grub_uki_entries ( ) ?;
212+ }
213+ } ,
214+
215+ Bootloader :: Systemd => {
216+ let parent = get_sysroot_parent_dev ( ) ?;
217+ let ( esp_part, ..) = get_esp_partition ( & parent) ?;
218+ let esp_mount = mount_esp ( & esp_part) ?;
219+
220+ // We use BLS entries for systemd UKI as well
221+ rollback_composefs_entries ( & esp_mount. fd , rollback_entry. bootloader . clone ( ) ) ?;
222+ }
223+ }
191224
192225 if reverting {
193226 println ! ( "Next boot: current deployment" ) ;
0 commit comments