Skip to content

Commit dbf3e35

Browse files
committed
rauc: display reboot note based on if other slot would be booted
If you've installed a bundle you likely want to boot it, regardless of if it is newer or older than the one you are currently running. You also do not want to be nagged about rebooting into another slot if a reboot will only land you in the same slot you are running again. Signed-off-by: Leonard Göhrs <l.goehrs@pengutronix.de>
1 parent 4a2384c commit dbf3e35

File tree

1 file changed

+136
-34
lines changed

1 file changed

+136
-34
lines changed

src/dbus/rauc.rs

Lines changed: 136 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ mod imports {
6565

6666
#[cfg(not(feature = "demo_mode"))]
6767
mod imports {
68-
pub(super) use anyhow::{anyhow, bail, Result};
68+
pub(super) use anyhow::{bail, Result};
6969
pub(super) use log::error;
7070

7171
pub(super) const CHANNELS_DIR: &str = "/usr/share/tacd/update_channels";
@@ -118,43 +118,61 @@ fn compare_versions(v1: &str, v2: &str) -> Option<Ordering> {
118118
}
119119

120120
#[cfg(not(feature = "demo_mode"))]
121-
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> {
122122
let rootfs_0 = slot_status.get("rootfs_0");
123123
let rootfs_1 = slot_status.get("rootfs_1");
124124

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+
125129
let rootfs_0_booted = rootfs_0.and_then(|r| r.get("state")).map(|s| s == "booted");
126130
let rootfs_1_booted = rootfs_1.and_then(|r| r.get("state")).map(|s| s == "booted");
127131

128-
let (booted, other) = match (rootfs_0_booted, rootfs_1_booted) {
129-
(Some(true), Some(true)) => {
130-
bail!("Two booted RAUC slots at the same time");
131-
}
132-
(Some(true), _) => (rootfs_0, rootfs_1),
133-
(_, Some(true)) => (rootfs_1, rootfs_0),
134-
_ => {
135-
bail!("No booted RAUC slot");
136-
}
137-
};
138-
139-
// Not having version information for the booted slot is an error.
140-
let booted_version = booted
141-
.and_then(|r| r.get("bundle_version"))
142-
.ok_or(anyhow!("No bundle version information for booted slot"))?;
143-
144-
// Not having version information for the other slot just means that
145-
// it is not newer.
146-
if let Some(other_version) = other.and_then(|r| r.get("bundle_version")) {
147-
if let Some(rel) = compare_versions(other_version, booted_version) {
148-
Ok(rel.is_gt())
149-
} else {
150-
Err(anyhow!(
151-
"Failed to compare date for bundle versions \"{}\" and \"{}\"",
152-
other_version,
153-
booted_version
154-
))
155-
}
156-
} else {
157-
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"),
158176
}
159177
}
160178

@@ -321,7 +339,7 @@ impl Rauc {
321339
// (The one that should be booted next _if it is bootable_)
322340
let new_primary = proxy.get_primary().await.ok().map(|p| p.replace('.', "_"));
323341

324-
if let Some(p) = new_primary {
342+
if let Some(p) = new_primary.clone() {
325343
primary.set_if_changed(p);
326344
}
327345

@@ -385,7 +403,7 @@ impl Rauc {
385403

386404
// Provide a simple yes/no "should reboot into other slot?" information
387405
// based on the bundle versions in the booted slot and the other slot.
388-
match booted_older_than_other(&slots) {
406+
match would_reboot_into_other_slot(&slots, new_primary) {
389407
Ok(b) => should_reboot.set_if_changed(b),
390408
Err(e) => warn!("Could not determine if TAC should be rebooted: {e}"),
391409
}
@@ -477,3 +495,87 @@ impl Rauc {
477495
inst
478496
}
479497
}
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

Comments
 (0)