]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
repart: respect `SOURCE_DATE_EPOCH` on `mkdir_p_root`
authorMoritz Sanft <58110325+msanft@users.noreply.github.com>
Mon, 29 Apr 2024 11:03:40 +0000 (13:03 +0200)
committerLuca Boccassi <luca.boccassi@gmail.com>
Tue, 7 May 2024 07:29:58 +0000 (09:29 +0200)
This let's systemd-repart respect the `SOURCE_DATE_EPOCH` environment
variable when creating directories in the local tree through `CopyFiles`
or `MakeDirectories`.

To do this, we pass a timestamp `ts` to `mkdir_p_root`, which it will
use to fix up `mtime` and `atime` of the directory it creates as
well as the `mtime` of the directory it creates the other directory *in*,
as the `mtime` of the latter is modified when creating a directory in it.

For the same reason, it also needs to fixup the `mtime` of the upper
directory when copying a file into it through `CopyFiles`.

If `SOURCE_DATE_EPOCH`, times are left as is. (`UTIME_OMIT`)

src/basic/mkdir.c
src/basic/mkdir.h
src/partition/repart.c
src/shared/dissect-image.c
src/test/test-conf-parser.c
src/test/test-mkdir.c

index 7c1ad7dd64ad6b9096771b204260192729cf0caa..c378f896041fe5101e996f58595f3aa5e1325175 100644 (file)
@@ -204,7 +204,7 @@ int mkdir_p_safe(const char *prefix, const char *path, mode_t mode, uid_t uid, g
         return mkdir_p_internal(prefix, path, mode, uid, gid, flags, mkdirat_errno_wrapper);
 }
 
-int mkdir_p_root(const char *root, const char *p, uid_t uid, gid_t gid, mode_t m, char **subvolumes) {
+int mkdir_p_root_full(const char *root, const char *p, uid_t uid, gid_t gid, mode_t m, usec_t ts, char **subvolumes) {
         _cleanup_free_ char *pp = NULL, *bn = NULL;
         _cleanup_close_ int dfd = -EBADF;
         int r;
@@ -222,7 +222,7 @@ int mkdir_p_root(const char *root, const char *p, uid_t uid, gid_t gid, mode_t m
                 return r;
         else {
                 /* Extracting the parent dir worked, hence we aren't top-level? Recurse up first. */
-                r = mkdir_p_root(root, pp, uid, gid, m, subvolumes);
+                r = mkdir_p_root_full(root, pp, uid, gid, m, ts, subvolumes);
                 if (r < 0)
                         return r;
 
@@ -248,16 +248,27 @@ int mkdir_p_root(const char *root, const char *p, uid_t uid, gid_t gid, mode_t m
                 return r;
         }
 
-        if (uid_is_valid(uid) || gid_is_valid(gid)) {
-                _cleanup_close_ int nfd = -EBADF;
+        if (ts == USEC_INFINITY && !uid_is_valid(uid) && !gid_is_valid(gid))
+                return 1;
 
-                nfd = openat(dfd, bn, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
-                if (nfd < 0)
+        _cleanup_close_ int nfd = -EBADF;
+        nfd = openat(dfd, bn, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
+        if (nfd < 0)
+                return -errno;
+
+        if (ts != USEC_INFINITY) {
+                struct timespec tspec;
+
+                timespec_store_nsec(&tspec, ts);
+                if (futimens(dfd, (const struct timespec[2]) { { .tv_nsec = UTIME_OMIT }, tspec }) < 0)
                         return -errno;
 
-                if (fchown(nfd, uid, gid) < 0)
+                if (futimens(nfd, (const struct timespec[2]) { tspec, tspec }) < 0)
                         return -errno;
         }
 
+        if ((uid_is_valid(uid) || gid_is_valid(gid)) && fchown(nfd, uid, gid) < 0)
+                return -errno;
+
         return 1;
 }
index e5387483e23190c5f7ad9c88b58dfc5642e65b07..471f45b69a0def9a211ef1007b03346d91ccdee9 100644 (file)
@@ -4,6 +4,8 @@
 #include <fcntl.h>
 #include <sys/types.h>
 
+#include "time-util.h"
+
 typedef enum MkdirFlags {
         MKDIR_FOLLOW_SYMLINK  = 1 << 0,
         MKDIR_IGNORE_EXISTING = 1 << 1,  /* Quietly accept a preexisting directory (or file) */
@@ -23,7 +25,10 @@ static inline int mkdir_parents(const char *path, mode_t mode) {
 int mkdir_parents_safe(const char *prefix, const char *path, mode_t mode, uid_t uid, gid_t gid, MkdirFlags flags);
 int mkdir_p(const char *path, mode_t mode);
 int mkdir_p_safe(const char *prefix, const char *path, mode_t mode, uid_t uid, gid_t gid, MkdirFlags flags);
-int mkdir_p_root(const char *root, const char *p, uid_t uid, gid_t gid, mode_t m, char **subvolumes);
+int mkdir_p_root_full(const char *root, const char *p, uid_t uid, gid_t gid, mode_t m, usec_t ts, char **subvolumes);
+static inline int mkdir_p_root(const char *root, const char *p, uid_t uid, gid_t gid, mode_t m) {
+        return mkdir_p_root_full(root, p, uid, gid, m, USEC_INFINITY, NULL);
+}
 
 /* The following are used to implement the mkdir_xyz_label() calls, don't use otherwise. */
 typedef int (*mkdirat_func_t)(int dir_fd, const char *pathname, mode_t mode);
index e35062f682248016bbaad6269e28bd7b3a739f83..8442388359c109eaeb835402d1c97e4fafe7c98c 100644 (file)
@@ -4774,6 +4774,30 @@ static int make_subvolumes_set(
         return 0;
 }
 
+static usec_t epoch_or_infinity(void) {
+        static usec_t cache;
+        static bool cached = false;
+        uint64_t epoch;
+        int r;
+
+        if (cached)
+                return cache;
+
+        r = secure_getenv_uint64("SOURCE_DATE_EPOCH", &epoch);
+        if (r >= 0) {
+                if (epoch <= UINT64_MAX / USEC_PER_SEC) { /* Overflow check */
+                        cached = true;
+                        return (cache = epoch * USEC_PER_SEC);
+                }
+                r = -ERANGE;
+        }
+        if (r != -ENXIO)
+                log_debug_errno(r, "Failed to parse $SOURCE_DATE_EPOCH, ignoring: %m");
+
+        cached = true;
+        return (cache = USEC_INFINITY);
+}
+
 static int do_copy_files(Context *context, Partition *p, const char *root) {
         int r;
 
@@ -4809,6 +4833,7 @@ static int do_copy_files(Context *context, Partition *p, const char *root) {
                 _cleanup_hashmap_free_ Hashmap *denylist = NULL;
                 _cleanup_set_free_ Set *subvolumes_by_source_inode = NULL;
                 _cleanup_close_ int sfd = -EBADF, pfd = -EBADF, tfd = -EBADF;
+                usec_t ts = epoch_or_infinity();
 
                 r = make_copy_files_denylist(context, p, *source, *target, &denylist);
                 if (r < 0)
@@ -4847,7 +4872,7 @@ static int do_copy_files(Context *context, Partition *p, const char *root) {
                                 if (r < 0)
                                         return log_error_errno(r, "Failed to extract directory from '%s': %m", *target);
 
-                                r = mkdir_p_root(root, dn, UID_INVALID, GID_INVALID, 0755, p->subvolumes);
+                                r = mkdir_p_root_full(root, dn, UID_INVALID, GID_INVALID, 0755, ts, p->subvolumes);
                                 if (r < 0)
                                         return log_error_errno(r, "Failed to create parent directory '%s': %m", dn);
 
@@ -4873,6 +4898,7 @@ static int do_copy_files(Context *context, Partition *p, const char *root) {
                                                        strempty(arg_copy_source), *source, strempty(root), *target);
                 } else {
                         _cleanup_free_ char *dn = NULL, *fn = NULL;
+                        struct timespec tspec;
 
                         /* We are looking at a regular file */
 
@@ -4887,7 +4913,7 @@ static int do_copy_files(Context *context, Partition *p, const char *root) {
                         if (r < 0)
                                 return log_error_errno(r, "Failed to extract directory from '%s': %m", *target);
 
-                        r = mkdir_p_root(root, dn, UID_INVALID, GID_INVALID, 0755, p->subvolumes);
+                        r = mkdir_p_root_full(root, dn, UID_INVALID, GID_INVALID, 0755, ts, p->subvolumes);
                         if (r < 0)
                                 return log_error_errno(r, "Failed to create parent directory: %m");
 
@@ -4906,6 +4932,11 @@ static int do_copy_files(Context *context, Partition *p, const char *root) {
                         (void) copy_xattr(sfd, NULL, tfd, NULL, COPY_ALL_XATTRS);
                         (void) copy_access(sfd, tfd);
                         (void) copy_times(sfd, tfd, 0);
+
+                        timespec_store_nsec(&tspec, ts);
+
+                        if (ts != USEC_INFINITY && futimens(pfd, (const struct timespec[2]) { { .tv_nsec = UTIME_OMIT }, tspec }) < 0)
+                                return -errno;
                 }
         }
 
@@ -4919,7 +4950,7 @@ static int do_make_directories(Partition *p, const char *root) {
         assert(root);
 
         STRV_FOREACH(d, p->make_directories) {
-                r = mkdir_p_root(root, *d, UID_INVALID, GID_INVALID, 0755, p->subvolumes);
+                r = mkdir_p_root_full(root, *d, UID_INVALID, GID_INVALID, 0755, epoch_or_infinity(), p->subvolumes);
                 if (r < 0)
                         return log_error_errno(r, "Failed to create directory '%s' in file system: %m", *d);
         }
index cd49276bd315434a06e20e27ed50bef490a86f4b..471e83b07480b49b09920d5aa234da4e8fd7705c 100644 (file)
@@ -2008,7 +2008,7 @@ static int mount_partition(
         if (where) {
                 if (directory) {
                         /* Automatically create missing mount points inside the image, if necessary. */
-                        r = mkdir_p_root(where, directory, uid_shift, (gid_t) uid_shift, 0755, NULL);
+                        r = mkdir_p_root(where, directory, uid_shift, (gid_t) uid_shift, 0755);
                         if (r < 0 && r != -EROFS)
                                 return r;
 
index 280abb53774abf3de2484a2173255450deaa7a99..4e236bddff54191232424de8f4e12543a222f4bf 100644 (file)
@@ -398,10 +398,10 @@ TEST(config_parse_standard_file_with_dropins_full) {
         int r;
 
         assert_se(mkdtemp_malloc(NULL, &root) >= 0);
-        assert_se(mkdir_p_root(root, "/etc/kernel/install.conf.d", UID_INVALID, GID_INVALID, 0755, NULL));
-        assert_se(mkdir_p_root(root, "/run/kernel/install.conf.d", UID_INVALID, GID_INVALID, 0755, NULL));
-        assert_se(mkdir_p_root(root, "/usr/lib/kernel/install.conf.d", UID_INVALID, GID_INVALID, 0755, NULL));
-        assert_se(mkdir_p_root(root, "/usr/local/lib/kernel/install.conf.d", UID_INVALID, GID_INVALID, 0755, NULL));
+        assert_se(mkdir_p_root(root, "/etc/kernel/install.conf.d", UID_INVALID, GID_INVALID, 0755));
+        assert_se(mkdir_p_root(root, "/run/kernel/install.conf.d", UID_INVALID, GID_INVALID, 0755));
+        assert_se(mkdir_p_root(root, "/usr/lib/kernel/install.conf.d", UID_INVALID, GID_INVALID, 0755));
+        assert_se(mkdir_p_root(root, "/usr/local/lib/kernel/install.conf.d", UID_INVALID, GID_INVALID, 0755));
 
         rfd = open(root, O_CLOEXEC|O_DIRECTORY);
         assert_se(rfd >= 0);
@@ -459,10 +459,10 @@ TEST(config_parse_standard_file_with_dropins_full) {
         assert_se(strv_length(dropins) == 4);
 
         /* Make sure that we follow symlinks */
-        assert_se(mkdir_p_root(root, "/etc/kernel/install2.conf.d", UID_INVALID, GID_INVALID, 0755, NULL));
-        assert_se(mkdir_p_root(root, "/run/kernel/install2.conf.d", UID_INVALID, GID_INVALID, 0755, NULL));
-        assert_se(mkdir_p_root(root, "/usr/lib/kernel/install2.conf.d", UID_INVALID, GID_INVALID, 0755, NULL));
-        assert_se(mkdir_p_root(root, "/usr/local/lib/kernel/install2.conf.d", UID_INVALID, GID_INVALID, 0755, NULL));
+        assert_se(mkdir_p_root(root, "/etc/kernel/install2.conf.d", UID_INVALID, GID_INVALID, 0755));
+        assert_se(mkdir_p_root(root, "/run/kernel/install2.conf.d", UID_INVALID, GID_INVALID, 0755));
+        assert_se(mkdir_p_root(root, "/usr/lib/kernel/install2.conf.d", UID_INVALID, GID_INVALID, 0755));
+        assert_se(mkdir_p_root(root, "/usr/local/lib/kernel/install2.conf.d", UID_INVALID, GID_INVALID, 0755));
 
         /* (Those symlinks are only useful relative to <root>. */
         assert_se(symlinkat("/usr/lib/kernel/install.conf", rfd, "usr/lib/kernel/install2.conf") == 0);
index 4820b3251a3f0204b0a193f342dadb98cf3ad26d..fcb8fc7a6b4749de85101e34dd42cb9f08cce72e 100644 (file)
@@ -96,7 +96,7 @@ TEST(mkdir_p_root) {
         assert_se(mkdtemp_malloc("/tmp/test-mkdir-XXXXXX", &tmp) >= 0);
 
         assert_se(p = path_join(tmp, "run/aaa/bbb"));
-        assert_se(mkdir_p_root(tmp, "/run/aaa/bbb", UID_INVALID, GID_INVALID, 0755, NULL) >= 0);
+        assert_se(mkdir_p_root(tmp, "/run/aaa/bbb", UID_INVALID, GID_INVALID, 0755) >= 0);
         assert_se(is_dir(p, false) > 0);
         assert_se(is_dir(p, true) > 0);
 
@@ -109,18 +109,18 @@ TEST(mkdir_p_root) {
 
         p = mfree(p);
         assert_se(p = path_join(tmp, "var/run/hoge/foo/baz"));
-        assert_se(mkdir_p_root(tmp, "/var/run/hoge/foo/baz", UID_INVALID, GID_INVALID, 0755, NULL) >= 0);
+        assert_se(mkdir_p_root(tmp, "/var/run/hoge/foo/baz", UID_INVALID, GID_INVALID, 0755) >= 0);
         assert_se(is_dir(p, false) > 0);
         assert_se(is_dir(p, true) > 0);
 
         p = mfree(p);
         assert_se(p = path_join(tmp, "not-exists"));
-        assert_se(mkdir_p_root(p, "/aaa", UID_INVALID, GID_INVALID, 0755, NULL) == -ENOENT);
+        assert_se(mkdir_p_root(p, "/aaa", UID_INVALID, GID_INVALID, 0755) == -ENOENT);
 
         p = mfree(p);
         assert_se(p = path_join(tmp, "regular-file"));
         assert_se(touch(p) >= 0);
-        assert_se(mkdir_p_root(p, "/aaa", UID_INVALID, GID_INVALID, 0755, NULL) == -ENOTDIR);
+        assert_se(mkdir_p_root(p, "/aaa", UID_INVALID, GID_INVALID, 0755) == -ENOTDIR);
 
         /* FIXME: The tests below do not work.
         p = mfree(p);
@@ -138,4 +138,31 @@ TEST(mkdir_p_root) {
         */
 }
 
+TEST(mkdir_p_root_full) {
+        _cleanup_(rm_rf_physical_and_freep) char *tmp = NULL;
+        _cleanup_free_ char *p = NULL;
+        struct stat st;
+
+        assert_se(mkdtemp_malloc("/tmp/test-mkdir-XXXXXX", &tmp) >= 0);
+
+        assert_se(p = path_join(tmp, "foo"));
+        assert_se(mkdir_p_root_full(tmp, "/foo", UID_INVALID, GID_INVALID, 0755, 1234, NULL) >= 0);
+        assert_se(is_dir(p, false) > 0);
+        assert_se(is_dir(p, true) > 0);
+        assert_se(stat(p, &st) >= 0);
+        assert_se(st.st_mtim.tv_nsec == 1234);
+        assert_se(st.st_atim.tv_nsec == 1234);
+
+        p = mfree(p);
+        assert_se(p = path_join(tmp, "dir-not-exists/foo"));
+        assert_se(mkdir_p_root_full(tmp, "/dir-not-exists/foo", UID_INVALID, GID_INVALID, 0755, 5678, NULL) >= 0);
+        assert_se(is_dir(p, false) > 0);
+        assert_se(is_dir(p, true) > 0);
+        p = mfree(p);
+        assert_se(p = path_join(tmp, "dir-not-exists"));
+        assert_se(stat(p, &st) >= 0);
+        assert_se(st.st_mtim.tv_nsec == 5678);
+        assert_se(st.st_atim.tv_nsec == 5678);
+}
+
 DEFINE_TEST_MAIN(LOG_DEBUG);