Skip to content

Commit c5eeb09

Browse files
committed
reset: Preserve /boot fstab entry in new deployment
Add logic to copy the /boot mount specification from the existing /etc/fstab to the newly created stateroot during factory reset. This ensures that the /boot partition configuration is preserved across the reset operation. Includes helper function read_boot_fstab_entry() to parse /etc/fstab and locate the /boot mount entry, along with comprehensive unit tests. Assisted-by: Claude Code Signed-off-by: ckyrouac <[email protected]>
1 parent f6b1249 commit c5eeb09

File tree

1 file changed

+97
-1
lines changed

1 file changed

+97
-1
lines changed

crates/lib/src/install.rs

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2245,6 +2245,36 @@ pub(crate) async fn install_to_existing_root(opts: InstallToExistingRootOpts) ->
22452245
install_to_filesystem(opts, true, cleanup).await
22462246
}
22472247

2248+
/// Read the /boot entry from /etc/fstab, if it exists
2249+
fn read_boot_fstab_entry(root: &Dir) -> Result<Option<MountSpec>> {
2250+
let fstab_path = "etc/fstab";
2251+
let fstab = match root.open_optional(fstab_path)? {
2252+
Some(f) => f,
2253+
None => return Ok(None),
2254+
};
2255+
2256+
let reader = std::io::BufReader::new(fstab);
2257+
for line in std::io::BufRead::lines(reader) {
2258+
let line = line?;
2259+
let line = line.trim();
2260+
2261+
// Skip empty lines and comments
2262+
if line.is_empty() || line.starts_with('#') {
2263+
continue;
2264+
}
2265+
2266+
// Parse the mount spec
2267+
let spec = MountSpec::from_str(line)?;
2268+
2269+
// Check if this is a /boot entry
2270+
if spec.target == "/boot" {
2271+
return Ok(Some(spec));
2272+
}
2273+
}
2274+
2275+
Ok(None)
2276+
}
2277+
22482278
pub(crate) async fn install_reset(opts: InstallResetOpts) -> Result<()> {
22492279
let rootfs = &Dir::open_ambient_dir("/", cap_std::ambient_authority())?;
22502280
if !opts.experimental {
@@ -2319,11 +2349,35 @@ pub(crate) async fn install_reset(opts: InstallResetOpts) -> Result<()> {
23192349
.collect::<Vec<_>>();
23202350

23212351
let from = MergeState::Reset {
2322-
stateroot: target_stateroot,
2352+
stateroot: target_stateroot.clone(),
23232353
kargs,
23242354
};
23252355
crate::deploy::stage(sysroot, from, &fetched, &spec, prog.clone()).await?;
23262356

2357+
// Copy /boot entry from /etc/fstab to the new stateroot if it exists
2358+
if let Some(boot_spec) = read_boot_fstab_entry(rootfs)? {
2359+
let staged_deployment = ostree
2360+
.staged_deployment()
2361+
.ok_or_else(|| anyhow!("No staged deployment found"))?;
2362+
let deployment_path = ostree.deployment_dirpath(&staged_deployment);
2363+
let sysroot_dir = crate::utils::sysroot_dir(ostree)?;
2364+
let deployment_root = sysroot_dir.open_dir(&deployment_path)?;
2365+
2366+
// Write the /boot entry to /etc/fstab in the new deployment
2367+
crate::lsm::atomic_replace_labeled(
2368+
&deployment_root,
2369+
"etc/fstab",
2370+
0o644.into(),
2371+
None,
2372+
|w| writeln!(w, "{}", boot_spec.to_fstab()).map_err(Into::into),
2373+
)?;
2374+
2375+
tracing::debug!(
2376+
"Copied /boot entry to new stateroot: {}",
2377+
boot_spec.to_fstab()
2378+
);
2379+
}
2380+
23272381
sysroot.update_mtime()?;
23282382

23292383
if opts.apply {
@@ -2457,4 +2511,46 @@ mod tests {
24572511

24582512
Ok(())
24592513
}
2514+
2515+
#[test]
2516+
fn test_read_boot_fstab_entry() -> Result<()> {
2517+
let td = cap_std_ext::cap_tempfile::TempDir::new(cap_std::ambient_authority())?;
2518+
2519+
// Test with no /etc/fstab
2520+
assert!(read_boot_fstab_entry(&td)?.is_none());
2521+
2522+
// Test with /etc/fstab but no /boot entry
2523+
td.create_dir("etc")?;
2524+
td.write("etc/fstab", "UUID=test-uuid / ext4 defaults 0 0\n")?;
2525+
assert!(read_boot_fstab_entry(&td)?.is_none());
2526+
2527+
// Test with /boot entry
2528+
let fstab_content = "\
2529+
# /etc/fstab
2530+
UUID=root-uuid / ext4 defaults 0 0
2531+
UUID=boot-uuid /boot ext4 ro 0 0
2532+
UUID=home-uuid /home ext4 defaults 0 0
2533+
";
2534+
td.write("etc/fstab", fstab_content)?;
2535+
let boot_spec = read_boot_fstab_entry(&td)?.unwrap();
2536+
assert_eq!(boot_spec.source, "UUID=boot-uuid");
2537+
assert_eq!(boot_spec.target, "/boot");
2538+
assert_eq!(boot_spec.fstype, "ext4");
2539+
assert_eq!(boot_spec.options, Some("ro".to_string()));
2540+
2541+
// Test with /boot entry with comments
2542+
let fstab_content = "\
2543+
# /etc/fstab
2544+
# Created by anaconda
2545+
UUID=root-uuid / ext4 defaults 0 0
2546+
# Boot partition
2547+
UUID=boot-uuid /boot ext4 defaults 0 0
2548+
";
2549+
td.write("etc/fstab", fstab_content)?;
2550+
let boot_spec = read_boot_fstab_entry(&td)?.unwrap();
2551+
assert_eq!(boot_spec.source, "UUID=boot-uuid");
2552+
assert_eq!(boot_spec.target, "/boot");
2553+
2554+
Ok(())
2555+
}
24602556
}

0 commit comments

Comments
 (0)