Skip to content

Commit 42e96dc

Browse files
cfs/stage: Compute etc diff before staging
Before we write the staged deployment file at `/run/composefs` and before we create new boot entries, we now compute the etc diff in advance to check whethere we will face any merge conflicts while finalizing the deployment. If we find conflicts we exit with and error and detailing where conflicts were found Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
1 parent de65e4f commit 42e96dc

6 files changed

Lines changed: 64 additions & 31 deletions

File tree

Justfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ test-composefs bootloader filesystem boot_type seal_state *ARGS:
217217
--seal-state={{seal_state}} \
218218
--boot-type={{boot_type}} \
219219
{{ARGS}} \
220-
$(if [ "{{boot_type}}" = "uki" ] && [ "{{seal_state}}" = "sealed" ]; then echo "readonly image-upgrade-reboot"; else echo "integration"; fi)
220+
$(if [ "{{boot_type}}" = "uki" ] && [ "{{seal_state}}" = "sealed" ]; then echo "readonly image-upgrade-reboot"; else echo "etc-merge-conflict"; fi)
221221

222222
# Run upgrade test: boot VM from published base image (with tmt deps added),
223223
# upgrade to locally-built image, reboot, then run readonly tests to verify.

crates/etc-merge/src/lib.rs

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ fn stat_eq_ignore_mtime(this: &Stat, other: &Stat) -> bool {
8080

8181
#[derive(Debug)]
8282
pub struct UnmergablePaths {
83-
path: PathBuf,
84-
reason: String,
83+
pub path: PathBuf,
84+
pub reason: String,
8585
}
8686

8787
/// Represents the differences between two directory trees.
@@ -95,7 +95,7 @@ pub struct Diff {
9595
/// Paths that exist in the pristine /etc but not in the current one
9696
removed: Vec<PathBuf>,
9797
/// Paths that are unmergable
98-
unmergable_paths: Vec<UnmergablePaths>,
98+
pub unmergable_paths: Vec<UnmergablePaths>,
9999
}
100100

101101
fn collect_all_files(
@@ -485,6 +485,19 @@ pub fn compute_diff(
485485
Ok(diff)
486486
}
487487

488+
pub fn print_unmergable_paths(diff: &Diff, writer: &mut impl Write) {
489+
use owo_colors::OwoColorize;
490+
491+
for unmergable in &diff.unmergable_paths {
492+
let _ = writeln!(
493+
writer,
494+
"{} {}",
495+
ModificationType::Unmergable.magenta(),
496+
unmergable.reason
497+
);
498+
}
499+
}
500+
488501
/// Prints a colorized summary of differences to standard output.
489502
pub fn print_diff(diff: &Diff, writer: &mut impl Write) {
490503
use owo_colors::OwoColorize;
@@ -501,14 +514,7 @@ pub fn print_diff(diff: &Diff, writer: &mut impl Write) {
501514
let _ = writeln!(writer, "{} {removed:?}", ModificationType::Removed.red());
502515
}
503516

504-
for unmergable in &diff.unmergable_paths {
505-
let _ = writeln!(
506-
writer,
507-
"{} {}",
508-
ModificationType::Unmergable.magenta(),
509-
unmergable.reason
510-
);
511-
}
517+
print_unmergable_paths(diff, writer);
512518
}
513519

514520
#[context("Collecting xattrs")]

crates/lib/src/bootc_composefs/finalize.rs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,16 @@ use cap_std_ext::cap_std::{ambient_authority, fs::Dir};
1414
use cap_std_ext::dirext::CapStdExtDirExt;
1515
use composefs::generic_tree::{FileSystem, Stat};
1616
use composefs_ctl::composefs;
17-
use etc_merge::{compute_diff, merge, print_diff, traverse_etc};
17+
use etc_merge::{Diff, compute_diff, merge, traverse_etc};
1818
use rustix::fs::fsync;
1919

2020
use fn_error_context::context;
2121

22-
pub(crate) async fn get_etc_diff(storage: &Storage, booted_cfs: &BootedComposefs) -> Result<()> {
22+
pub(crate) async fn get_etc_diff(
23+
storage: &Storage,
24+
booted_cfs: &BootedComposefs,
25+
new_etc: Option<&Dir>,
26+
) -> Result<Diff> {
2327
let host = get_composefs_status(storage, booted_cfs).await?;
2428
let booted_composefs = host.require_composefs_booted()?;
2529

@@ -37,16 +41,18 @@ pub(crate) async fn get_etc_diff(storage: &Storage, booted_cfs: &BootedComposefs
3741
Dir::open_ambient_dir(erofs_tmp_mnt.dir.path().join("etc"), ambient_authority())?;
3842
let current_etc = Dir::open_ambient_dir("/etc", ambient_authority())?;
3943

40-
let (pristine_files, current_files, _) = traverse_etc(&pristine_etc, &current_etc, None)?;
44+
let (pristine_files, current_files, new_etc_files) =
45+
traverse_etc(&pristine_etc, &current_etc, new_etc)?;
46+
4147
let diff = compute_diff(
4248
&pristine_files,
4349
&current_files,
44-
&FileSystem::new(Stat::uninitialized()),
50+
&new_etc_files
51+
.as_ref()
52+
.unwrap_or(&FileSystem::new(Stat::uninitialized())),
4553
)?;
4654

47-
print_diff(&diff, &mut std::io::stdout());
48-
49-
Ok(())
55+
Ok(diff)
5056
}
5157

5258
pub(crate) async fn composefs_backend_finalize(

crates/lib/src/bootc_composefs/update.rs

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ use composefs_ctl::composefs;
77
use composefs_ctl::composefs_boot;
88
use composefs_ctl::composefs_oci;
99
use composefs_oci::image::create_filesystem;
10+
use etc_merge::print_unmergable_paths;
1011
use fn_error_context::context;
1112
use ocidir::cap_std::ambient_authority;
1213
use ostree_ext::container::ManifestDiff;
1314

15+
use crate::bootc_composefs::finalize::get_etc_diff;
1416
use crate::bootc_composefs::gc::GCOpts;
1517
use crate::spec::BootloaderKind;
1618
use crate::{
@@ -288,6 +290,18 @@ pub(crate) async fn do_upgrade(
288290
.context("Failed to mount composefs image")?,
289291
)?;
290292

293+
// Check if three way etc merge is possible without conflicts
294+
let new_etc = mounted_fs
295+
.open_dir("etc")
296+
.context("Opening deployment's etc")?;
297+
298+
let diff = get_etc_diff(storage, booted_cfs, Some(&new_etc)).await?;
299+
300+
if !diff.unmergable_paths.is_empty() {
301+
print_unmergable_paths(&diff, &mut std::io::stderr());
302+
anyhow::bail!("Merge conflicts found in etc");
303+
}
304+
291305
let boot_type = BootType::from(entry);
292306

293307
let boot_digest = match boot_type {
@@ -307,14 +321,16 @@ pub(crate) async fn do_upgrade(
307321
)?,
308322
};
309323

324+
let staged_state = StagedDeployment {
325+
depl_id: id.to_hex(),
326+
finalization_locked: opts.download_only,
327+
};
328+
310329
write_composefs_state(
311330
&Utf8PathBuf::from("/sysroot"),
312331
&id,
313332
imgref,
314-
Some(StagedDeployment {
315-
depl_id: id.to_hex(),
316-
finalization_locked: opts.download_only,
317-
}),
333+
Some(staged_state),
318334
boot_type,
319335
boot_digest,
320336
&manifest_digest,
@@ -393,16 +409,20 @@ pub(crate) async fn upgrade_composefs(
393409

394410
start_finalize_stated_svc()?;
395411

396-
// Make the staged deployment not download_only
397-
let new_staged = StagedDeployment {
398-
depl_id: staged.require_composefs()?.verity.clone(),
399-
finalization_locked: false,
400-
};
401-
402412
let staged_depl_dir =
403413
Dir::open_ambient_dir(COMPOSEFS_TRANSIENT_STATE_DIR, ambient_authority())
404414
.context("Opening transient state directory")?;
405415

416+
let current = staged_depl_dir
417+
.read_to_string(COMPOSEFS_STAGED_DEPLOYMENT_FNAME)
418+
.context("Reading staged file")?;
419+
420+
let mut new_staged: StagedDeployment =
421+
serde_json::from_str(&current).context("Deserialzing staged file")?;
422+
423+
// Make the staged deployment not download_only
424+
new_staged.finalization_locked = false;
425+
406426
staged_depl_dir
407427
.atomic_replace_with(
408428
COMPOSEFS_STAGED_DEPLOYMENT_FNAME,

crates/lib/src/cli.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2455,7 +2455,9 @@ async fn run_from_opt(opt: Opt) -> Result<()> {
24552455
anyhow::bail!("ConfigDiff is only supported for composefs backend")
24562456
}
24572457
BootedStorageKind::Composefs(booted_cfs) => {
2458-
get_etc_diff(storage, &booted_cfs).await
2458+
let diff = get_etc_diff(storage, &booted_cfs, None).await?;
2459+
print_diff(&diff, &mut std::io::stdout());
2460+
Ok(())
24592461
}
24602462
}
24612463
}

crates/lib/src/store/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,6 @@ impl<'a> BootedOstree<'a> {
249249
}
250250

251251
/// Represents a composefs-based boot environment
252-
#[allow(dead_code)]
253252
pub struct BootedComposefs {
254253
pub repo: Arc<ComposefsRepository>,
255254
pub cmdline: &'static ComposefsCmdline,

0 commit comments

Comments
 (0)