@@ -65,7 +65,7 @@ mod imports {
65
65
66
66
#[ cfg( not( feature = "demo_mode" ) ) ]
67
67
mod imports {
68
- pub ( super ) use anyhow:: { anyhow , bail, Result } ;
68
+ pub ( super ) use anyhow:: { bail, Result } ;
69
69
pub ( super ) use log:: error;
70
70
71
71
pub ( super ) const CHANNELS_DIR : & str = "/usr/share/tacd/update_channels" ;
@@ -98,6 +98,7 @@ pub struct Rauc {
98
98
pub operation : Arc < Topic < String > > ,
99
99
pub progress : Arc < Topic < Progress > > ,
100
100
pub slot_status : Arc < Topic < Arc < SlotStatus > > > ,
101
+ pub primary : Arc < Topic < String > > ,
101
102
pub last_error : Arc < Topic < String > > ,
102
103
pub install : Arc < Topic < String > > ,
103
104
pub channels : Arc < Topic < Vec < Channel > > > ,
@@ -117,43 +118,61 @@ fn compare_versions(v1: &str, v2: &str) -> Option<Ordering> {
117
118
}
118
119
119
120
#[ cfg( not( feature = "demo_mode" ) ) ]
120
- fn booted_older_than_other ( slot_status : & SlotStatus ) -> Result < bool > {
121
+ fn would_reboot_into_other_slot ( slot_status : & SlotStatus , primary : Option < String > ) -> Result < bool > {
121
122
let rootfs_0 = slot_status. get ( "rootfs_0" ) ;
122
123
let rootfs_1 = slot_status. get ( "rootfs_1" ) ;
123
124
125
+ let ( rootfs_0_is_primary, rootfs_1_is_primary) = primary
126
+ . map ( |p| ( p == "rootfs_0" , p == "rootfs_1" ) )
127
+ . unwrap_or ( ( false , false ) ) ;
128
+
124
129
let rootfs_0_booted = rootfs_0. and_then ( |r| r. get ( "state" ) ) . map ( |s| s == "booted" ) ;
125
130
let rootfs_1_booted = rootfs_1. and_then ( |r| r. get ( "state" ) ) . map ( |s| s == "booted" ) ;
126
131
127
- let ( booted, other) = match ( rootfs_0_booted, rootfs_1_booted) {
128
- ( Some ( true ) , Some ( true ) ) => {
129
- bail ! ( "Two booted RAUC slots at the same time" ) ;
130
- }
131
- ( Some ( true ) , _) => ( rootfs_0, rootfs_1) ,
132
- ( _, Some ( true ) ) => ( rootfs_1, rootfs_0) ,
133
- _ => {
134
- bail ! ( "No booted RAUC slot" ) ;
135
- }
136
- } ;
137
-
138
- // Not having version information for the booted slot is an error.
139
- let booted_version = booted
140
- . and_then ( |r| r. get ( "bundle_version" ) )
141
- . ok_or ( anyhow ! ( "No bundle version information for booted slot" ) ) ?;
142
-
143
- // Not having version information for the other slot just means that
144
- // it is not newer.
145
- if let Some ( other_version) = other. and_then ( |r| r. get ( "bundle_version" ) ) {
146
- if let Some ( rel) = compare_versions ( other_version, booted_version) {
147
- Ok ( rel. is_gt ( ) )
148
- } else {
149
- Err ( anyhow ! (
150
- "Failed to compare date for bundle versions \" {}\" and \" {}\" " ,
151
- other_version,
152
- booted_version
153
- ) )
154
- }
155
- } else {
156
- Ok ( false )
132
+ let ( ( booted_slot, booted_is_primary) , ( other_slot, other_is_primary) ) =
133
+ match ( rootfs_0_booted, rootfs_1_booted) {
134
+ ( Some ( true ) , Some ( true ) ) => {
135
+ bail ! ( "Two booted RAUC slots at the same time" ) ;
136
+ }
137
+ ( Some ( true ) , _) => (
138
+ ( rootfs_0, rootfs_0_is_primary) ,
139
+ ( rootfs_1, rootfs_1_is_primary) ,
140
+ ) ,
141
+ ( _, Some ( true ) ) => (
142
+ ( rootfs_1, rootfs_1_is_primary) ,
143
+ ( rootfs_0, rootfs_0_is_primary) ,
144
+ ) ,
145
+ _ => {
146
+ bail ! ( "No booted RAUC slot" ) ;
147
+ }
148
+ } ;
149
+
150
+ let booted_good = booted_slot
151
+ . and_then ( |r| r. get ( "boot_status" ) )
152
+ . map ( |s| s == "good" ) ;
153
+ let other_good = other_slot
154
+ . and_then ( |r| r. get ( "boot_status" ) )
155
+ . map ( |s| s == "good" ) ;
156
+
157
+ let booted_ok = booted_slot. and_then ( |r| r. get ( "status" ) ) . map ( |s| s == "ok" ) ;
158
+ let other_ok = other_slot. and_then ( |r| r. get ( "status" ) ) . map ( |s| s == "ok" ) ;
159
+
160
+ let booted_viable = booted_good. unwrap_or ( false ) && booted_ok. unwrap_or ( false ) ;
161
+ let other_viable = other_good. unwrap_or ( false ) && other_ok. unwrap_or ( false ) ;
162
+
163
+ match (
164
+ booted_viable,
165
+ other_viable,
166
+ booted_is_primary,
167
+ other_is_primary,
168
+ ) {
169
+ ( true , false , _, _) => Ok ( false ) ,
170
+ ( false , true , _, _) => Ok ( true ) ,
171
+ ( true , true , true , false ) => Ok ( false ) ,
172
+ ( true , true , false , true ) => Ok ( true ) ,
173
+ ( false , false , _, _) => bail ! ( "No bootable slot present" ) ,
174
+ ( _, _, false , false ) => bail ! ( "No primary slot present" ) ,
175
+ ( true , true , true , true ) => bail ! ( "Two primary slots present" ) ,
157
176
}
158
177
}
159
178
@@ -265,6 +284,7 @@ impl Rauc {
265
284
operation : bb. topic_ro ( "/v1/tac/update/operation" , None ) ,
266
285
progress : bb. topic_ro ( "/v1/tac/update/progress" , None ) ,
267
286
slot_status : bb. topic_ro ( "/v1/tac/update/slots" , None ) ,
287
+ primary : bb. topic_ro ( "/v1/tac/update/primary" , None ) ,
268
288
last_error : bb. topic_ro ( "/v1/tac/update/last_error" , None ) ,
269
289
install : bb. topic_wo ( "/v1/tac/update/install" , Some ( "" . to_string ( ) ) ) ,
270
290
channels : bb. topic_ro ( "/v1/tac/update/channels" , None ) ,
@@ -300,6 +320,7 @@ impl Rauc {
300
320
let conn_task = conn. clone ( ) ;
301
321
let operation = inst. operation . clone ( ) ;
302
322
let slot_status = inst. slot_status . clone ( ) ;
323
+ let primary = inst. primary . clone ( ) ;
303
324
let channels = inst. channels . clone ( ) ;
304
325
let should_reboot = inst. should_reboot . clone ( ) ;
305
326
@@ -313,6 +334,15 @@ impl Rauc {
313
334
}
314
335
315
336
loop {
337
+ // Update which slot is considered the primary whenever the current
338
+ // operation changes.
339
+ // (The one that should be booted next _if it is bootable_)
340
+ let new_primary = proxy. get_primary ( ) . await . ok ( ) . map ( |p| p. replace ( '.' , "_" ) ) ;
341
+
342
+ if let Some ( p) = new_primary. clone ( ) {
343
+ primary. set_if_changed ( p) ;
344
+ }
345
+
316
346
// Referesh the slot status whenever the current operation changes
317
347
// This is mostly relevant for "installing" -> "idle" transitions
318
348
// but it can't hurt to do it on any transition.
@@ -373,7 +403,7 @@ impl Rauc {
373
403
374
404
// Provide a simple yes/no "should reboot into other slot?" information
375
405
// based on the bundle versions in the booted slot and the other slot.
376
- match booted_older_than_other ( & slots) {
406
+ match would_reboot_into_other_slot ( & slots, new_primary ) {
377
407
Ok ( b) => should_reboot. set_if_changed ( b) ,
378
408
Err ( e) => warn ! ( "Could not determine if TAC should be rebooted: {e}" ) ,
379
409
}
@@ -465,3 +495,87 @@ impl Rauc {
465
495
inst
466
496
}
467
497
}
498
+
499
+ #[ cfg( test) ]
500
+ mod tests {
501
+ use std:: collections:: HashMap ;
502
+
503
+ use super :: { would_reboot_into_other_slot, SlotStatus } ;
504
+
505
+ #[ test]
506
+ fn reboot_notifications ( ) {
507
+ let bootable = HashMap :: from ( [
508
+ ( "boot_status" . to_string ( ) , "good" . to_string ( ) ) ,
509
+ ( "status" . to_string ( ) , "ok" . to_string ( ) ) ,
510
+ ] ) ;
511
+
512
+ let not_bootable = HashMap :: from ( [
513
+ ( "boot_status" . to_string ( ) , "bad" . to_string ( ) ) ,
514
+ ( "status" . to_string ( ) , "ok" . to_string ( ) ) ,
515
+ ] ) ;
516
+
517
+ let cases = [
518
+ ( bootable. clone ( ) , bootable. clone ( ) , 0 , 1 , Ok ( true ) ) ,
519
+ ( bootable. clone ( ) , bootable. clone ( ) , 1 , 0 , Ok ( true ) ) ,
520
+ ( bootable. clone ( ) , bootable. clone ( ) , 0 , 0 , Ok ( false ) ) ,
521
+ ( bootable. clone ( ) , bootable. clone ( ) , 1 , 1 , Ok ( false ) ) ,
522
+ ( not_bootable. clone ( ) , bootable. clone ( ) , 1 , 0 , Ok ( false ) ) ,
523
+ ( bootable. clone ( ) , not_bootable. clone ( ) , 0 , 1 , Ok ( false ) ) ,
524
+ ( not_bootable. clone ( ) , bootable. clone ( ) , 0 , 0 , Ok ( true ) ) ,
525
+ ( bootable. clone ( ) , not_bootable. clone ( ) , 1 , 1 , Ok ( true ) ) ,
526
+ ( not_bootable. clone ( ) , not_bootable. clone ( ) , 0 , 1 , Err ( ( ) ) ) ,
527
+ ( bootable. clone ( ) , bootable. clone ( ) , 2 , 0 , Err ( ( ) ) ) ,
528
+ ( bootable. clone ( ) , bootable. clone ( ) , 0 , 2 , Err ( ( ) ) ) ,
529
+ ] ;
530
+
531
+ for ( mut rootfs_0, mut rootfs_1, booted, primary, expected) in cases {
532
+ let slots = {
533
+ rootfs_0. insert (
534
+ "state" . to_string ( ) ,
535
+ if booted == 0 {
536
+ "booted" . to_string ( )
537
+ } else {
538
+ "inactive" . to_string ( )
539
+ } ,
540
+ ) ;
541
+
542
+ rootfs_1. insert (
543
+ "state" . to_string ( ) ,
544
+ if booted == 1 {
545
+ "booted" . to_string ( )
546
+ } else {
547
+ "inactive" . to_string ( )
548
+ } ,
549
+ ) ;
550
+
551
+ SlotStatus :: from ( [
552
+ ( "rootfs_0" . to_string ( ) , rootfs_0) ,
553
+ ( "rootfs_1" . to_string ( ) , rootfs_1) ,
554
+ ] )
555
+ } ;
556
+
557
+ let primary = Some ( format ! ( "rootfs_{primary}" ) ) ;
558
+
559
+ let res = would_reboot_into_other_slot ( & slots, primary. clone ( ) ) ;
560
+
561
+ match ( res, expected) {
562
+ ( Ok ( true ) , Ok ( true ) ) | ( Ok ( false ) , Ok ( false ) ) | ( Err ( _) , Err ( _) ) => { }
563
+ ( Ok ( r) , Ok ( e) ) => {
564
+ eprintln ! (
565
+ "Slot status {slots:?} with primary {primary:?} yielded wrong result"
566
+ ) ;
567
+ assert_eq ! ( r, e) ;
568
+ }
569
+ ( Err ( e) , Ok ( _) ) => {
570
+ eprintln ! (
571
+ "Slot status {slots:?} with primary {primary:?} returned unexpected error"
572
+ ) ;
573
+ panic ! ( "{:?}" , e) ;
574
+ }
575
+ ( Ok ( res) , Err ( _) ) => {
576
+ panic ! ( "Slot status {:?} with primary {:?} returned Ok({})) but should have errored" , slots, primary, res) ;
577
+ }
578
+ }
579
+ }
580
+ }
581
+ }
0 commit comments