repart: make vfat creation reproducible (#42446)

Two fixes to get this byte-stable:

- `fd_copy_directory()` was using `FOREACH_DIRENT_ALL`, which doesn't
give stable ordering. Read all paths, sort, then iterate.
- `mcopy -s` depends on `readdir()` ordering and thus isn't
reproducible. Implement the recursion/sorting here and only invoke
mcopy/mmd per dir.

First change increases memory usage, as we don't stream the paths
anymore, second increases the number of context switches when invoking
external tools. Both should be fine given the ESP content should usually
be pretty limited.

I'd like to write a test for this, but didn't come up with a way that
doesn't require privileges and would surface the error reliably.
This commit is contained in:
Lennart Poettering
2026-06-22 11:40:27 +02:00
committed by GitHub
3 changed files with 176 additions and 53 deletions
+16 -1
View File
@@ -141,7 +141,7 @@ TEST(copy_tree) {
"link2", "dir1/file");
char **hardlinks = STRV_MAKE("hlink", "file",
"hlink2", "dir1/file");
const char *unixsockp, *ignorep;
const char *unixsockp, *ignorep, *denydirp, *denyfilep;
struct stat st;
int xattr_worked = -1; /* xattr support is optional in temporary directories, hence use it if we can,
* but don't fail if we can't */
@@ -194,6 +194,14 @@ TEST(copy_tree) {
assert_se(hashmap_ensure_put(&denylist, &inode_hash_ops, cp, INT_TO_PTR(DENY_INODE)) >= 0);
TAKE_PTR(cp);
denyfilep = strjoina(original_dir, "denycontents/file");
assert_se(write_string_file(denyfilep, "denied", WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MKDIR_0755) == 0);
denydirp = strjoina(original_dir, "denycontents");
assert_se(RET_NERRNO(stat(denydirp, &st)) >= 0);
assert_se(cp = memdup(&st, sizeof(st)));
assert_se(hashmap_ensure_put(&denylist, &inode_hash_ops, cp, INT_TO_PTR(DENY_CONTENTS)) >= 0);
TAKE_PTR(cp);
assert_se(copy_tree(original_dir, copy_dir, UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_MERGE|COPY_HARDLINKS, denylist, NULL) == 0);
STRV_FOREACH(p, files) {
@@ -252,6 +260,13 @@ TEST(copy_tree) {
ignorep = strjoina(copy_dir, "ignore/file");
assert_se(RET_NERRNO(access(ignorep, F_OK)) == -ENOENT);
/* The DENY_CONTENTS directory itself must exist in the copy, but its contents must not have been copied. */
denydirp = strjoina(copy_dir, "denycontents");
assert_se(stat(denydirp, &st) >= 0);
assert_se(S_ISDIR(st.st_mode));
denyfilep = strjoina(copy_dir, "denycontents/file");
assert_se(RET_NERRNO(access(denyfilep, F_OK)) == -ENOENT);
(void) rm_rf(copy_dir, REMOVE_ROOT|REMOVE_PHYSICAL);
(void) rm_rf(original_dir, REMOVE_ROOT|REMOVE_PHYSICAL);
}