Skip to content

Initial implementation for /etc merge #1485

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

Draft
wants to merge 10 commits into
base: composefs-backend
Choose a base branch
from

Conversation

Johan-Liebert1
Copy link
Collaborator

Very initial iteration for /etc merge

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new crate etc-merge for calculating differences between /etc directories. The initial implementation provides a compute_diff function and related helpers. My review focuses on correctness issues, such as a typo in a key data structure and an incomplete hashing function that misses file permissions. I've also pointed out an unused variable that indicates incomplete logic, and suggested improvements to test data and assertions for better maintainability.

Comment on lines 254 to 258
assert_eq!(res.added.len(), new_files.len());
assert!(res.added.iter().all(|file| new_files
.iter()
.find(|x| PathBuf::from(*x) == *file)
.is_some()));
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This assertion logic is a bit complex. A simpler and more idiomatic way to compare two collections for equality regardless of order is to sort them and then compare the sorted collections. This pattern is repeated for modified and removed files as well.

Suggested change
assert_eq!(res.added.len(), new_files.len());
assert!(res.added.iter().all(|file| new_files
.iter()
.find(|x| PathBuf::from(*x) == *file)
.is_some()));
let mut res_added = res.added;
res_added.sort();
let mut expected_added: Vec<_> = new_files.iter().map(PathBuf::from).collect();
expected_added.sort();
assert_eq!(res_added, expected_added);

@Johan-Liebert1
Copy link
Collaborator Author

I've tested the output of this implementation with ostree admin config-diff and it seems correct. But again, I haven't tested this exhaustively

Copy link
Contributor

@p5 p5 Aug 5, 2025

Choose a reason for hiding this comment

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

General question, not something specific about this PR (this looks great BTW!)


Would Bootc's ostree backend be refactored to make use of this crate in time, or is the plan for ostree to remain mostly separate? Obviously the implementation would be completely separate PR to this anyway.

Copy link
Collaborator

Choose a reason for hiding this comment

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

This is a good question indeed. I've been really tempted to basically vendor+rewrite some of the ostree-sysroot stuff in this project. However, it has a ton of implications.

Copy link
Collaborator

@cgwalters cgwalters left a comment

Choose a reason for hiding this comment

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

Thanks for working on this!

Comment on lines 152 to 156
let mut buf = vec![0u8; entry.metadata()?.size() as usize];

let mut file = entry.open().context(format!("Opening entry {path:?}"))?;
file.read_exact(&mut buf)
.context(format!("Reading {path:?}. Buf: {buf:?}"))?;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Two key things here. First I think we should incremental hashing instead of reading the whole thing into memory and then hashing the memory buffer.

But second a nice optimization to make here is handling the case where the file has fsverity enabled. Then we can efficiently just query its fsverity hash to do a diff.

It won't be the default for files in a mutable /etc to have fsverity enabled but it makes sense to do so (dir is mutable, so the write-tmpfile-then-rename pattern continues to work) and we should make things faster for those who do use it.


path.pop();

// file has fs-verity enabled. We don't need to check the content/metadata
Copy link
Collaborator

Choose a reason for hiding this comment

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

Note that fsverity only covers file contents, not metadata. It's totally allowed to do e.g. chmod or chown on such a file...which we probably want to have tracked as modified right?

Eh sorry...I guess maybe this fsverity suggestion is adding too much complexity at the moment for a non-default corner case.

Let's I guess just leave it as a // TODO consider also using fsverity here if available?

@Johan-Liebert1 Johan-Liebert1 force-pushed the etc-merge branch 4 times, most recently from dc635c5 to 0faabae Compare August 11, 2025 10:22
@Johan-Liebert1 Johan-Liebert1 changed the title Initial implementaion for /etc merge Initial implementation for /etc merge Aug 11, 2025
Test for whether the file has fs-verity enabled or not, and if it does
we simply check the verity.

Incrementally compute hash for files rather than reading the entire file
in memory.

Signed-off-by: Johan-Liebert1 <[email protected]>
Signed-off-by: Johan-Liebert1 <[email protected]>
Signed-off-by: Johan-Liebert1 <[email protected]>
Merge added, modified, removed files from the current etc into the new
etc directory, following the rules

1. If file is removed from current_etc, it will be removed from new_etc
2. If file is modified in current_etc, it will be copied to the new_etc
   overwriting any existing files
3. If a file is added in current_etc, then the above modification rule
   applies

Modification includes change in content/permissions. Changed in Xattrs
and/or ownership is not handled yet.

Signed-off-by: Johan-Liebert1 <[email protected]>
Signed-off-by: Johan-Liebert1 <[email protected]>
@Johan-Liebert1 Johan-Liebert1 force-pushed the etc-merge branch 2 times, most recently from b2f05a1 to eafc75f Compare August 12, 2025 10:59
@Johan-Liebert1
Copy link
Collaborator Author

Hmm... tests failed because we can't chown unless we're root. And some issue with getxattr arguments

@cgwalters
Copy link
Collaborator

Hmm... tests failed because we can't chown unless we're root. And some issue with getxattr arguments

Yeah, some of this stuff really needs privileged integration tests, not unit tests.

In the short term what I think is easiest is to add an option to the core logic to ignore uid/gid + xattrs and that's what we do in unit tests.

For integration tests what we have in some other places is exposing the core functionality via bootc internals and then the tests can drive it, see e.g.

InternalsOpts::TestComposefs => {

(actually that's not so good an example because the command hardcodes the test, which bloats production binaries)
This one is maybe a bit better:
InternalsOpts::Relabel { as_path, path } => {

There's the tests-integration crate which is also intended to hold stuff like this.

Signed-off-by: Johan-Liebert1 <[email protected]>
Copy link
Collaborator

@cgwalters cgwalters left a comment

Choose a reason for hiding this comment

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

Ugh I reviewed but forgot to click submit

composefs = { git = "https://github.com/containers/composefs-rs", rev = "28d4721f77f973f0e394d60d6a69d9b39cb38d7f", package = "composefs", features = ["rhel9"] }
composefs-boot = { git = "https://github.com/containers/composefs-rs", rev = "28d4721f77f973f0e394d60d6a69d9b39cb38d7f", package = "composefs-boot" }
composefs-oci = { git = "https://github.com/containers/composefs-rs", rev = "28d4721f77f973f0e394d60d6a69d9b39cb38d7f", package = "composefs-oci" }
composefs = { git = "https://github.com/containers/composefs-rs", rev = "8d1e570275621531d39b8f01681584bcc85ce01c", package = "composefs", features = ["rhel9"] }
Copy link
Collaborator

Choose a reason for hiding this comment

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

In order to reduce conflicts let's do separate PRs for bumping crate dependencies.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Done in #1503

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants