Skip to content

Commit 0444e6a

Browse files
ckyrouaccgwalters
authored andcommitted
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 13cdb08 commit 0444e6a

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
@@ -2224,6 +2224,36 @@ pub(crate) async fn install_to_existing_root(opts: InstallToExistingRootOpts) ->
22242224
install_to_filesystem(opts, true, cleanup).await
22252225
}
22262226

2227+
/// Read the /boot entry from /etc/fstab, if it exists
2228+
fn read_boot_fstab_entry(root: &Dir) -> Result<Option<MountSpec>> {
2229+
let fstab_path = "etc/fstab";
2230+
let fstab = match root.open_optional(fstab_path)? {
2231+
Some(f) => f,
2232+
None => return Ok(None),
2233+
};
2234+
2235+
let reader = std::io::BufReader::new(fstab);
2236+
for line in std::io::BufRead::lines(reader) {
2237+
let line = line?;
2238+
let line = line.trim();
2239+
2240+
// Skip empty lines and comments
2241+
if line.is_empty() || line.starts_with('#') {
2242+
continue;
2243+
}
2244+
2245+
// Parse the mount spec
2246+
let spec = MountSpec::from_str(line)?;
2247+
2248+
// Check if this is a /boot entry
2249+
if spec.target == "/boot" {
2250+
return Ok(Some(spec));
2251+
}
2252+
}
2253+
2254+
Ok(None)
2255+
}
2256+
22272257
pub(crate) async fn install_reset(opts: InstallResetOpts) -> Result<()> {
22282258
let rootfs = &Dir::open_ambient_dir("/", cap_std::ambient_authority())?;
22292259
if !opts.experimental {
@@ -2298,11 +2328,35 @@ pub(crate) async fn install_reset(opts: InstallResetOpts) -> Result<()> {
22982328
.collect::<Vec<_>>();
22992329

23002330
let from = MergeState::Reset {
2301-
stateroot: target_stateroot,
2331+
stateroot: target_stateroot.clone(),
23022332
kargs,
23032333
};
23042334
crate::deploy::stage(sysroot, from, &fetched, &spec, prog.clone()).await?;
23052335

2336+
// Copy /boot entry from /etc/fstab to the new stateroot if it exists
2337+
if let Some(boot_spec) = read_boot_fstab_entry(rootfs)? {
2338+
let staged_deployment = ostree
2339+
.staged_deployment()
2340+
.ok_or_else(|| anyhow!("No staged deployment found"))?;
2341+
let deployment_path = ostree.deployment_dirpath(&staged_deployment);
2342+
let sysroot_dir = crate::utils::sysroot_dir(ostree)?;
2343+
let deployment_root = sysroot_dir.open_dir(&deployment_path)?;
2344+
2345+
// Write the /boot entry to /etc/fstab in the new deployment
2346+
crate::lsm::atomic_replace_labeled(
2347+
&deployment_root,
2348+
"etc/fstab",
2349+
0o644.into(),
2350+
None,
2351+
|w| writeln!(w, "{}", boot_spec.to_fstab()).map_err(Into::into),
2352+
)?;
2353+
2354+
tracing::debug!(
2355+
"Copied /boot entry to new stateroot: {}",
2356+
boot_spec.to_fstab()
2357+
);
2358+
}
2359+
23062360
sysroot.update_mtime()?;
23072361

23082362
if opts.apply {
@@ -2436,4 +2490,46 @@ mod tests {
24362490

24372491
Ok(())
24382492
}
2493+
2494+
#[test]
2495+
fn test_read_boot_fstab_entry() -> Result<()> {
2496+
let td = cap_std_ext::cap_tempfile::TempDir::new(cap_std::ambient_authority())?;
2497+
2498+
// Test with no /etc/fstab
2499+
assert!(read_boot_fstab_entry(&td)?.is_none());
2500+
2501+
// Test with /etc/fstab but no /boot entry
2502+
td.create_dir("etc")?;
2503+
td.write("etc/fstab", "UUID=test-uuid / ext4 defaults 0 0\n")?;
2504+
assert!(read_boot_fstab_entry(&td)?.is_none());
2505+
2506+
// Test with /boot entry
2507+
let fstab_content = "\
2508+
# /etc/fstab
2509+
UUID=root-uuid / ext4 defaults 0 0
2510+
UUID=boot-uuid /boot ext4 ro 0 0
2511+
UUID=home-uuid /home ext4 defaults 0 0
2512+
";
2513+
td.write("etc/fstab", fstab_content)?;
2514+
let boot_spec = read_boot_fstab_entry(&td)?.unwrap();
2515+
assert_eq!(boot_spec.source, "UUID=boot-uuid");
2516+
assert_eq!(boot_spec.target, "/boot");
2517+
assert_eq!(boot_spec.fstype, "ext4");
2518+
assert_eq!(boot_spec.options, Some("ro".to_string()));
2519+
2520+
// Test with /boot entry with comments
2521+
let fstab_content = "\
2522+
# /etc/fstab
2523+
# Created by anaconda
2524+
UUID=root-uuid / ext4 defaults 0 0
2525+
# Boot partition
2526+
UUID=boot-uuid /boot ext4 defaults 0 0
2527+
";
2528+
td.write("etc/fstab", fstab_content)?;
2529+
let boot_spec = read_boot_fstab_entry(&td)?.unwrap();
2530+
assert_eq!(boot_spec.source, "UUID=boot-uuid");
2531+
assert_eq!(boot_spec.target, "/boot");
2532+
2533+
Ok(())
2534+
}
24392535
}

0 commit comments

Comments
 (0)