11//! On-disk saved state.
22
3+ use crate :: bootloader:: Bootloader ;
4+ use crate :: efi:: Efi ;
5+ use crate :: freezethaw:: fsfreeze_thaw_cycle;
36use crate :: model:: SavedState ;
47use crate :: util:: SignalTerminationGuard ;
58use anyhow:: { bail, Context , Result } ;
@@ -10,7 +13,9 @@ use fn_error_context::context;
1013use fs2:: FileExt ;
1114use std:: fs:: File ;
1215use std:: io:: prelude:: * ;
16+ use std:: os:: fd:: { AsRawFd , FromRawFd } ;
1317use std:: path:: Path ;
18+ use tempfile:: tempdir;
1419
1520impl SavedState {
1621 /// System-wide bootupd write lock (relative to sysroot).
@@ -57,13 +62,52 @@ impl SavedState {
5762
5863 /// Load the JSON file containing on-disk state.
5964 #[ context( "Loading saved state" ) ]
60- pub ( crate ) fn load_from_disk ( root_path : impl AsRef < Path > ) -> Result < Option < SavedState > > {
65+ pub ( crate ) fn load_from_disk (
66+ root_path : impl AsRef < Path > ,
67+ bootloader : Option < Bootloader > ,
68+ ) -> Result < Option < SavedState > > {
6169 let root_path = root_path. as_ref ( ) ;
6270 let sysroot = Dir :: open_ambient_dir ( root_path, ambient_authority ( ) )
6371 . with_context ( || format ! ( "opening sysroot '{}'" , root_path. display( ) ) ) ?;
6472
65- let statefile_path = Path :: new ( Self :: STATEFILE_DIR ) . join ( Self :: STATEFILE_NAME ) ;
66- let saved_state = if let Some ( statusf) = sysroot. open_optional ( & statefile_path) ? {
73+ let ( statefile, _esp_guard) = match bootloader {
74+ Some ( b) => match b {
75+ Bootloader :: Grub => {
76+ let path = Path :: new ( Self :: STATEFILE_DIR ) . join ( Self :: STATEFILE_NAME ) ;
77+ ( sysroot. open_optional ( & path) ?, None )
78+ }
79+
80+ Bootloader :: GrubCC => {
81+ let efi = Efi :: default ( ) ;
82+
83+ let dir = Dir :: open_ambient_dir ( & root_path, ambient_authority ( ) )
84+ . with_context ( || format ! ( "Opening filesystem path {root_path:?}" ) ) ?;
85+ let device = bootc_internal_blockdev:: list_dev_by_dir ( & dir) ?;
86+
87+ // Since we write the state file to all ESPs, it should be enough to get it
88+ // from the first one. Though, we could check the integrity by getting from
89+ // all the ESPs and making sure they're all the same...
90+ let esp = device. find_first_colocated_esp ( ) ?;
91+
92+ let tmpdir = tempdir ( ) ?;
93+ std:: fs:: create_dir_all ( tmpdir. path ( ) . join ( "efi" ) )
94+ . context ( "Creating efi inside tmpdir" ) ?;
95+
96+ let mounted = efi
97+ . ensure_mounted_esp ( tmpdir. path ( ) , & Path :: new ( & esp. path ( ) ) )
98+ . context ( "Mounting ESP" ) ?;
99+
100+ let dir = Dir :: open_ambient_dir ( & mounted, ambient_authority ( ) ) ?;
101+
102+ ( dir. open_optional ( Self :: STATEFILE_NAME ) ?, Some ( efi) )
103+ }
104+ } ,
105+
106+ // No bootloader, we're probably running inside a container
107+ None => ( None , None ) ,
108+ } ;
109+
110+ let saved_state = if let Some ( statusf) = statefile {
67111 let mut bufr = std:: io:: BufReader :: new ( statusf) ;
68112 let mut s = String :: new ( ) ;
69113 bufr. read_to_string ( & mut s) ?;
@@ -85,18 +129,34 @@ impl SavedState {
85129 } else {
86130 None
87131 } ;
132+
88133 Ok ( saved_state)
89134 }
90135
91136 /// Check whether statefile exists.
92- pub ( crate ) fn ensure_not_present ( root_path : impl AsRef < Path > ) -> Result < ( ) > {
93- let statepath = Path :: new ( root_path. as_ref ( ) )
94- . join ( Self :: STATEFILE_DIR )
95- . join ( Self :: STATEFILE_NAME ) ;
96- if statepath. exists ( ) {
97- bail ! ( "{} already exists" , statepath. display( ) ) ;
137+ pub ( crate ) fn ensure_not_present (
138+ root_path : impl AsRef < Path > ,
139+ bootloader : Bootloader ,
140+ ) -> Result < ( ) > {
141+ let saved_state = SavedState :: load_from_disk ( & root_path, Some ( bootloader) ) ?;
142+
143+ if saved_state. is_none ( ) {
144+ return Ok ( ( ) ) ;
145+ }
146+
147+ match bootloader {
148+ Bootloader :: Grub => {
149+ let statepath = Path :: new ( root_path. as_ref ( ) )
150+ . join ( Self :: STATEFILE_DIR )
151+ . join ( Self :: STATEFILE_NAME ) ;
152+
153+ bail ! ( "{} already exists" , statepath. display( ) ) ;
154+ }
155+
156+ Bootloader :: GrubCC => {
157+ bail ! ( "{} already exists in the ESP" , Self :: STATEFILE_NAME ) ;
158+ }
98159 }
99- Ok ( ( ) )
100160 }
101161}
102162
@@ -112,16 +172,59 @@ pub(crate) struct StateLockGuard {
112172
113173impl StateLockGuard {
114174 /// Atomically replace the on-disk state with a new version.
115- pub ( crate ) fn update_state ( & mut self , state : & SavedState ) -> Result < ( ) > {
116- let subdir = self . sysroot . open_dir ( SavedState :: STATEFILE_DIR ) ?;
117-
118- subdir
119- . atomic_write_with_perms (
120- SavedState :: STATEFILE_NAME ,
121- serde_json:: to_vec ( state) . context ( "Serializing state" ) ?,
122- Permissions :: from_mode ( 0o644 ) ,
123- )
124- . context ( "Writing state file" ) ?;
175+ #[ context( "Updating state" ) ]
176+ pub ( crate ) fn update_state (
177+ & mut self ,
178+ state : & SavedState ,
179+ bootloader : Bootloader ,
180+ ) -> Result < ( ) > {
181+ if bootloader == Bootloader :: Grub {
182+ let subdir = self . sysroot . open_dir ( SavedState :: STATEFILE_DIR ) ?;
183+
184+ subdir
185+ . atomic_write_with_perms (
186+ SavedState :: STATEFILE_NAME ,
187+ serde_json:: to_vec ( state) . context ( "Serializing state" ) ?,
188+ Permissions :: from_mode ( 0o644 ) ,
189+ )
190+ . context ( "Writing state file" ) ?;
191+
192+ return Ok ( ( ) ) ;
193+ }
194+
195+ let dir = unsafe { Dir :: from_raw_fd ( self . sysroot . as_raw_fd ( ) ) } ;
196+ let device = bootc_internal_blockdev:: list_dev_by_dir ( & dir) ?;
197+ let all_esps = device
198+ . find_colocated_esps ( )
199+ . context ( "Searching for ESP" ) ?
200+ . ok_or_else ( || anyhow:: anyhow!( "ESP not found" ) ) ?;
201+
202+ let efi = Efi :: default ( ) ;
203+
204+ let tmpdir = tempdir ( ) ?;
205+
206+ // [`ensure_mounted_esp`] needs this
207+ std:: fs:: create_dir_all ( tmpdir. path ( ) . join ( "efi" ) ) . context ( "Creating efi inside tmpdir" ) ?;
208+
209+ for esp in all_esps {
210+ let mounted = efi
211+ . ensure_mounted_esp ( tmpdir. path ( ) , & Path :: new ( & esp. path ( ) ) )
212+ . context ( "Mounting ESP" ) ?;
213+
214+ let dir = Dir :: open_ambient_dir ( & mounted, ambient_authority ( ) ) ?;
215+
216+ dir. atomic_replace_with ( SavedState :: STATEFILE_NAME , |w| -> std:: io:: Result < ( ) > {
217+ serde_json:: to_writer ( w, state) ?;
218+ Ok ( ( ) )
219+ } ) ?;
220+
221+ // dir.set_permissions(SavedState::STATEFILE_NAME, 0o644)?;
222+
223+ // Do the sync before unmount
224+ fsfreeze_thaw_cycle ( dir. reopen_as_ownedfd ( ) ?) ?;
225+ drop ( dir) ;
226+ efi. unmount ( ) . context ( "unmount after update" ) ?;
227+ }
125228
126229 Ok ( ( ) )
127230 }
0 commit comments