11//! On-disk saved state.
22
33use crate :: bootloader:: Bootloader ;
4+ use crate :: bootupd:: list_dev_current_root;
45use crate :: efi:: Efi ;
56use crate :: freezethaw:: fsfreeze_thaw_cycle;
67use crate :: model:: SavedState ;
78use crate :: util:: SignalTerminationGuard ;
89use anyhow:: { bail, Context , Result } ;
10+ use bootc_internal_blockdev:: Device ;
11+ use camino:: Utf8PathBuf ;
912use cap_std:: ambient_authority;
1013use cap_std:: fs:: { Dir , Permissions , PermissionsExt } ;
1114use cap_std_ext:: dirext:: CapStdExtDirExt ;
1215use fn_error_context:: context;
1316use fs2:: FileExt ;
1417use std:: fs:: File ;
1518use std:: io:: prelude:: * ;
16- use std:: os:: fd:: { AsRawFd , FromRawFd } ;
1719use std:: path:: Path ;
18- use tempfile:: tempdir;
20+
21+ fn parse_statefile ( statusf : cap_std:: fs:: File ) -> Result < Option < SavedState > > {
22+ let mut bufr = std:: io:: BufReader :: new ( statusf) ;
23+ let mut s = String :: new ( ) ;
24+ bufr. read_to_string ( & mut s) ?;
25+ let state: serde_json:: Result < SavedState > = serde_json:: from_str ( s. as_str ( ) ) ;
26+
27+ let r = match state {
28+ Ok ( s) => s,
29+ Err ( orig_err) => {
30+ let state: serde_json:: Result < crate :: model_legacy:: SavedState01 > =
31+ serde_json:: from_str ( s. as_str ( ) ) ;
32+ match state {
33+ Ok ( s) => s. upconvert ( ) ,
34+ Err ( _) => {
35+ return Err ( orig_err. into ( ) ) ;
36+ }
37+ }
38+ }
39+ } ;
40+
41+ Ok ( Some ( r) )
42+ }
43+
44+ /// lsblk: composefs:abc123..: not a block device
45+ /// is what lsblk throws on composefs booted systems if we try to
46+ /// get block devices using "/"
47+ ///
48+ /// First, try to get the device from the `root` which is necessary
49+ /// during installs as we don't want (or can't) to open up /sysroot or /boot
50+ ///
51+ /// If that fails, it means we're not on the install path so we get the
52+ /// device from checking mount point from /boot or /sysroot
53+ #[ context( "Getting parent device" ) ]
54+ fn get_parent_device ( root : & Dir ) -> Result < Device > {
55+ match bootc_internal_blockdev:: list_dev_by_dir ( & root) {
56+ Ok ( d) => Ok ( d) ,
57+ Err ( e) => {
58+ // Not really an error just yet
59+ log:: debug!( "{e:?}" ) ;
60+ list_dev_current_root ( )
61+ }
62+ }
63+ }
1964
2065impl SavedState {
2166 /// System-wide bootupd write lock (relative to sysroot).
2267 const WRITE_LOCK_PATH : & ' static str = "run/bootupd-lock" ;
2368 /// Top-level directory for statefile (relative to sysroot).
2469 pub ( crate ) const STATEFILE_DIR : & ' static str = "boot" ;
25- /// On-disk bootloader statefile, akin to a tiny rpm/dpkg database, stored in `/boot`.
70+ /// On-disk bootloader statefile, akin to a tiny rpm/dpkg database,
71+ /// stored in `/boot` for Grub and in `ESP` for GrubCC
2672 pub ( crate ) const STATEFILE_NAME : & ' static str = "bootupd-state.json" ;
2773
2874 /// Try to acquire a system-wide lock to ensure non-conflicting state updates.
2975 ///
3076 /// While ordinarily the daemon runs as a systemd unit (which implicitly
3177 /// ensures a single instance) this is a double check against other
3278 /// execution paths.
33- pub ( crate ) fn acquire_write_lock ( sysroot : Dir ) -> Result < StateLockGuard > {
79+ pub ( crate ) fn acquire_write_lock (
80+ sysroot_path : Utf8PathBuf ,
81+ sysroot : Dir ,
82+ ) -> Result < StateLockGuard > {
3483 sysroot
3584 . atomic_write_with_perms ( Self :: WRITE_LOCK_PATH , "" , Permissions :: from_mode ( 0o644 ) )
3685 . context ( "Creating lock file" ) ?;
@@ -43,6 +92,7 @@ impl SavedState {
4392 lockfile. lock_exclusive ( ) . context ( "Acquiring lock" ) ?;
4493
4594 let guard = StateLockGuard {
95+ sysroot_path,
4696 sysroot,
4797 termguard : Some ( SignalTerminationGuard :: new ( ) ?) ,
4898 lockfile : Some ( lockfile) ,
@@ -52,8 +102,9 @@ impl SavedState {
52102
53103 /// Use this for cases when the target root isn't booted, which is
54104 /// offline installs.
55- pub ( crate ) fn unlocked ( sysroot : Dir ) -> Result < StateLockGuard > {
105+ pub ( crate ) fn unlocked ( sysroot_path : Utf8PathBuf , sysroot : Dir ) -> Result < StateLockGuard > {
56106 Ok ( StateLockGuard {
107+ sysroot_path,
57108 sysroot,
58109 termguard : None ,
59110 lockfile : None ,
@@ -67,70 +118,49 @@ impl SavedState {
67118 bootloader : Option < Bootloader > ,
68119 ) -> Result < Option < SavedState > > {
69120 let root_path = root_path. as_ref ( ) ;
70- let sysroot = Dir :: open_ambient_dir ( root_path, ambient_authority ( ) )
121+
122+ let root = Dir :: open_ambient_dir ( root_path, ambient_authority ( ) )
71123 . with_context ( || format ! ( "opening sysroot '{}'" , root_path. display( ) ) ) ?;
72124
73- let ( statefile , _esp_guard ) = match bootloader {
125+ match bootloader {
74126 Some ( b) => match b {
75127 Bootloader :: Grub => {
76128 let path = Path :: new ( Self :: STATEFILE_DIR ) . join ( Self :: STATEFILE_NAME ) ;
77- ( sysroot. open_optional ( & path) ?, None )
129+
130+ match root. open_optional ( & path) ? {
131+ Some ( f) => parse_statefile ( f) ,
132+ None => Ok ( None ) ,
133+ }
78134 }
79135
80136 Bootloader :: GrubCC => {
81137 let efi = Efi :: default ( ) ;
82138
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) ?;
139+ let device = get_parent_device ( & root) ?;
86140
87141 // Since we write the state file to all ESPs, it should be enough to get it
88142 // from the first one. Though, we could check the integrity by getting from
89143 // all the ESPs and making sure they're all the same...
90144 let esp = device. find_first_colocated_esp ( ) ?;
91145
92- let tmpdir = tempdir ( ) ?;
93- std:: fs:: create_dir_all ( tmpdir. path ( ) . join ( "efi" ) )
94- . context ( "Creating efi inside tmpdir" ) ?;
95-
146+ // According to BLS, the ESP should be mounted at /boot or /boot/efi
147+ // which the following method already checks
96148 let mounted = efi
97- . ensure_mounted_esp ( tmpdir . path ( ) , & Path :: new ( & esp. path ( ) ) )
149+ . ensure_mounted_esp ( & Path :: new ( "/" ) , & Path :: new ( & esp. path ( ) ) )
98150 . context ( "Mounting ESP" ) ?;
99151
100152 let dir = Dir :: open_ambient_dir ( & mounted, ambient_authority ( ) ) ?;
101153
102- ( dir. open_optional ( Self :: STATEFILE_NAME ) ?, Some ( efi) )
154+ match dir. open_optional ( Self :: STATEFILE_NAME ) ? {
155+ Some ( f) => parse_statefile ( f) ,
156+ None => Ok ( None ) ,
157+ }
103158 }
104159 } ,
105160
106161 // No bootloader, we're probably running inside a container
107- None => ( None , None ) ,
108- } ;
109-
110- let saved_state = if let Some ( statusf) = statefile {
111- let mut bufr = std:: io:: BufReader :: new ( statusf) ;
112- let mut s = String :: new ( ) ;
113- bufr. read_to_string ( & mut s) ?;
114- let state: serde_json:: Result < SavedState > = serde_json:: from_str ( s. as_str ( ) ) ;
115- let r = match state {
116- Ok ( s) => s,
117- Err ( orig_err) => {
118- let state: serde_json:: Result < crate :: model_legacy:: SavedState01 > =
119- serde_json:: from_str ( s. as_str ( ) ) ;
120- match state {
121- Ok ( s) => s. upconvert ( ) ,
122- Err ( _) => {
123- return Err ( orig_err. into ( ) ) ;
124- }
125- }
126- }
127- } ;
128- Some ( r)
129- } else {
130- None
131- } ;
132-
133- Ok ( saved_state)
162+ None => Ok ( None ) ,
163+ }
134164 }
135165
136166 /// Check whether statefile exists.
@@ -163,6 +193,7 @@ impl SavedState {
163193/// Write-lock guard for statefile, protecting against concurrent state updates.
164194#[ derive( Debug ) ]
165195pub ( crate ) struct StateLockGuard {
196+ pub ( crate ) sysroot_path : Utf8PathBuf ,
166197 pub ( crate ) sysroot : Dir ,
167198 #[ allow( dead_code) ]
168199 termguard : Option < SignalTerminationGuard > ,
@@ -192,33 +223,29 @@ impl StateLockGuard {
192223 return Ok ( ( ) ) ;
193224 }
194225
195- let dir = unsafe { Dir :: from_raw_fd ( self . sysroot . as_raw_fd ( ) ) } ;
196- let device = bootc_internal_blockdev:: list_dev_by_dir ( & dir) ?;
226+ let device = get_parent_device ( & self . sysroot ) ?;
197227 let all_esps = device
198228 . find_colocated_esps ( )
199229 . context ( "Searching for ESP" ) ?
200230 . ok_or_else ( || anyhow:: anyhow!( "ESP not found" ) ) ?;
201231
202232 let efi = Efi :: default ( ) ;
203233
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" ) ?;
234+ let serialized_state = serde_json:: to_vec ( state) . context ( "Serializing state" ) ?;
208235
209236 for esp in all_esps {
210237 let mounted = efi
211- . ensure_mounted_esp ( tmpdir . path ( ) , & Path :: new ( & esp. path ( ) ) )
238+ . ensure_mounted_esp ( & self . sysroot_path . as_std_path ( ) , & Path :: new ( & esp. path ( ) ) )
212239 . context ( "Mounting ESP" ) ?;
213240
214241 let dir = Dir :: open_ambient_dir ( & mounted, ambient_authority ( ) ) ?;
215242
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 )?;
243+ dir. atomic_write_with_perms (
244+ SavedState :: STATEFILE_NAME ,
245+ & serialized_state ,
246+ Permissions :: from_mode ( 0o644 ) ,
247+ )
248+ . context ( "Writing state file" ) ?;
222249
223250 // Do the sync before unmount
224251 fsfreeze_thaw_cycle ( dir. reopen_as_ownedfd ( ) ?) ?;
0 commit comments