@@ -82,54 +82,91 @@ fn unset_boot_counter_at(grub_path: &str) -> Result<()> {
8282 unset_grub_var ( "boot_counter" , grub_path)
8383}
8484
85- /// sets greenboot_rollback_trigger=1
86- pub fn set_rollback_trigger ( ) -> Result < ( ) > {
87- set_rollback_trigger_at ( GRUB_PATH )
85+ /// sets fallback=1 for GRUB-level kernel fallback protection
86+ pub fn set_fallback ( ) -> Result < ( ) > {
87+ set_fallback_at ( GRUB_PATH )
8888}
8989
90- fn set_rollback_trigger_at ( grub_path : & str ) -> Result < ( ) > {
91- set_grub_var ( "greenboot_rollback_trigger " , 1 , grub_path)
90+ fn set_fallback_at ( grub_path : & str ) -> Result < ( ) > {
91+ set_grub_var ( "fallback " , 1 , grub_path)
9292}
9393
94- /// unsets greenboot_rollback_trigger
95- pub fn unset_rollback_trigger ( ) -> Result < ( ) > {
96- unset_rollback_trigger_at ( GRUB_PATH )
94+ /// unsets the fallback grub variable
95+ pub fn unset_fallback ( ) -> Result < ( ) > {
96+ unset_fallback_at ( GRUB_PATH )
9797}
9898
99- fn unset_rollback_trigger_at ( grub_path : & str ) -> Result < ( ) > {
100- unset_grub_var ( "greenboot_rollback_trigger " , grub_path)
99+ fn unset_fallback_at ( grub_path : & str ) -> Result < ( ) > {
100+ unset_grub_var ( "fallback " , grub_path)
101101}
102102
103- /// gets greenboot_rollback_trigger value, returns true if set to 1
104- pub fn get_rollback_trigger ( ) -> Result < bool > {
105- get_rollback_trigger_at ( GRUB_PATH )
103+ /// gets fallback value, returns true if set to 1
104+ pub fn get_fallback ( ) -> Result < bool > {
105+ get_fallback_at ( GRUB_PATH )
106106}
107107
108- fn get_rollback_trigger_at ( grub_path : & str ) -> Result < bool > {
108+ fn get_fallback_at ( grub_path : & str ) -> Result < bool > {
109+ get_grub_bool_var ( "fallback" , grub_path)
110+ }
111+
112+ /// sets greenboot_next_deployment_id to the given deployment ID string
113+ pub fn set_next_deployment_id ( id : & str ) -> Result < ( ) > {
114+ set_next_deployment_id_at ( id, GRUB_PATH )
115+ }
116+
117+ fn set_next_deployment_id_at ( id : & str , grub_path : & str ) -> Result < ( ) > {
118+ set_grub_str_var ( "greenboot_next_deployment_id" , id, grub_path)
119+ }
120+
121+ /// unsets greenboot_next_deployment_id
122+ pub fn unset_next_deployment_id ( ) -> Result < ( ) > {
123+ unset_next_deployment_id_at ( GRUB_PATH )
124+ }
125+
126+ fn unset_next_deployment_id_at ( grub_path : & str ) -> Result < ( ) > {
127+ unset_grub_var ( "greenboot_next_deployment_id" , grub_path)
128+ }
129+
130+ /// returns the stored deployment ID, or None if not set
131+ pub fn get_next_deployment_id ( ) -> Result < Option < String > > {
132+ get_next_deployment_id_at ( GRUB_PATH )
133+ }
134+
135+ fn get_next_deployment_id_at ( grub_path : & str ) -> Result < Option < String > > {
136+ get_grub_str_var ( "greenboot_next_deployment_id" , grub_path)
137+ }
138+
139+ fn get_grub_bool_var ( key : & str , grub_path : & str ) -> Result < bool > {
109140 let grub_vars = Command :: new ( "grub2-editenv" )
110141 . arg ( grub_path)
111142 . arg ( "list" )
112143 . output ( )
113- . context ( "Unable to list grubenv variables" ) ?;
144+ . context ( format ! ( "Unable to list grubenv variables for key: {key}" ) ) ?;
145+
146+ if !grub_vars. status . success ( ) {
147+ bail ! (
148+ "grub2-editenv failed to list variables: {}" ,
149+ String :: from_utf8_lossy( & grub_vars. stderr)
150+ ) ;
151+ }
114152
153+ let prefix = format ! ( "{key}=" ) ;
115154 let output = String :: from_utf8_lossy ( & grub_vars. stdout ) ;
116155 for line in output. lines ( ) {
117- if line. starts_with ( "greenboot_rollback_trigger=" ) {
118- let value = line. split ( '=' ) . nth ( 1 ) . unwrap_or ( "0" ) ;
156+ if let Some ( value) = line. strip_prefix ( & prefix) {
119157 return Ok ( value == "1" ) ;
120158 }
121159 }
122- Ok ( false ) // Not set means false
160+ Ok ( false )
123161}
124162
125163fn unset_grub_var ( key : & str , grub_path : & str ) -> Result < ( ) > {
126- // Execute GRUB command and capture result
127164 let grub_result = Command :: new ( "grub2-editenv" )
128165 . arg ( grub_path)
129166 . arg ( "unset" )
130167 . arg ( key)
131168 . status ( )
132- . context ( "Unable to clear boot_counter" ) ?;
169+ . context ( format ! ( "Unable to unset grubenv key: {key}" ) ) ?;
133170
134171 if !grub_result. success ( ) {
135172 bail ! ( "Failed to unset grubenv key: {key}" ) ;
@@ -156,11 +193,56 @@ fn set_grub_var(key: &str, val: u16, grub_path: &str) -> Result<()> {
156193 Ok ( ( ) )
157194}
158195
196+ fn set_grub_str_var ( key : & str , val : & str , grub_path : & str ) -> Result < ( ) > {
197+ let grub_result = Command :: new ( "grub2-editenv" )
198+ . arg ( grub_path)
199+ . arg ( "set" )
200+ . arg ( format ! ( "{key}={val}" ) )
201+ . status ( )
202+ . context ( "Unable to set grubenv" ) ?;
203+
204+ if !grub_result. success ( ) {
205+ bail ! ( "Failed to set grubenv key: {key}" ) ;
206+ }
207+
208+ log:: info!( "Set grubenv: {key}={val}" ) ;
209+ Ok ( ( ) )
210+ }
211+
212+ fn get_grub_str_var ( key : & str , grub_path : & str ) -> Result < Option < String > > {
213+ let grub_vars = Command :: new ( "grub2-editenv" )
214+ . arg ( grub_path)
215+ . arg ( "list" )
216+ . output ( )
217+ . context ( format ! ( "Unable to list grubenv variables for key: {key}" ) ) ?;
218+
219+ if !grub_vars. status . success ( ) {
220+ bail ! (
221+ "grub2-editenv failed to list variables: {}" ,
222+ String :: from_utf8_lossy( & grub_vars. stderr)
223+ ) ;
224+ }
225+
226+ let prefix = format ! ( "{key}=" ) ;
227+ let output = String :: from_utf8_lossy ( & grub_vars. stdout ) ;
228+ for line in output. lines ( ) {
229+ if let Some ( value) = line. strip_prefix ( & prefix) {
230+ let trimmed = value. trim ( ) ;
231+ if trimmed. is_empty ( ) {
232+ return Ok ( None ) ;
233+ }
234+ return Ok ( Some ( trimmed. to_string ( ) ) ) ;
235+ }
236+ }
237+ Ok ( None )
238+ }
239+
159240#[ cfg( test) ]
160241mod tests {
161242 use super :: {
162- get_boot_counter_at, get_rollback_trigger_at, set_boot_counter_at, set_rollback_trigger_at,
163- unset_boot_counter_at, unset_rollback_trigger_at,
243+ get_boot_counter_at, get_fallback_at, get_next_deployment_id_at, set_boot_counter_at,
244+ set_fallback_at, set_next_deployment_id_at, unset_boot_counter_at, unset_fallback_at,
245+ unset_next_deployment_id_at,
164246 } ;
165247 use anyhow:: Context ;
166248 use std:: fs;
@@ -234,38 +316,122 @@ mod tests {
234316 }
235317
236318 #[ test]
237- fn test_rollback_trigger_functions ( ) {
319+ fn test_next_deployment_id_set_and_get ( ) {
238320 let ( _temp_dir, grubenv) = setup_test_paths ( ) ;
239321
240- // Test when rollback trigger is not set
241- assert ! ( !get_rollback_trigger_at( & grubenv) . unwrap( ) ) ;
322+ assert_eq ! ( get_next_deployment_id_at( & grubenv) . unwrap( ) , None ) ;
323+
324+ let id = "sha256:abc123def456" ;
325+ set_next_deployment_id_at ( id, & grubenv) . unwrap ( ) ;
326+ assert_eq ! (
327+ get_next_deployment_id_at( & grubenv) . unwrap( ) ,
328+ Some ( id. to_string( ) )
329+ ) ;
330+ }
331+
332+ #[ test]
333+ fn test_next_deployment_id_unset ( ) {
334+ let ( _temp_dir, grubenv) = setup_test_paths ( ) ;
242335
243- // Test setting rollback trigger
244- set_rollback_trigger_at ( & grubenv) . unwrap ( ) ;
245- assert ! ( get_rollback_trigger_at ( & grubenv) . unwrap( ) ) ;
336+ let id = "sha256:abc123def456" ;
337+ set_next_deployment_id_at ( id , & grubenv) . unwrap ( ) ;
338+ assert ! ( get_next_deployment_id_at ( & grubenv) . unwrap( ) . is_some ( ) ) ;
246339
247- // Test unsetting rollback trigger
248- unset_rollback_trigger_at ( & grubenv) . unwrap ( ) ;
249- assert ! ( !get_rollback_trigger_at( & grubenv) . unwrap( ) ) ;
340+ unset_next_deployment_id_at ( & grubenv) . unwrap ( ) ;
341+ assert_eq ! ( get_next_deployment_id_at( & grubenv) . unwrap( ) , None ) ;
250342 }
251343
252344 #[ test]
253- fn test_rollback_trigger_with_other_vars ( ) {
345+ fn test_next_deployment_id_coexists_with_boot_counter ( ) {
254346 let ( _temp_dir, grubenv) = setup_test_paths ( ) ;
255347
256- // Set boot counter
348+ let id = "sha256:abc123def456" ;
257349 set_boot_counter_at ( 3 , & grubenv) . unwrap ( ) ;
350+ set_next_deployment_id_at ( id, & grubenv) . unwrap ( ) ;
258351
259- // Set rollback trigger
260- set_rollback_trigger_at ( & grubenv) . unwrap ( ) ;
352+ assert_eq ! ( get_boot_counter_at( & grubenv) . unwrap( ) , Some ( 3 ) ) ;
353+ assert_eq ! (
354+ get_next_deployment_id_at( & grubenv) . unwrap( ) ,
355+ Some ( id. to_string( ) )
356+ ) ;
261357
262- // Both should coexist
358+ unset_next_deployment_id_at ( & grubenv ) . unwrap ( ) ;
263359 assert_eq ! ( get_boot_counter_at( & grubenv) . unwrap( ) , Some ( 3 ) ) ;
264- assert ! ( get_rollback_trigger_at( & grubenv) . unwrap( ) ) ;
360+ assert_eq ! ( get_next_deployment_id_at( & grubenv) . unwrap( ) , None ) ;
361+ }
362+
363+ #[ test]
364+ fn test_next_deployment_id_coexists_with_fallback ( ) {
365+ let ( _temp_dir, grubenv) = setup_test_paths ( ) ;
366+
367+ let id = "sha256:abc123def456" ;
368+ set_next_deployment_id_at ( id, & grubenv) . unwrap ( ) ;
369+ set_fallback_at ( & grubenv) . unwrap ( ) ;
370+
371+ assert_eq ! (
372+ get_next_deployment_id_at( & grubenv) . unwrap( ) ,
373+ Some ( id. to_string( ) )
374+ ) ;
375+ assert ! ( get_fallback_at( & grubenv) . unwrap( ) ) ;
376+
377+ unset_next_deployment_id_at ( & grubenv) . unwrap ( ) ;
378+ assert_eq ! ( get_next_deployment_id_at( & grubenv) . unwrap( ) , None ) ;
379+ assert ! ( get_fallback_at( & grubenv) . unwrap( ) ) ;
380+ }
381+
382+ #[ test]
383+ fn test_fallback_set_and_get ( ) {
384+ let ( _temp_dir, grubenv) = setup_test_paths ( ) ;
385+
386+ assert ! ( !get_fallback_at( & grubenv) . unwrap( ) ) ;
387+
388+ set_fallback_at ( & grubenv) . unwrap ( ) ;
389+ assert ! ( get_fallback_at( & grubenv) . unwrap( ) ) ;
390+ }
391+
392+ #[ test]
393+ fn test_fallback_unset ( ) {
394+ let ( _temp_dir, grubenv) = setup_test_paths ( ) ;
395+
396+ set_fallback_at ( & grubenv) . unwrap ( ) ;
397+ assert ! ( get_fallback_at( & grubenv) . unwrap( ) ) ;
398+
399+ unset_fallback_at ( & grubenv) . unwrap ( ) ;
400+ assert ! ( !get_fallback_at( & grubenv) . unwrap( ) ) ;
401+ }
402+
403+ #[ test]
404+ fn test_fallback_unset_when_not_set ( ) {
405+ let ( _temp_dir, grubenv) = setup_test_paths ( ) ;
406+
407+ unset_fallback_at ( & grubenv) . unwrap ( ) ;
408+ assert ! ( !get_fallback_at( & grubenv) . unwrap( ) ) ;
409+ }
410+
411+ #[ test]
412+ fn test_fallback_coexists_with_boot_counter ( ) {
413+ let ( _temp_dir, grubenv) = setup_test_paths ( ) ;
414+
415+ set_boot_counter_at ( 3 , & grubenv) . unwrap ( ) ;
416+ set_fallback_at ( & grubenv) . unwrap ( ) ;
265417
266- // Unset rollback trigger, boot_counter should remain
267- unset_rollback_trigger_at ( & grubenv) . unwrap ( ) ;
268418 assert_eq ! ( get_boot_counter_at( & grubenv) . unwrap( ) , Some ( 3 ) ) ;
269- assert ! ( !get_rollback_trigger_at( & grubenv) . unwrap( ) ) ;
419+ assert ! ( get_fallback_at( & grubenv) . unwrap( ) ) ;
420+ }
421+
422+ #[ test]
423+ fn test_fallback_independent_of_deployment_id ( ) {
424+ let ( _temp_dir, grubenv) = setup_test_paths ( ) ;
425+
426+ let id = "sha256:abc123def456" ;
427+ set_next_deployment_id_at ( id, & grubenv) . unwrap ( ) ;
428+ set_fallback_at ( & grubenv) . unwrap ( ) ;
429+
430+ unset_next_deployment_id_at ( & grubenv) . unwrap ( ) ;
431+ assert_eq ! ( get_next_deployment_id_at( & grubenv) . unwrap( ) , None ) ;
432+ assert ! ( get_fallback_at( & grubenv) . unwrap( ) ) ;
433+
434+ unset_fallback_at ( & grubenv) . unwrap ( ) ;
435+ assert ! ( !get_fallback_at( & grubenv) . unwrap( ) ) ;
270436 }
271437}
0 commit comments