repart: clamp copied timestamps to $SOURCE_DATE_EPOCH
Use the new copy timestamp clamping mechanism from the previous commit to fix unreproducible wall-clock files in the generated images. That covers both modes: online mode writes through the kernel, and offline mode stages the tree in a temporary directory before handing it to mkfs or mcopy. `mcopy -m` then carries the clamped mtimes into the FAT. This complements commit34c3d57474(directory times) and commitf191ca982c(FAT insertion order). Cover all combinations (online/offline, with and without epoch) in the new `testcase_vfat_reproducibility()`. Update `testcase_make_symlinks()` to cover the symlink creation/re-fix their parent directory code paths. Sadly, images are still not reproducible byte for byte. Document systemd-repart's $SOURCE_DATE_EPOCH handling and remaining reproducibility issues in the manpage.
This commit is contained in:
+66
-14
@@ -6807,6 +6807,17 @@ static int file_is_denylisted(const char *source, Hashmap *denylist) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Adding an entry to a directory bumps its mtime. Clamp it back to ts. */
|
||||
static int clamp_directory_mtime(int dir_fd, usec_t ts) {
|
||||
assert(dir_fd >= 0);
|
||||
|
||||
if (ts == USEC_INFINITY)
|
||||
return 0;
|
||||
|
||||
/* chase() hands out O_PATH descriptors. */
|
||||
return futimens_opath(dir_fd, (const struct timespec[2]) { TIMESPEC_OMIT, *TIMESPEC_STORE(ts) });
|
||||
}
|
||||
|
||||
static int do_copy_files(Context *context, Partition *p, const char *root) {
|
||||
_cleanup_hashmap_free_ Hashmap *subvolumes = NULL;
|
||||
int r;
|
||||
@@ -6830,6 +6841,7 @@ static int do_copy_files(Context *context, Partition *p, const char *root) {
|
||||
}
|
||||
|
||||
bool copy_ownership = fstype_can_ownership(p->format);
|
||||
usec_t ts = parse_source_date_epoch();
|
||||
|
||||
/* copy_tree_at() automatically copies the permissions of source directories to target directories if
|
||||
* it created them. However, the root directory is created by us, so we have to manually take care
|
||||
@@ -6854,7 +6866,7 @@ static int do_copy_files(Context *context, Partition *p, const char *root) {
|
||||
(void) copy_xattr(sfd, NULL, rfd, NULL, COPY_ALL_XATTRS);
|
||||
if (copy_ownership)
|
||||
(void) copy_access(sfd, rfd);
|
||||
(void) copy_times(sfd, rfd, 0);
|
||||
(void) copy_times_full(sfd, rfd, /* flags= */ 0, ts);
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -6866,7 +6878,6 @@ static int do_copy_files(Context *context, Partition *p, const char *root) {
|
||||
_cleanup_hashmap_free_ Hashmap *denylist = NULL;
|
||||
_cleanup_hashmap_free_ Hashmap *subvolumes_by_source_inode = NULL;
|
||||
_cleanup_close_ int sfd = -EBADF, pfd = -EBADF, tfd = -EBADF;
|
||||
usec_t ts = parse_source_date_epoch();
|
||||
|
||||
r = make_copy_files_denylist(context, p, line->source, line->target, &denylist);
|
||||
if (r < 0)
|
||||
@@ -6915,19 +6926,35 @@ static int do_copy_files(Context *context, Partition *p, const char *root) {
|
||||
if (pfd < 0)
|
||||
return log_error_errno(pfd, "Failed to open parent directory of target: %m");
|
||||
|
||||
r = copy_tree_at(
|
||||
r = copy_tree_at_full(
|
||||
sfd, ".",
|
||||
pfd, fn,
|
||||
uid, gid,
|
||||
line->flags,
|
||||
denylist, subvolumes_by_source_inode);
|
||||
ts,
|
||||
denylist, subvolumes_by_source_inode,
|
||||
/* progress_path= */ NULL,
|
||||
/* progress_bytes= */ NULL,
|
||||
/* userdata= */ NULL);
|
||||
if (r >= 0) {
|
||||
/* Creating the target bumped the parent directory's mtime,
|
||||
* just like in the regular file case below. */
|
||||
r = clamp_directory_mtime(pfd, ts);
|
||||
if (r < 0)
|
||||
return log_error_errno(
|
||||
r, "Failed to set timestamp of '%s': %m", dn);
|
||||
}
|
||||
} else
|
||||
r = copy_tree_at(
|
||||
r = copy_tree_at_full(
|
||||
sfd, ".",
|
||||
tfd, ".",
|
||||
uid, gid,
|
||||
line->flags,
|
||||
denylist, subvolumes_by_source_inode);
|
||||
ts,
|
||||
denylist, subvolumes_by_source_inode,
|
||||
/* progress_path= */ NULL,
|
||||
/* progress_bytes= */ NULL,
|
||||
/* userdata= */ NULL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to copy '%s%s' to '%s%s': %m",
|
||||
strempty(arg_copy_source), line->source, strempty(root), line->target);
|
||||
@@ -6974,15 +7001,12 @@ static int do_copy_files(Context *context, Partition *p, const char *root) {
|
||||
(void) copy_xattr(sfd, NULL, tfd, NULL, COPY_ALL_XATTRS);
|
||||
if (copy_ownership)
|
||||
(void) copy_access(sfd, tfd);
|
||||
(void) copy_times(sfd, tfd, 0);
|
||||
(void) copy_times_full(sfd, tfd, /* flags= */ 0, ts);
|
||||
|
||||
if (ts != USEC_INFINITY) {
|
||||
struct timespec tspec;
|
||||
timespec_store(&tspec, ts);
|
||||
|
||||
if (futimens(pfd, (const struct timespec[2]) { TIMESPEC_OMIT, tspec }) < 0)
|
||||
return log_error_errno(errno, "Failed to set timestamp of '%s': %m", dn);
|
||||
}
|
||||
/* Creating the file bumped the parent directory's mtime. */
|
||||
r = clamp_directory_mtime(pfd, ts);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to set timestamp of '%s': %m", dn);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7017,6 +7041,7 @@ static int do_make_directories(Partition *p, const char *root) {
|
||||
}
|
||||
|
||||
static int do_make_symlinks(Partition *p, const char *root) {
|
||||
usec_t ts = parse_source_date_epoch();
|
||||
int r;
|
||||
|
||||
assert(p);
|
||||
@@ -7032,6 +7057,18 @@ static int do_make_symlinks(Partition *p, const char *root) {
|
||||
|
||||
if (symlinkat(*target, parent_fd, f) < 0)
|
||||
return log_error_errno(errno, "Failed to create symlink at %s to %s: %m", *path, *target);
|
||||
|
||||
/* The new symlink carries the wall clock, and creating it bumped the directory we put
|
||||
* it into. We are the last population step, so nothing overwrites this again. */
|
||||
if (ts != USEC_INFINITY &&
|
||||
utimensat(parent_fd, f,
|
||||
(const struct timespec[2]) { *TIMESPEC_STORE(ts), *TIMESPEC_STORE(ts) },
|
||||
AT_SYMLINK_NOFOLLOW) < 0)
|
||||
return log_error_errno(errno, "Failed to set timestamp of symlink '%s': %m", *path);
|
||||
|
||||
r = clamp_directory_mtime(parent_fd, ts);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to set timestamp of '%s': %m", *path);
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -7218,6 +7255,21 @@ static int partition_populate_directory(Context *context, Partition *p, char **r
|
||||
if (r < 0)
|
||||
return log_error_errno(errno, "Failed to create temporary directory: %m");
|
||||
|
||||
/* Nothing below touches its atime (adding an entry to a directory only moves mtime), so stamp here. */
|
||||
usec_t ts = parse_source_date_epoch();
|
||||
if (ts != USEC_INFINITY) {
|
||||
_cleanup_close_ int rfd = -EBADF;
|
||||
struct timespec tspec;
|
||||
|
||||
rfd = open(root, O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
|
||||
if (rfd < 0)
|
||||
return log_error_errno(errno, "Failed to open temporary directory: %m");
|
||||
|
||||
timespec_store(&tspec, ts);
|
||||
if (futimens(rfd, (const struct timespec[2]) { tspec, tspec }) < 0)
|
||||
return log_error_errno(errno, "Failed to set timestamp of temporary directory: %m");
|
||||
}
|
||||
|
||||
r = do_copy_files(context, p, root);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
Reference in New Issue
Block a user