Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion crates/etc-merge/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -794,7 +794,9 @@ pub fn merge(
.context("Merging modified files")?;

for removed in &diff.removed {
let stat = new_etc_fd.metadata_optional(&removed)?;
// Use symlink_metadata_optional so that symlinks that resolve to a path
// outside the new_etc_fd don't get followed
let stat = new_etc_fd.symlink_metadata_optional(&removed)?;

let Some(stat) = stat else {
// File/dir doesn't exist in new_etc
Expand Down Expand Up @@ -1071,6 +1073,12 @@ mod tests {
n.create_dir_all("dir/perms")?;
n.write("dir/perms/some-file", "Some-file")?;

// File exists in pristine and new_etc (as a symlink pointing outside the root),
// but was deleted in current_etc. The merge should remove it from new_etc
// without following the symlink.
p.write("ext-symlink", "pristine content")?;
symlinkat("/usr/bin/bash", &n, "ext-symlink")?;

let (pristine_etc_files, current_etc_files, new_etc_files) =
traverse_etc(&p, &c, Some(&n))?;
let diff = compute_diff(
Expand Down Expand Up @@ -1128,6 +1136,9 @@ mod tests {
DIR_BITS | 0o775
);

// External symlink should be removed without following it
assert!(!n.exists("ext-symlink"));

Ok(())
}

Expand Down
2 changes: 1 addition & 1 deletion crates/lib/src/bootc_composefs/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ pub async fn export_repo_to_image(

let depl_verity = depl_verity.ok_or_else(|| anyhow::anyhow!("Image {source} not found"))?;

let imginfo = get_imginfo(storage, &depl_verity)?;
let (imginfo, _) = get_imginfo(storage, &depl_verity)?;

let config_digest = imginfo.manifest.config().digest().clone();

Expand Down
17 changes: 12 additions & 5 deletions crates/lib/src/bootc_composefs/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use composefs_ctl::composefs::fsverity::Sha512HashValue;
use composefs_ctl::composefs_oci;
use composefs_oci::OciImage;
use fn_error_context::context;
use openssl::sha::Sha256;
use serde::{Deserialize, Serialize};

use crate::{
Expand Down Expand Up @@ -480,7 +481,10 @@ pub(crate) fn get_bootloader() -> Result<Bootloader> {
/// Falls back to reading legacy `.imginfo` files for backwards compatibility
/// with deployments created before the manifest digest was stored in `.origin`.
#[context("Reading image info for deployment {deployment_id}")]
pub(crate) fn get_imginfo(storage: &Storage, deployment_id: &str) -> Result<ImgConfigManifest> {
pub(crate) fn get_imginfo(
storage: &Storage,
deployment_id: &str,
) -> Result<(ImgConfigManifest, String)> {
let ini = read_origin(&storage.physical_root, deployment_id)?
.ok_or_else(|| anyhow::anyhow!("No origin file for deployment {deployment_id}"))?;

Expand All @@ -501,7 +505,7 @@ pub(crate) fn get_imginfo(storage: &Storage, deployment_id: &str) -> Result<ImgC
.cloned()
.ok_or_else(|| anyhow::anyhow!("OCI image has no config (artifact?)"))?;

return Ok(ImgConfigManifest { config, manifest });
return Ok((ImgConfigManifest { config, manifest }, manifest_digest_str));
}

// Fallback: read legacy .imginfo file for deployments created before
Expand All @@ -528,7 +532,11 @@ pub(crate) fn get_imginfo(storage: &Storage, deployment_id: &str) -> Result<ImgC
let img_conf = serde_json::from_str::<ImgConfigManifest>(&buffer)
.context("Failed to parse .imginfo file as JSON")?;

Ok(img_conf)
// Compute the manifest digest
let mut hasher = Sha256::new();
hasher.update(&serde_json::to_vec(&img_conf.manifest).context("Serializing image manifest")?);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is re-serializing the manifest object. But for our purposes here, we want to be certain we end up with the same manifest digest as the remote. Are we guaranteed a match here? Ideally we'd just hash the raw manifest string we originally fetched from the remote at pull time, which IIRC is how this works on the ostree backend.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is more "legacy" code for backwards compatibility for older systems where we used to store the config + manifest in an .imginfo file. To answer your question, yes, it should, in theory, produce the same digest since we pretty much used to store the manifest and config unchanged. Now though, we do what ostree does and store the manifest digest string inside the .origin file

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To answer your question, yes, it should, in theory, produce the same digest since we pretty much used to store the manifest and config unchanged.

I think the point Jonathan is making here is that we should hash the raw data and not re-serialize.

But @Johan-Liebert1 is also right in this is just the legacy path, which I think we can schedule to remove at some point and isn't really relevant anymore.

let manifest_digest_str = hex::encode(hasher.finish());
Ok((img_conf, manifest_digest_str))
}

#[context("Getting composefs deployment metadata")]
Expand All @@ -543,9 +551,8 @@ fn boot_entry_from_composefs_deployment(
let ostree_img_ref = OstreeImageReference::from_str(&img_name_from_config)?;
let img_ref = crate::spec::ImageReference::from(ostree_img_ref);

let img_conf = get_imginfo(storage, &verity)?;
let (img_conf, image_digest) = get_imginfo(storage, &verity)?;

let image_digest = img_conf.manifest.config().digest().to_string();
let architecture = img_conf.config.architecture().to_string();
let version = img_conf
.manifest
Expand Down
2 changes: 1 addition & 1 deletion crates/lib/src/bootc_composefs/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -529,7 +529,7 @@ pub(crate) async fn upgrade_composefs(
}

if opts.check {
let current_manifest = get_imginfo(storage, &*composefs.cmdline.digest)?;
let (current_manifest, _) = get_imginfo(storage, &*composefs.cmdline.digest)?;
let diff = ManifestDiff::new(&current_manifest.manifest, &img_config.manifest);
diff.print();
return Ok(());
Expand Down
7 changes: 7 additions & 0 deletions tmt/tests/booted/test-loader-entries-source.nu
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@
use std assert
use tap.nu

let is_bad_version = ostree --version | lines | any {|l| $l | str contains "2026.2" }

if $is_bad_version {
print "Found Ostree v2026.2, skipping test"
exit 0
}

def parse_cmdline [] {
open /proc/cmdline | str trim | split row " "
}
Expand Down
Loading