Skip to content

Commit 92e6eda

Browse files
authored
Merge pull request #89 from say-paul/preserve-boot-state
Preserve boot state
2 parents e646421 + 4b299bd commit 92e6eda

9 files changed

Lines changed: 228 additions & 87 deletions

File tree

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ vendor:
5454
vendor_filterer_cmd=$$(command -v cargo-vendor-filterer||:) \
5555
[ -z "$$vendor_filterer_cmd" ] || rm -f $${vendor_filterer_cmd}; \
5656
. /etc/os-release; \
57-
if [ "$$ID" = "fedora" ]; then \
57+
if [[ "$$ID" = "fedora" ]] || [[ "$$ID" = "centos" ]]; then \
5858
sudo dnf install -y pkgconf-pkg-config openssl-devel; \
5959
fi; \
6060
cargo install --quiet cargo-vendor-filterer@0.5.16; \

src/lib/grub.rs

Lines changed: 86 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
// SPDX-License-Identifier: BSD-3-Clause
22

33
use anyhow::{Context, Result, bail};
4-
use std::path::Path;
54
use std::process::Command;
65
use std::str;
76

8-
use crate::mount::{remount_boot_ro, remount_boot_rw};
7+
/// Shared GRUB environment path used by default helpers
8+
static GRUB_PATH: &str = "/boot/grub2/grubenv";
99

1010
/// fetches boot_counter value, none if not set
11-
pub fn get_boot_counter(grub_path: &str) -> Result<Option<i32>> {
11+
pub fn get_boot_counter() -> Result<Option<i32>> {
12+
get_boot_counter_at(GRUB_PATH)
13+
}
14+
15+
fn get_boot_counter_at(grub_path: &str) -> Result<Option<i32>> {
1216
let grub_vars = Command::new("grub2-editenv")
1317
.arg(grub_path)
1418
.arg("list")
@@ -33,8 +37,12 @@ pub fn get_boot_counter(grub_path: &str) -> Result<Option<i32>> {
3337
}
3438

3539
/// sets grub variable boot_counter if not set
36-
pub fn set_boot_counter(reboot_count: u16, grub_path: &str, mount_info_path: &str) -> Result<()> {
37-
match get_boot_counter(grub_path) {
40+
pub fn set_boot_counter(reboot_count: u16) -> Result<()> {
41+
set_boot_counter_at(reboot_count, GRUB_PATH)
42+
}
43+
44+
fn set_boot_counter_at(reboot_count: u16, grub_path: &str) -> Result<()> {
45+
match get_boot_counter_at(grub_path) {
3846
Ok(Some(i)) => {
3947
bail!("already set boot_counter={i}");
4048
}
@@ -48,36 +56,56 @@ pub fn set_boot_counter(reboot_count: u16, grub_path: &str, mount_info_path: &st
4856
}
4957

5058
log::info!("setting boot counter");
51-
set_grub_var("boot_counter", reboot_count, grub_path, mount_info_path)?;
59+
set_grub_var("boot_counter", reboot_count, grub_path)?;
5260
Ok(())
5361
}
5462
/// sets grub variable boot_success
55-
pub fn set_boot_status(success: bool, grub_path: &str, mount_info_path: &str) -> Result<()> {
63+
pub fn set_boot_status(success: bool) -> Result<()> {
64+
set_boot_status_at(success, GRUB_PATH)
65+
}
66+
67+
fn set_boot_status_at(success: bool, grub_path: &str) -> Result<()> {
5668
if success {
57-
set_grub_var("boot_success", 1, grub_path, mount_info_path)?;
58-
unset_boot_counter(grub_path, mount_info_path)?;
69+
set_grub_var("boot_success", 1, grub_path)?;
70+
unset_boot_counter_at(grub_path)?;
5971
return Ok(());
6072
}
61-
set_grub_var("boot_success", 0, grub_path, mount_info_path)
73+
set_grub_var("boot_success", 0, grub_path)
6274
}
6375

6476
/// unset boot_counter
65-
pub fn unset_boot_counter(grub_path: &str, mount_info_path: &str) -> Result<()> {
66-
unset_grub_var("boot_counter", grub_path, mount_info_path)
77+
pub fn unset_boot_counter() -> Result<()> {
78+
unset_boot_counter_at(GRUB_PATH)
79+
}
80+
81+
fn unset_boot_counter_at(grub_path: &str) -> Result<()> {
82+
unset_grub_var("boot_counter", grub_path)
6783
}
6884

6985
/// sets greenboot_rollback_trigger=1
70-
pub fn set_rollback_trigger(grub_path: &str, mount_info_path: &str) -> Result<()> {
71-
set_grub_var("greenboot_rollback_trigger", 1, grub_path, mount_info_path)
86+
pub fn set_rollback_trigger() -> Result<()> {
87+
set_rollback_trigger_at(GRUB_PATH)
88+
}
89+
90+
fn set_rollback_trigger_at(grub_path: &str) -> Result<()> {
91+
set_grub_var("greenboot_rollback_trigger", 1, grub_path)
7292
}
7393

7494
/// unsets greenboot_rollback_trigger
75-
pub fn unset_rollback_trigger(grub_path: &str, mount_info_path: &str) -> Result<()> {
76-
unset_grub_var("greenboot_rollback_trigger", grub_path, mount_info_path)
95+
pub fn unset_rollback_trigger() -> Result<()> {
96+
unset_rollback_trigger_at(GRUB_PATH)
97+
}
98+
99+
fn unset_rollback_trigger_at(grub_path: &str) -> Result<()> {
100+
unset_grub_var("greenboot_rollback_trigger", grub_path)
77101
}
78102

79103
/// gets greenboot_rollback_trigger value, returns true if set to 1
80-
pub fn get_rollback_trigger(grub_path: &str) -> Result<bool> {
104+
pub fn get_rollback_trigger() -> Result<bool> {
105+
get_rollback_trigger_at(GRUB_PATH)
106+
}
107+
108+
fn get_rollback_trigger_at(grub_path: &str) -> Result<bool> {
81109
let grub_vars = Command::new("grub2-editenv")
82110
.arg(grub_path)
83111
.arg("list")
@@ -94,44 +122,52 @@ pub fn get_rollback_trigger(grub_path: &str) -> Result<bool> {
94122
Ok(false) // Not set means false
95123
}
96124

97-
fn unset_grub_var(key: &str, grub_path: &str, mount_info_path: &str) -> Result<()> {
98-
remount_boot_rw(Path::new(mount_info_path)).context("Failed to remount /boot as rw")?;
99-
Command::new("grub2-editenv")
125+
fn unset_grub_var(key: &str, grub_path: &str) -> Result<()> {
126+
// Execute GRUB command and capture result
127+
let grub_result = Command::new("grub2-editenv")
100128
.arg(grub_path)
101129
.arg("unset")
102130
.arg(key)
103131
.status()
104132
.context("Unable to clear boot_counter")?;
133+
134+
if !grub_result.success() {
135+
bail!("Failed to unset grubenv key: {key}");
136+
}
137+
105138
log::info!("Clear grubenv: {key}");
106-
remount_boot_ro(Path::new(mount_info_path)).context("Failed to remount /boot as read-only")
139+
Ok(())
107140
}
108141

109-
fn set_grub_var(key: &str, val: u16, grub_path: &str, mount_info_path: &str) -> Result<()> {
110-
remount_boot_rw(Path::new(mount_info_path)).context("Failed to remount /boot as rw")?;
111-
Command::new("grub2-editenv")
142+
fn set_grub_var(key: &str, val: u16, grub_path: &str) -> Result<()> {
143+
// Execute GRUB command and capture result
144+
let grub_result = Command::new("grub2-editenv")
112145
.arg(grub_path)
113146
.arg("set")
114147
.arg(format!("{key}={val}"))
115148
.status()
116149
.context("Unable to set grubenv")?;
150+
151+
if !grub_result.success() {
152+
bail!("Failed to set grubenv key: {key}");
153+
}
154+
117155
log::info!("Set grubenv: {key}={val}");
118-
remount_boot_ro(Path::new(mount_info_path)).context("Failed to remount /boot as read-only")
156+
Ok(())
119157
}
120158

121159
#[cfg(test)]
122160
mod tests {
123161
use super::{
124-
get_boot_counter, get_rollback_trigger, set_boot_counter, set_rollback_trigger,
125-
unset_boot_counter, unset_rollback_trigger,
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,
126164
};
127165
use anyhow::Context;
128166
use std::fs;
129167
use std::process::Command;
130168
use tempfile::TempDir;
131169
use tempfile::tempdir;
132170

133-
static MOUNT_INFO_PATH: &str = "testing_assets/mounts";
134-
135171
fn setup_test_paths() -> (TempDir, String) {
136172
let temp_dir = tempdir().unwrap();
137173
let temp_grubenv = temp_dir.path().join("grubenv");
@@ -142,8 +178,8 @@ mod tests {
142178
#[test]
143179
fn test_boot_counter_set() {
144180
let (_temp_dir, grubenv) = setup_test_paths();
145-
set_boot_counter(10, &grubenv, MOUNT_INFO_PATH).unwrap();
146-
assert_eq!(get_boot_counter(&grubenv).unwrap(), Some(10));
181+
set_boot_counter_at(10, &grubenv).unwrap();
182+
assert_eq!(get_boot_counter_at(&grubenv).unwrap(), Some(10));
147183
}
148184

149185
#[test]
@@ -155,8 +191,8 @@ mod tests {
155191
.arg("boot_counter=99")
156192
.status()
157193
.context("Cannot create grub variable boot_counter");
158-
set_boot_counter(20, &grubenv, MOUNT_INFO_PATH).ok();
159-
assert_eq!(get_boot_counter(&grubenv).unwrap(), Some(99));
194+
set_boot_counter_at(20, &grubenv).ok();
195+
assert_eq!(get_boot_counter_at(&grubenv).unwrap(), Some(99));
160196
}
161197

162198
#[test]
@@ -168,8 +204,8 @@ mod tests {
168204
.arg("boot_counter=foo")
169205
.status()
170206
.context("Cannot create grub variable boot_counter");
171-
set_boot_counter(13, &grubenv, MOUNT_INFO_PATH).unwrap();
172-
assert_eq!(get_boot_counter(&grubenv).unwrap(), Some(13));
207+
set_boot_counter_at(13, &grubenv).unwrap();
208+
assert_eq!(get_boot_counter_at(&grubenv).unwrap(), Some(13));
173209
}
174210

175211
#[test]
@@ -181,8 +217,8 @@ mod tests {
181217
.arg("boot_counter=199")
182218
.status()
183219
.context("Cannot create grub variable boot_counter");
184-
unset_boot_counter(&grubenv, MOUNT_INFO_PATH).unwrap();
185-
assert_eq!(get_boot_counter(&grubenv).unwrap(), None);
220+
unset_boot_counter_at(&grubenv).unwrap();
221+
assert_eq!(get_boot_counter_at(&grubenv).unwrap(), None);
186222
}
187223

188224
#[test]
@@ -194,42 +230,42 @@ mod tests {
194230
.arg("boot_counter=99")
195231
.status()
196232
.context("Cannot create grub variable boot_counter");
197-
assert_eq!(get_boot_counter(&grubenv).unwrap(), Some(99));
233+
assert_eq!(get_boot_counter_at(&grubenv).unwrap(), Some(99));
198234
}
199235

200236
#[test]
201237
fn test_rollback_trigger_functions() {
202238
let (_temp_dir, grubenv) = setup_test_paths();
203239

204240
// Test when rollback trigger is not set
205-
assert!(!get_rollback_trigger(&grubenv).unwrap());
241+
assert!(!get_rollback_trigger_at(&grubenv).unwrap());
206242

207243
// Test setting rollback trigger
208-
set_rollback_trigger(&grubenv, MOUNT_INFO_PATH).unwrap();
209-
assert!(get_rollback_trigger(&grubenv).unwrap());
244+
set_rollback_trigger_at(&grubenv).unwrap();
245+
assert!(get_rollback_trigger_at(&grubenv).unwrap());
210246

211247
// Test unsetting rollback trigger
212-
unset_rollback_trigger(&grubenv, MOUNT_INFO_PATH).unwrap();
213-
assert!(!get_rollback_trigger(&grubenv).unwrap());
248+
unset_rollback_trigger_at(&grubenv).unwrap();
249+
assert!(!get_rollback_trigger_at(&grubenv).unwrap());
214250
}
215251

216252
#[test]
217253
fn test_rollback_trigger_with_other_vars() {
218254
let (_temp_dir, grubenv) = setup_test_paths();
219255

220256
// Set boot counter
221-
set_boot_counter(3, &grubenv, MOUNT_INFO_PATH).unwrap();
257+
set_boot_counter_at(3, &grubenv).unwrap();
222258

223259
// Set rollback trigger
224-
set_rollback_trigger(&grubenv, MOUNT_INFO_PATH).unwrap();
260+
set_rollback_trigger_at(&grubenv).unwrap();
225261

226262
// Both should coexist
227-
assert_eq!(get_boot_counter(&grubenv).unwrap(), Some(3));
228-
assert!(get_rollback_trigger(&grubenv).unwrap());
263+
assert_eq!(get_boot_counter_at(&grubenv).unwrap(), Some(3));
264+
assert!(get_rollback_trigger_at(&grubenv).unwrap());
229265

230266
// Unset rollback trigger, boot_counter should remain
231-
unset_rollback_trigger(&grubenv, MOUNT_INFO_PATH).unwrap();
232-
assert_eq!(get_boot_counter(&grubenv).unwrap(), Some(3));
233-
assert!(!get_rollback_trigger(&grubenv).unwrap());
267+
unset_rollback_trigger_at(&grubenv).unwrap();
268+
assert_eq!(get_boot_counter_at(&grubenv).unwrap(), Some(3));
269+
assert!(!get_rollback_trigger_at(&grubenv).unwrap());
234270
}
235271
}

src/lib/handler.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ fn detect_os_deployment() -> Option<&'static str> {
3636
/// reboots the system if boot_counter is greater than 0 or can be forced too
3737
pub fn handle_reboot(force: bool) -> Result<()> {
3838
if !force {
39-
let boot_counter = get_boot_counter("/boot/grub2/grubenv")?;
39+
let boot_counter = get_boot_counter()?;
4040
if boot_counter <= Some(0) {
4141
bail!("countdown ended, check greenboot-rollback status")
4242
};
@@ -48,7 +48,7 @@ pub fn handle_reboot(force: bool) -> Result<()> {
4848

4949
/// Rollback to the previous deployment if the boot counter allows.
5050
pub fn handle_rollback() -> Result<()> {
51-
let boot_counter = get_boot_counter("/boot/grub2/grubenv")?;
51+
let boot_counter = get_boot_counter()?;
5252

5353
match boot_counter {
5454
// Exit early if boot_counter is not set

0 commit comments

Comments
 (0)