Skip to content

Commit 9c406ec

Browse files
grub-cc: Handle statefile
For grub we store the statefile in `/sysroot/boot/bootupd.json`. For grub-cc, and in future systemd-boot, we won't have a `/boot`, so we now store the state pre bootloader. Grub2: Statefile is still in `/sysroot/boot/bootupd.json` GrubCC: Statefile is stored in all the ESPs Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
1 parent c8e466e commit 9c406ec

3 files changed

Lines changed: 147 additions & 31 deletions

File tree

src/backend/statefile.rs

Lines changed: 123 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
//! On-disk saved state.
22
3+
use crate::bootloader::Bootloader;
4+
use crate::efi::Efi;
5+
use crate::freezethaw::fsfreeze_thaw_cycle;
36
use crate::model::SavedState;
47
use crate::util::SignalTerminationGuard;
58
use anyhow::{bail, Context, Result};
@@ -10,7 +13,9 @@ use fn_error_context::context;
1013
use fs2::FileExt;
1114
use std::fs::File;
1215
use std::io::prelude::*;
16+
use std::os::fd::{AsRawFd, FromRawFd};
1317
use std::path::Path;
18+
use tempfile::tempdir;
1419

1520
impl SavedState {
1621
/// System-wide bootupd write lock (relative to sysroot).
@@ -57,13 +62,52 @@ impl SavedState {
5762

5863
/// Load the JSON file containing on-disk state.
5964
#[context("Loading saved state")]
60-
pub(crate) fn load_from_disk(root_path: impl AsRef<Path>) -> Result<Option<SavedState>> {
65+
pub(crate) fn load_from_disk(
66+
root_path: impl AsRef<Path>,
67+
bootloader: Option<Bootloader>,
68+
) -> Result<Option<SavedState>> {
6169
let root_path = root_path.as_ref();
6270
let sysroot = Dir::open_ambient_dir(root_path, ambient_authority())
6371
.with_context(|| format!("opening sysroot '{}'", root_path.display()))?;
6472

65-
let statefile_path = Path::new(Self::STATEFILE_DIR).join(Self::STATEFILE_NAME);
66-
let saved_state = if let Some(statusf) = sysroot.open_optional(&statefile_path)? {
73+
let (statefile, _esp_guard) = match bootloader {
74+
Some(b) => match b {
75+
Bootloader::Grub => {
76+
let path = Path::new(Self::STATEFILE_DIR).join(Self::STATEFILE_NAME);
77+
(sysroot.open_optional(&path)?, None)
78+
}
79+
80+
Bootloader::GrubCC => {
81+
let efi = Efi::default();
82+
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)?;
86+
87+
// Since we write the state file to all ESPs, it should be enough to get it
88+
// from the first one. Though, we could check the integrity by getting from
89+
// all the ESPs and making sure they're all the same...
90+
let esp = device.find_first_colocated_esp()?;
91+
92+
let tmpdir = tempdir()?;
93+
std::fs::create_dir_all(tmpdir.path().join("efi"))
94+
.context("Creating efi inside tmpdir")?;
95+
96+
let mounted = efi
97+
.ensure_mounted_esp(tmpdir.path(), &Path::new(&esp.path()))
98+
.context("Mounting ESP")?;
99+
100+
let dir = Dir::open_ambient_dir(&mounted, ambient_authority())?;
101+
102+
(dir.open_optional(Self::STATEFILE_NAME)?, Some(efi))
103+
}
104+
},
105+
106+
// No bootloader, we're probably running inside a container
107+
None => (None, None),
108+
};
109+
110+
let saved_state = if let Some(statusf) = statefile {
67111
let mut bufr = std::io::BufReader::new(statusf);
68112
let mut s = String::new();
69113
bufr.read_to_string(&mut s)?;
@@ -85,18 +129,34 @@ impl SavedState {
85129
} else {
86130
None
87131
};
132+
88133
Ok(saved_state)
89134
}
90135

91136
/// Check whether statefile exists.
92-
pub(crate) fn ensure_not_present(root_path: impl AsRef<Path>) -> Result<()> {
93-
let statepath = Path::new(root_path.as_ref())
94-
.join(Self::STATEFILE_DIR)
95-
.join(Self::STATEFILE_NAME);
96-
if statepath.exists() {
97-
bail!("{} already exists", statepath.display());
137+
pub(crate) fn ensure_not_present(
138+
root_path: impl AsRef<Path>,
139+
bootloader: Bootloader,
140+
) -> Result<()> {
141+
let saved_state = SavedState::load_from_disk(&root_path, Some(bootloader))?;
142+
143+
if saved_state.is_none() {
144+
return Ok(());
145+
}
146+
147+
match bootloader {
148+
Bootloader::Grub => {
149+
let statepath = Path::new(root_path.as_ref())
150+
.join(Self::STATEFILE_DIR)
151+
.join(Self::STATEFILE_NAME);
152+
153+
bail!("{} already exists", statepath.display());
154+
}
155+
156+
Bootloader::GrubCC => {
157+
bail!("{} already exists in the ESP", Self::STATEFILE_NAME);
158+
}
98159
}
99-
Ok(())
100160
}
101161
}
102162

@@ -112,16 +172,59 @@ pub(crate) struct StateLockGuard {
112172

113173
impl StateLockGuard {
114174
/// Atomically replace the on-disk state with a new version.
115-
pub(crate) fn update_state(&mut self, state: &SavedState) -> Result<()> {
116-
let subdir = self.sysroot.open_dir(SavedState::STATEFILE_DIR)?;
117-
118-
subdir
119-
.atomic_write_with_perms(
120-
SavedState::STATEFILE_NAME,
121-
serde_json::to_vec(state).context("Serializing state")?,
122-
Permissions::from_mode(0o644),
123-
)
124-
.context("Writing state file")?;
175+
#[context("Updating state")]
176+
pub(crate) fn update_state(
177+
&mut self,
178+
state: &SavedState,
179+
bootloader: Bootloader,
180+
) -> Result<()> {
181+
if bootloader == Bootloader::Grub {
182+
let subdir = self.sysroot.open_dir(SavedState::STATEFILE_DIR)?;
183+
184+
subdir
185+
.atomic_write_with_perms(
186+
SavedState::STATEFILE_NAME,
187+
serde_json::to_vec(state).context("Serializing state")?,
188+
Permissions::from_mode(0o644),
189+
)
190+
.context("Writing state file")?;
191+
192+
return Ok(());
193+
}
194+
195+
let dir = unsafe { Dir::from_raw_fd(self.sysroot.as_raw_fd()) };
196+
let device = bootc_internal_blockdev::list_dev_by_dir(&dir)?;
197+
let all_esps = device
198+
.find_colocated_esps()
199+
.context("Searching for ESP")?
200+
.ok_or_else(|| anyhow::anyhow!("ESP not found"))?;
201+
202+
let efi = Efi::default();
203+
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")?;
208+
209+
for esp in all_esps {
210+
let mounted = efi
211+
.ensure_mounted_esp(tmpdir.path(), &Path::new(&esp.path()))
212+
.context("Mounting ESP")?;
213+
214+
let dir = Dir::open_ambient_dir(&mounted, ambient_authority())?;
215+
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)?;
222+
223+
// Do the sync before unmount
224+
fsfreeze_thaw_cycle(dir.reopen_as_ownedfd()?)?;
225+
drop(dir);
226+
efi.unmount().context("unmount after update")?;
227+
}
125228

126229
Ok(())
127230
}

src/bootupd.rs

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#[cfg(any(target_arch = "x86_64", target_arch = "powerpc64"))]
22
use crate::bios;
3-
use crate::bootloader::Bootloader;
3+
use crate::bootloader::{get_bootloader, Bootloader};
44
use crate::component;
55
use crate::component::{Component, ValidationResult};
66
use crate::coreos;
@@ -19,6 +19,7 @@ use crate::freezethaw::fsfreeze_thaw_cycle;
1919
))]
2020
use crate::grubconfigs::{ensure_grub_permissions, GRUB2DIR};
2121
use crate::model::{ComponentStatus, ComponentUpdatable, ContentMetadata, SavedState, Status};
22+
use crate::util::running_in_container;
2223
use crate::{ostreeutil, util};
2324
use anyhow::{anyhow, Context, Result};
2425
use camino::{Utf8Path, Utf8PathBuf};
@@ -65,7 +66,7 @@ pub(crate) fn install(
6566
) -> Result<()> {
6667
let source_root_dir =
6768
Dir::open_ambient_dir(source_root, ambient_authority()).context("Opening source root")?;
68-
SavedState::ensure_not_present(dest_root)
69+
SavedState::ensure_not_present(dest_root, bootloader)
6970
.context("failed to install, invalid re-install attempted")?;
7071

7172
let all_components = get_components_impl(auto_components);
@@ -218,7 +219,7 @@ pub(crate) fn install(
218219
let mut state_guard =
219220
SavedState::unlocked(sysroot.try_clone()?).context("failed to acquire write lock")?;
220221
state_guard
221-
.update_state(&state)
222+
.update_state(&state, bootloader)
222223
.context("failed to update state")?;
223224

224225
Ok(())
@@ -332,7 +333,9 @@ fn ensure_writable_boot() -> Result<()> {
332333

333334
/// daemon implementation of component update
334335
pub(crate) fn update(name: &str, rootcxt: &RootContext) -> Result<ComponentUpdateResult> {
335-
let mut state = SavedState::load_from_disk("/")?.unwrap_or_default();
336+
let bootloader = get_bootloader()?;
337+
338+
let mut state = SavedState::load_from_disk("/", Some(bootloader))?.unwrap_or_default();
336339
let component = component::new_from_name(name)?;
337340
let inst = if let Some(inst) = state.installed.get(name) {
338341
inst.clone()
@@ -374,15 +377,15 @@ pub(crate) fn update(name: &str, rootcxt: &RootContext) -> Result<ComponentUpdat
374377
let mut state_guard =
375378
SavedState::acquire_write_lock(sysroot).context("Failed to acquire write lock")?;
376379
state_guard
377-
.update_state(&state)
380+
.update_state(&state, bootloader)
378381
.context("Failed to update state")?;
379382

380383
let newinst = component
381384
.run_update(rootcxt, &inst)
382385
.with_context(|| format!("Failed to update {}", component.name()))?;
383386
state.installed.insert(component.name().into(), newinst);
384387
pending_container.remove(component.name());
385-
state_guard.update_state(&state)?;
388+
state_guard.update_state(&state, bootloader)?;
386389

387390
Ok(ComponentUpdateResult::Updated {
388391
previous: inst.meta,
@@ -397,8 +400,9 @@ pub(crate) fn adopt_and_update(
397400
rootcxt: &RootContext,
398401
with_static_config: bool,
399402
) -> Result<Option<ContentMetadata>> {
403+
let bootloader = get_bootloader()?;
400404
let sysroot = &rootcxt.sysroot;
401-
let mut state = SavedState::load_from_disk("/")?.unwrap_or_default();
405+
let mut state = SavedState::load_from_disk("/", Some(bootloader))?.unwrap_or_default();
402406
let component = component::new_from_name(name)?;
403407
if state.installed.contains_key(name) {
404408
anyhow::bail!("Component {} is already installed", name);
@@ -441,7 +445,7 @@ pub(crate) fn adopt_and_update(
441445

442446
println!("Static GRUB configuration has been adopted successfully.");
443447
}
444-
state_guard.update_state(&state)?;
448+
state_guard.update_state(&state, bootloader)?;
445449
return Ok(Some(update));
446450
} else {
447451
// Nothing adopted, skip
@@ -468,7 +472,7 @@ fn list_dev_current_root() -> Result<Device> {
468472

469473
/// daemon implementation of component validate
470474
pub(crate) fn validate(name: &str) -> Result<ValidationResult> {
471-
let state = SavedState::load_from_disk("/")?.unwrap_or_default();
475+
let state = SavedState::load_from_disk("/", Some(get_bootloader()?))?.unwrap_or_default();
472476
let component = component::new_from_name(name)?;
473477
let Some(inst) = state.installed.get(name) else {
474478
anyhow::bail!("Component {} is not installed", name);
@@ -477,11 +481,20 @@ pub(crate) fn validate(name: &str) -> Result<ValidationResult> {
477481
component.validate(inst, &device)
478482
}
479483

484+
/// Impl for bootupctl status
485+
/// This function assumes we're not running in a container
480486
pub(crate) fn status() -> Result<Status> {
481487
let mut ret: Status = Default::default();
482488
let mut known_components = get_components();
483489
let sysroot = Dir::open_ambient_dir("/", ambient_authority())?;
484-
let state = SavedState::load_from_disk("/")?;
490+
491+
let bootloader = if running_in_container() {
492+
None
493+
} else {
494+
Some(get_bootloader()?)
495+
};
496+
497+
let state = SavedState::load_from_disk("/", bootloader)?;
485498
if let Some(state) = state {
486499
for (name, ic) in state.installed.iter() {
487500
log::trace!("Gathering status for installed component: {}", name);

src/efi.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ impl Efi {
179179
Ok(destdir)
180180
}
181181

182-
fn unmount(&self) -> Result<()> {
182+
pub(crate) fn unmount(&self) -> Result<()> {
183183
if let Some(mount) = self.mountpoint.borrow_mut().take() {
184184
Command::new("umount")
185185
.arg(&mount)

0 commit comments

Comments
 (0)