Skip to content

Commit c8bd774

Browse files
state: Handle state file for GrubCC
Store the sysroot_path in StateLockGuard which we require for mounting the ESP. Also, prevent mounting ESP at a tempdir, instead just mount it at `/boot` which is what the BLS spec suggest Fix issue with composefs systems where lsblk would fail to find the backing device for "/" due to it being mounted as a virtual filesystem. Instead we now use `/sysroot` or `/boot` to find the baking device Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
1 parent 4b1b90e commit c8bd774

2 files changed

Lines changed: 93 additions & 70 deletions

File tree

src/backend/statefile.rs

Lines changed: 84 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,85 @@
11
//! On-disk saved state.
22
33
use crate::bootloader::Bootloader;
4+
use crate::bootupd::list_dev_current_root;
45
use crate::efi::Efi;
56
use crate::freezethaw::fsfreeze_thaw_cycle;
67
use crate::model::SavedState;
78
use crate::util::SignalTerminationGuard;
89
use anyhow::{bail, Context, Result};
10+
use bootc_internal_blockdev::Device;
11+
use camino::Utf8PathBuf;
912
use cap_std::ambient_authority;
1013
use cap_std::fs::{Dir, Permissions, PermissionsExt};
1114
use cap_std_ext::dirext::CapStdExtDirExt;
1215
use fn_error_context::context;
1316
use fs2::FileExt;
1417
use std::fs::File;
1518
use std::io::prelude::*;
16-
use std::os::fd::{AsRawFd, FromRawFd};
1719
use std::path::Path;
18-
use tempfile::tempdir;
20+
21+
fn parse_statefile(statusf: cap_std::fs::File) -> Result<Option<SavedState>> {
22+
let mut bufr = std::io::BufReader::new(statusf);
23+
let mut s = String::new();
24+
bufr.read_to_string(&mut s)?;
25+
let state: serde_json::Result<SavedState> = serde_json::from_str(s.as_str());
26+
27+
let r = match state {
28+
Ok(s) => s,
29+
Err(orig_err) => {
30+
let state: serde_json::Result<crate::model_legacy::SavedState01> =
31+
serde_json::from_str(s.as_str());
32+
match state {
33+
Ok(s) => s.upconvert(),
34+
Err(_) => {
35+
return Err(orig_err.into());
36+
}
37+
}
38+
}
39+
};
40+
41+
Ok(Some(r))
42+
}
43+
44+
/// lsblk: composefs:abc123..: not a block device
45+
/// is what lsblk throws on composefs booted systems if we try to
46+
/// get block devices using "/"
47+
///
48+
/// First, try to get the device from the `root` which is necessary
49+
/// during installs as we don't want (or can't) to open up /sysroot or /boot
50+
///
51+
/// If that fails, it means we're not on the install path so we get the
52+
/// device from checking mount point from /boot or /sysroot
53+
#[context("Getting parent device")]
54+
fn get_parent_device(root: &Dir) -> Result<Device> {
55+
match bootc_internal_blockdev::list_dev_by_dir(&root) {
56+
Ok(d) => Ok(d),
57+
Err(e) => {
58+
// Not really an error just yet
59+
log::debug!("{e:?}");
60+
list_dev_current_root()
61+
}
62+
}
63+
}
1964

2065
impl SavedState {
2166
/// System-wide bootupd write lock (relative to sysroot).
2267
const WRITE_LOCK_PATH: &'static str = "run/bootupd-lock";
2368
/// Top-level directory for statefile (relative to sysroot).
2469
pub(crate) const STATEFILE_DIR: &'static str = "boot";
25-
/// On-disk bootloader statefile, akin to a tiny rpm/dpkg database, stored in `/boot`.
70+
/// On-disk bootloader statefile, akin to a tiny rpm/dpkg database,
71+
/// stored in `/boot` for Grub and in `ESP` for GrubCC
2672
pub(crate) const STATEFILE_NAME: &'static str = "bootupd-state.json";
2773

2874
/// Try to acquire a system-wide lock to ensure non-conflicting state updates.
2975
///
3076
/// While ordinarily the daemon runs as a systemd unit (which implicitly
3177
/// ensures a single instance) this is a double check against other
3278
/// execution paths.
33-
pub(crate) fn acquire_write_lock(sysroot: Dir) -> Result<StateLockGuard> {
79+
pub(crate) fn acquire_write_lock(
80+
sysroot_path: Utf8PathBuf,
81+
sysroot: Dir,
82+
) -> Result<StateLockGuard> {
3483
sysroot
3584
.atomic_write_with_perms(Self::WRITE_LOCK_PATH, "", Permissions::from_mode(0o644))
3685
.context("Creating lock file")?;
@@ -43,6 +92,7 @@ impl SavedState {
4392
lockfile.lock_exclusive().context("Acquiring lock")?;
4493

4594
let guard = StateLockGuard {
95+
sysroot_path,
4696
sysroot,
4797
termguard: Some(SignalTerminationGuard::new()?),
4898
lockfile: Some(lockfile),
@@ -52,8 +102,9 @@ impl SavedState {
52102

53103
/// Use this for cases when the target root isn't booted, which is
54104
/// offline installs.
55-
pub(crate) fn unlocked(sysroot: Dir) -> Result<StateLockGuard> {
105+
pub(crate) fn unlocked(sysroot_path: Utf8PathBuf, sysroot: Dir) -> Result<StateLockGuard> {
56106
Ok(StateLockGuard {
107+
sysroot_path,
57108
sysroot,
58109
termguard: None,
59110
lockfile: None,
@@ -67,70 +118,49 @@ impl SavedState {
67118
bootloader: Option<Bootloader>,
68119
) -> Result<Option<SavedState>> {
69120
let root_path = root_path.as_ref();
70-
let sysroot = Dir::open_ambient_dir(root_path, ambient_authority())
121+
122+
let root = Dir::open_ambient_dir(root_path, ambient_authority())
71123
.with_context(|| format!("opening sysroot '{}'", root_path.display()))?;
72124

73-
let (statefile, _esp_guard) = match bootloader {
125+
match bootloader {
74126
Some(b) => match b {
75127
Bootloader::Grub => {
76128
let path = Path::new(Self::STATEFILE_DIR).join(Self::STATEFILE_NAME);
77-
(sysroot.open_optional(&path)?, None)
129+
130+
match root.open_optional(&path)? {
131+
Some(f) => parse_statefile(f),
132+
None => Ok(None),
133+
}
78134
}
79135

80136
Bootloader::GrubCC => {
81137
let efi = Efi::default();
82138

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)?;
139+
let device = get_parent_device(&root)?;
86140

87141
// Since we write the state file to all ESPs, it should be enough to get it
88142
// from the first one. Though, we could check the integrity by getting from
89143
// all the ESPs and making sure they're all the same...
90144
let esp = device.find_first_colocated_esp()?;
91145

92-
let tmpdir = tempdir()?;
93-
std::fs::create_dir_all(tmpdir.path().join("efi"))
94-
.context("Creating efi inside tmpdir")?;
95-
146+
// According to BLS, the ESP should be mounted at /boot or /boot/efi
147+
// which the following method already checks
96148
let mounted = efi
97-
.ensure_mounted_esp(tmpdir.path(), &Path::new(&esp.path()))
149+
.ensure_mounted_esp(&Path::new("/"), &Path::new(&esp.path()))
98150
.context("Mounting ESP")?;
99151

100152
let dir = Dir::open_ambient_dir(&mounted, ambient_authority())?;
101153

102-
(dir.open_optional(Self::STATEFILE_NAME)?, Some(efi))
154+
match dir.open_optional(Self::STATEFILE_NAME)? {
155+
Some(f) => parse_statefile(f),
156+
None => Ok(None),
157+
}
103158
}
104159
},
105160

106161
// No bootloader, we're probably running inside a container
107-
None => (None, None),
108-
};
109-
110-
let saved_state = if let Some(statusf) = statefile {
111-
let mut bufr = std::io::BufReader::new(statusf);
112-
let mut s = String::new();
113-
bufr.read_to_string(&mut s)?;
114-
let state: serde_json::Result<SavedState> = serde_json::from_str(s.as_str());
115-
let r = match state {
116-
Ok(s) => s,
117-
Err(orig_err) => {
118-
let state: serde_json::Result<crate::model_legacy::SavedState01> =
119-
serde_json::from_str(s.as_str());
120-
match state {
121-
Ok(s) => s.upconvert(),
122-
Err(_) => {
123-
return Err(orig_err.into());
124-
}
125-
}
126-
}
127-
};
128-
Some(r)
129-
} else {
130-
None
131-
};
132-
133-
Ok(saved_state)
162+
None => Ok(None),
163+
}
134164
}
135165

136166
/// Check whether statefile exists.
@@ -163,6 +193,7 @@ impl SavedState {
163193
/// Write-lock guard for statefile, protecting against concurrent state updates.
164194
#[derive(Debug)]
165195
pub(crate) struct StateLockGuard {
196+
pub(crate) sysroot_path: Utf8PathBuf,
166197
pub(crate) sysroot: Dir,
167198
#[allow(dead_code)]
168199
termguard: Option<SignalTerminationGuard>,
@@ -192,33 +223,29 @@ impl StateLockGuard {
192223
return Ok(());
193224
}
194225

195-
let dir = unsafe { Dir::from_raw_fd(self.sysroot.as_raw_fd()) };
196-
let device = bootc_internal_blockdev::list_dev_by_dir(&dir)?;
226+
let device = get_parent_device(&self.sysroot)?;
197227
let all_esps = device
198228
.find_colocated_esps()
199229
.context("Searching for ESP")?
200230
.ok_or_else(|| anyhow::anyhow!("ESP not found"))?;
201231

202232
let efi = Efi::default();
203233

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")?;
234+
let serialized_state = serde_json::to_vec(state).context("Serializing state")?;
208235

209236
for esp in all_esps {
210237
let mounted = efi
211-
.ensure_mounted_esp(tmpdir.path(), &Path::new(&esp.path()))
238+
.ensure_mounted_esp(&self.sysroot_path.as_std_path(), &Path::new(&esp.path()))
212239
.context("Mounting ESP")?;
213240

214241
let dir = Dir::open_ambient_dir(&mounted, ambient_authority())?;
215242

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)?;
243+
dir.atomic_write_with_perms(
244+
SavedState::STATEFILE_NAME,
245+
&serialized_state,
246+
Permissions::from_mode(0o644),
247+
)
248+
.context("Writing state file")?;
222249

223250
// Do the sync before unmount
224251
fsfreeze_thaw_cycle(dir.reopen_as_ownedfd()?)?;

src/bootupd.rs

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -216,8 +216,8 @@ pub(crate) fn install(
216216
// Unmount the ESP, etc.
217217
drop(target_components);
218218

219-
let mut state_guard =
220-
SavedState::unlocked(sysroot.try_clone()?).context("failed to acquire write lock")?;
219+
let mut state_guard = SavedState::unlocked(dest_root.into(), sysroot.try_clone()?)
220+
.context("failed to acquire write lock")?;
221221
state_guard
222222
.update_state(&state, bootloader)
223223
.context("failed to update state")?;
@@ -374,8 +374,8 @@ pub(crate) fn update(name: &str, rootcxt: &RootContext) -> Result<ComponentUpdat
374374
let interrupted = pending_container.get(component.name()).cloned();
375375
pending_container.insert(component.name().into(), update.clone());
376376
let sysroot = sysroot.try_clone()?;
377-
let mut state_guard =
378-
SavedState::acquire_write_lock(sysroot).context("Failed to acquire write lock")?;
377+
let mut state_guard = SavedState::acquire_write_lock(rootcxt.path.clone(), sysroot)
378+
.context("Failed to acquire write lock")?;
379379
state_guard
380380
.update_state(&state, bootloader)
381381
.context("Failed to update state")?;
@@ -428,8 +428,8 @@ pub(crate) fn adopt_and_update(
428428
};
429429

430430
let sysroot = sysroot.try_clone()?;
431-
let mut state_guard =
432-
SavedState::acquire_write_lock(sysroot).context("Failed to acquire write lock")?;
431+
let mut state_guard = SavedState::acquire_write_lock(rootcxt.path.clone(), sysroot)
432+
.context("Failed to acquire write lock")?;
433433

434434
let inst = component
435435
.adopt_update(&rootcxt, &update, with_static_config)
@@ -458,7 +458,7 @@ pub(crate) fn adopt_and_update(
458458
/// then falling back to `/sysroot`. This avoids issues with virtual
459459
/// filesystems like composefs that are mounted on `/`.
460460
#[context("Finding block device from boot or sysroot")]
461-
fn list_dev_current_root() -> Result<Device> {
461+
pub(crate) fn list_dev_current_root() -> Result<Device> {
462462
let auth = cap_std::ambient_authority();
463463
for path in ["/boot", "/sysroot"] {
464464
if let Ok(dir) = Dir::open_ambient_dir(path, auth) {
@@ -488,13 +488,9 @@ pub(crate) fn status() -> Result<Status> {
488488
let mut known_components = get_components();
489489
let sysroot = Dir::open_ambient_dir("/", ambient_authority())?;
490490

491-
let bootloader = if running_in_container() {
492-
None
493-
} else {
494-
Some(get_bootloader()?)
495-
};
491+
let bootloader = get_bootloader()?;
492+
let state = SavedState::load_from_disk("/", Some(bootloader))?;
496493

497-
let state = SavedState::load_from_disk("/", bootloader)?;
498494
if let Some(state) = state {
499495
for (name, ic) in state.installed.iter() {
500496
log::trace!("Gathering status for installed component: {}", name);

0 commit comments

Comments
 (0)