-
Notifications
You must be signed in to change notification settings - Fork 208
UKI print dump diff #2315
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
UKI print dump diff #2315
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -61,6 +61,7 @@ | |
| //! 1. **Primary**: New/upgraded deployment (default boot target) | ||
| //! 2. **Secondary**: Currently booted deployment (rollback option) | ||
|
|
||
| use std::ffi::OsStr; | ||
| use std::fs::create_dir_all; | ||
| use std::io::{Read, Seek, SeekFrom, Write}; | ||
| use std::path::Path; | ||
|
|
@@ -89,6 +90,7 @@ use composefs_ctl::composefs_boot; | |
| use composefs_ctl::composefs_oci; | ||
| use fn_error_context::context; | ||
| use linux_kernel_cmdline::utf8::{Cmdline, Parameter}; | ||
| use ostree_ext::composefs::dumpfile; | ||
| use rustix::{mount::MountFlags, path::Arg}; | ||
| use schemars::JsonSchema; | ||
| use serde::{Deserialize, Serialize}; | ||
|
|
@@ -134,6 +136,14 @@ const AUTH_EXT: &str = "auth"; | |
| /// This is relative to the ESP | ||
| pub(crate) const BOOTC_UKI_DIR: &str = "EFI/Linux/bootc"; | ||
|
|
||
| #[derive(thiserror::Error, Debug)] | ||
| #[error("The UKI has the wrong composefs= parameter (is '{actual}', should be '{expected}')")] | ||
| pub(crate) struct UKIDigestMismatch { | ||
| pub actual: String, | ||
| pub expected: String, | ||
| pub uki_name: Option<String>, | ||
| } | ||
|
|
||
| pub(crate) enum BootSetupType<'a> { | ||
| /// For initial setup, i.e. install to-disk | ||
| Setup((&'a RootSetup, &'a State, &'a PostFetchState)), | ||
|
|
@@ -491,7 +501,7 @@ struct BLSEntryPath { | |
| #[context("Setting up BLS boot")] | ||
| pub(crate) fn setup_composefs_bls_boot( | ||
| setup_type: BootSetupType, | ||
| repo: crate::store::ComposefsRepository, | ||
| repo: &crate::store::ComposefsRepository, | ||
| id: &Sha512HashValue, | ||
| entry: &ComposefsBootEntry<Sha512HashValue>, | ||
| mounted_erofs: &Dir, | ||
|
|
@@ -845,10 +855,15 @@ fn write_pe_to_esp( | |
| _ => { /* no-op */ } | ||
| } | ||
|
|
||
| let file_name = file_path.file_name(); | ||
|
|
||
| if *composefs_cmdline != *uki_id { | ||
| anyhow::bail!( | ||
| "The UKI has the wrong composefs= parameter (is '{composefs_cmdline:?}', should be {uki_id:?})" | ||
| ); | ||
| return Err(UKIDigestMismatch { | ||
| actual: composefs_cmdline.to_hex(), | ||
| expected: uki_id.to_hex(), | ||
| uki_name: file_name.map(|x| x.to_string()), | ||
| } | ||
| .into()); | ||
| } | ||
|
|
||
| uki_reader.seek(SeekFrom::Start(0))?; | ||
|
|
@@ -1083,7 +1098,7 @@ fn write_systemd_uki_config( | |
| #[context("Setting up UKI boot")] | ||
| pub(crate) fn setup_composefs_uki_boot( | ||
| setup_type: BootSetupType, | ||
| repo: crate::store::ComposefsRepository, | ||
| repo: &crate::store::ComposefsRepository, | ||
| id: &Sha512HashValue, | ||
| entries: Vec<ComposefsBootEntry<Sha512HashValue>>, | ||
| ) -> Result<String> { | ||
|
|
@@ -1450,7 +1465,6 @@ pub(crate) async fn setup_composefs_boot( | |
|
|
||
| let boot_type = BootType::from(entry); | ||
|
|
||
| // Unwrap Arc to pass owned repo to boot setup functions. | ||
| let repo = Arc::try_unwrap(repo).map_err(|_| { | ||
| anyhow::anyhow!( | ||
| "BUG: Arc<Repository> still has other references after boot image generation" | ||
|
|
@@ -1460,17 +1474,82 @@ pub(crate) async fn setup_composefs_boot( | |
| let boot_digest = match boot_type { | ||
| BootType::Bls => setup_composefs_bls_boot( | ||
| BootSetupType::Setup((&root_setup, &state, &postfetch)), | ||
| repo, | ||
| &repo, | ||
| &id, | ||
| entry, | ||
| mounted_root.dir(), | ||
| )?, | ||
| BootType::Uki => setup_composefs_uki_boot( | ||
| BootSetupType::Setup((&root_setup, &state, &postfetch)), | ||
| repo, | ||
| &id, | ||
| entries, | ||
| )?, | ||
| BootType::Uki => { | ||
| let uki_setup_result = setup_composefs_uki_boot( | ||
| BootSetupType::Setup((&root_setup, &state, &postfetch)), | ||
| &repo, | ||
| &id, | ||
| entries, | ||
| ); | ||
|
|
||
| match uki_setup_result { | ||
| Ok(boot_digest) => boot_digest, | ||
| Err(e) => match e.downcast::<UKIDigestMismatch>() { | ||
| Ok(mismatch) => { | ||
| // We expect dumpfile in /boot as that's the only directory that gets | ||
| // masked | ||
| let boot_dir = fs.root.get_directory(OsStr::new("boot"))?; | ||
|
|
||
| // We expect the dumpfile to be named the same as the UKI | ||
| // Ex. UKI - 6.19.14-108.fc42.x86_64.efi | ||
| // Dumpfile - 6.19.14-108.fc42.x86_64.dump | ||
| let dumpfile_name = mismatch | ||
| .uki_name | ||
| .as_ref() | ||
| .and_then(|x| x.strip_suffix(EFI_EXT).map(|x| format!("{x}.dump"))); | ||
|
|
||
| let Some(dumpfile_name) = &dumpfile_name else { | ||
| tracing::warn!("Dumpfile not found for diff"); | ||
| return Err(mismatch.into()); | ||
| }; | ||
|
|
||
| let dumpfile = boot_dir | ||
| .get_file_opt(OsStr::new(&dumpfile_name), &fs.leaves)? | ||
| .map(|df| read_file(&df, &repo)) | ||
| .transpose() | ||
| .context("Reading dumpfile")?; | ||
|
|
||
| let Some(embedded) = dumpfile else { | ||
| tracing::warn!("Dumpfile not found for diff"); | ||
| return Err(mismatch.into()); | ||
| }; | ||
|
|
||
| let tempdir = tempfile::tempdir()?; | ||
| let path = tempdir.path(); | ||
| let tempdir = Dir::open_ambient_dir(path, ambient_authority())?; | ||
|
|
||
| // TODO: This can use the `dump_files` API once we have | ||
| // https://github.com/composefs/composefs-rs/pull/359 | ||
| let mut original = tempdir.create("original")?; | ||
| original.write_all(&embedded)?; | ||
|
|
||
| let mut tmpfile = tempdir.create("current")?; | ||
| dumpfile::write_dumpfile(&mut tmpfile, &fs).context("Writing dumpfile")?; | ||
|
|
||
| let mut cmd = std::process::Command::new("diff"); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we can use https://crates.io/crates/similar
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't seem to accept files as params, only strings, ranges etc. Reading the entire diff into memory is okay-ish I guess as it's usually around < 20M |
||
| let out = cmd | ||
| .arg("--color=auto") | ||
| .arg(format!("{}/original", path.display())) | ||
| .arg(format!("{}/current", path.display())) | ||
| .status(); | ||
|
|
||
| // Intentionally not short-circuiting here as the real error is digest | ||
| // mismtach | ||
| if let Err(e) = out { | ||
| tracing::warn!("diffing dumpfiles failed with Err: {e:?}"); | ||
| }; | ||
|
|
||
| return Err(mismatch.into()); | ||
| } | ||
| Err(e) => Err(e)?, | ||
| }, | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| write_composefs_state( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| # number: 48 | ||
| # tmt: | ||
| # summary: Test composefs garbage collection for UKI | ||
| # duration: 30m | ||
|
|
||
| use std assert | ||
| use tap.nu | ||
|
|
||
| if not (tap is_composefs) { | ||
| exit 0 | ||
| } | ||
|
|
||
| # bootc status | ||
| let st = bootc status --json | from json | ||
| let booted = $st.status.booted.image | ||
|
|
||
| let is_uki = (($st.status.booted.composefs.bootType | str downcase) == "uki") | ||
|
|
||
| if not $is_uki { | ||
| exit 0 | ||
| } | ||
|
|
||
| def first_boot [] { | ||
| bootc image copy-to-storage | ||
|
|
||
| mut containerfile = $" | ||
| FROM localhost/bootc as base | ||
| RUN touch /usr/share/accepted-file | ||
| " | ||
|
|
||
| $containerfile = (tap make_uki_containerfile $containerfile) | ||
|
|
||
| $containerfile += " | ||
| RUN touch /usr/share/new-file | ||
| " | ||
|
|
||
| echo $containerfile | podman build -t localhost/dump-diff . -f - | ||
|
|
||
| let result = do { bootc switch --transport containers-storage localhost/dump-diff } | complete | ||
|
|
||
| let actual_digest = ./bootc internals cfs oci compute-id $"@(podman images --no-trunc | grep dump-diff | awk '{print $3}')" | ||
|
|
||
| assert ($result.exit_code != 0) "bootc switch should fail" | ||
|
|
||
| print ($result.stderr) | ||
|
|
||
| assert ($result.stderr | str contains "The UKI has the wrong composefs= parameter") $"Expected 'The UKI has the wrong composefs= parameter' in stderr" | ||
| assert ($result.stderr | str contains $"should be '($actual_digest)'") $"Expected digest to be ($actual_digest) in stderr" | ||
| assert ($result.stderr | str contains "/usr/share/new-file") $"Expected '/usr/share/new-file' in stderr" | ||
|
|
||
| tap ok | ||
| } | ||
|
|
||
| def main [] { | ||
| match $env.TMT_REBOOT_COUNT? { | ||
| null | "0" => first_boot, | ||
| $o => { error make { msg: $"Invalid TMT_REBOOT_COUNT ($o)" } }, | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a normal situation, not a warning case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right. I'll change it to info
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or debug maybe