]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
copy: Add COPY_LOCK_BSD
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Fri, 24 Mar 2023 16:12:24 +0000 (17:12 +0100)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Thu, 15 Jun 2023 15:14:34 +0000 (17:14 +0200)
When making ephemeral copies of files/directories whose cleanup
depends on whether they're locked or not, it's necessary to have the
lock from the very beginning, so let's support that with a new
COPY_LOCK_BSD flag.

src/shared/copy.c
src/shared/copy.h
src/test/test-copy.c

index 9c2217856182f47d937799a4edc5f900a396ee85..db26945be8e0513db357980b7af6c32bbef652af 100644 (file)
@@ -167,6 +167,7 @@ int copy_bytes_full(
 
         assert(fdf >= 0);
         assert(fdt >= 0);
+        assert(!FLAGS_SET(copy_flags, COPY_LOCK_BSD));
 
         /* Tries to copy bytes from the file descriptor 'fdf' to 'fdt' in the smartest possible way. Copies a maximum
          * of 'max_bytes', which may be specified as UINT64_MAX, in which no maximum is applied. Returns negative on
@@ -931,7 +932,7 @@ static int fd_copy_directory(
 
         _cleanup_close_ int fdf = -EBADF, fdt = -EBADF;
         _cleanup_closedir_ DIR *d = NULL;
-        bool exists, created;
+        bool exists;
         int r;
 
         assert(st);
@@ -961,33 +962,22 @@ static int fd_copy_directory(
         if (!d)
                 return -errno;
 
-        exists = false;
-        if (copy_flags & COPY_MERGE_EMPTY) {
-                r = dir_is_empty_at(dt, to, /* ignore_hidden_or_backup= */ false);
-                if (r < 0 && r != -ENOENT)
-                        return r;
-                else if (r == 1)
-                        exists = true;
-        }
+        r = dir_is_empty_at(dt, to, /* ignore_hidden_or_backup= */ false);
+        if (r < 0 && r != -ENOENT)
+                return r;
+        if ((r > 0 && !(copy_flags & (COPY_MERGE|COPY_MERGE_EMPTY))) || (r == 0 && !FLAGS_SET(copy_flags, COPY_MERGE)))
+                return -EEXIST;
 
-        if (exists)
-                created = false;
-        else {
-                if (copy_flags & COPY_MAC_CREATE)
-                        r = mkdirat_label(dt, to, st->st_mode & 07777);
-                else
-                        r = mkdirat(dt, to, st->st_mode & 07777);
-                if (r >= 0)
-                        created = true;
-                else if (errno == EEXIST && (copy_flags & COPY_MERGE))
-                        created = false;
-                else
-                        return -errno;
-        }
+        exists = r >= 0;
 
-        fdt = openat(dt, to, O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW);
+        fdt = xopenat_lock(dt, to,
+                           O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW|(exists ? 0 : O_CREAT|O_EXCL),
+                           (copy_flags & COPY_MAC_CREATE ? XO_LABEL : 0),
+                           st->st_mode & 07777,
+                           copy_flags & COPY_LOCK_BSD ? LOCK_BSD : LOCK_NONE,
+                           LOCK_EX);
         if (fdt < 0)
-                return -errno;
+                return fdt;
 
         r = 0;
 
@@ -1063,9 +1053,9 @@ static int fd_copy_directory(
                 }
 
                 q = fd_copy_tree_generic(dirfd(d), de->d_name, &buf, fdt, de->d_name, original_device,
-                                         depth_left-1, override_uid, override_gid, copy_flags, denylist,
-                                         hardlink_context, child_display_path, progress_path, progress_bytes,
-                                         userdata);
+                                         depth_left-1, override_uid, override_gid, copy_flags & ~COPY_LOCK_BSD,
+                                         denylist, hardlink_context, child_display_path, progress_path,
+                                         progress_bytes, userdata);
 
                 if (q == -EINTR) /* Propagate SIGINT/SIGTERM up instantly */
                         return q;
@@ -1076,7 +1066,7 @@ static int fd_copy_directory(
         }
 
 finish:
-        if (created) {
+        if (!exists) {
                 if (fchown(fdt,
                            uid_is_valid(override_uid) ? override_uid : st->st_uid,
                            gid_is_valid(override_gid) ? override_gid : st->st_gid) < 0)
@@ -1094,7 +1084,7 @@ finish:
                         return -errno;
         }
 
-        return r;
+        return copy_flags & COPY_LOCK_BSD ? TAKE_FD(fdt) : 0;
 }
 
 static int fd_copy_leaf(
@@ -1188,6 +1178,7 @@ int copy_tree_at_full(
 
         assert(from);
         assert(to);
+        assert(!FLAGS_SET(copy_flags, COPY_LOCK_BSD));
 
         if (fstatat(fdf, from, &st, AT_SYMLINK_NOFOLLOW) < 0)
                 return -errno;
@@ -1290,6 +1281,7 @@ int copy_file_fd_at_full(
         assert(dir_fdf >= 0 || dir_fdf == AT_FDCWD);
         assert(from);
         assert(fdt >= 0);
+        assert(!FLAGS_SET(copy_flags, COPY_LOCK_BSD));
 
         fdf = openat(dir_fdf, from, O_RDONLY|O_CLOEXEC|O_NOCTTY);
         if (fdf < 0)
@@ -1360,17 +1352,13 @@ int copy_file_at_full(
                 return r;
 
         WITH_UMASK(0000) {
-                if (copy_flags & COPY_MAC_CREATE) {
-                        r = mac_selinux_create_file_prepare_at(dir_fdt, to, S_IFREG);
-                        if (r < 0)
-                                return r;
-                }
-                fdt = openat(dir_fdt, to, flags|O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY,
-                           mode != MODE_INVALID ? mode : st.st_mode);
-                if (copy_flags & COPY_MAC_CREATE)
-                        mac_selinux_create_file_clear();
+                fdt = xopenat_lock(dir_fdt, to,
+                                   flags|O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY,
+                                   (copy_flags & COPY_MAC_CREATE ? XO_LABEL : 0),
+                                   mode != MODE_INVALID ? mode : st.st_mode,
+                                   copy_flags & COPY_LOCK_BSD ? LOCK_BSD : LOCK_NONE, LOCK_EX);
                 if (fdt < 0)
-                        return -errno;
+                        return fdt;
         }
 
         if (!FLAGS_SET(flags, O_EXCL)) { /* if O_EXCL was used we created the thing as regular file, no need to check again */
@@ -1382,7 +1370,7 @@ int copy_file_at_full(
         if (chattr_mask != 0)
                 (void) chattr_fd(fdt, chattr_flags, chattr_mask & CHATTR_EARLY_FL, NULL);
 
-        r = copy_bytes_full(fdf, fdt, UINT64_MAX, copy_flags, NULL, NULL, progress_bytes, userdata);
+        r = copy_bytes_full(fdf, fdt, UINT64_MAX, copy_flags & ~COPY_LOCK_BSD, NULL, NULL, progress_bytes, userdata);
         if (r < 0)
                 goto fail;
 
@@ -1399,9 +1387,11 @@ int copy_file_at_full(
                 }
         }
 
-        r = close_nointr(TAKE_FD(fdt)); /* even if this fails, the fd is now invalidated */
-        if (r < 0)
-                goto fail;
+        if (!FLAGS_SET(copy_flags, COPY_LOCK_BSD)) {
+                r = close_nointr(TAKE_FD(fdt)); /* even if this fails, the fd is now invalidated */
+                if (r < 0)
+                        goto fail;
+        }
 
         if (copy_flags & COPY_FSYNC_FULL) {
                 r = fsync_parent_at(dir_fdt, to);
@@ -1409,7 +1399,7 @@ int copy_file_at_full(
                         goto fail;
         }
 
-        return 0;
+        return copy_flags & COPY_LOCK_BSD ? TAKE_FD(fdt) : 0;
 
 fail:
         /* Only unlink if we definitely are the ones who created the file */
@@ -1437,6 +1427,7 @@ int copy_file_atomic_at_full(
 
         assert(from);
         assert(to);
+        assert(!FLAGS_SET(copy_flags, COPY_LOCK_BSD));
 
         if (copy_flags & COPY_MAC_CREATE) {
                 r = mac_selinux_create_file_prepare_at(dir_fdt, to, S_IFREG);
index da084b66f299e8925499a928c6bf31300c8960a1..3bf496e42bd0c4587b1b7a277014f3b11ffb11c3 100644 (file)
@@ -29,6 +29,7 @@ typedef enum CopyFlags {
         COPY_HOLES         = 1 << 14, /* Copy holes */
         COPY_GRACEFUL_WARN = 1 << 15, /* Skip copying file types that aren't supported by the target filesystem */
         COPY_TRUNCATE      = 1 << 16, /* Truncate to current file offset after copying */
+        COPY_LOCK_BSD      = 1 << 17, /* Return a BSD exclusively locked file descriptor referring to the copied image/directory. */
 } CopyFlags;
 
 typedef enum DenyType {
index f5bcb8756f9d09e18627279c30494798d0c036f6..72aea4efb6d957afc193304a48f6df822a99da13 100644 (file)
@@ -1,5 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 
+#include <sys/file.h>
 #include <sys/xattr.h>
 #include <unistd.h>
 
@@ -435,4 +436,24 @@ TEST_RET(copy_holes) {
         return 0;
 }
 
+TEST(copy_lock) {
+        _cleanup_(rm_rf_physical_and_freep) char *t = NULL;
+        _cleanup_close_ int tfd = -EBADF, fd = -EBADF;
+
+        assert_se((tfd = mkdtemp_open(NULL, 0, &t)) >= 0);
+        assert_se(mkdirat(tfd, "abc", 0755) >= 0);
+        assert_se(write_string_file_at(tfd, "abc/def", "abc", WRITE_STRING_FILE_CREATE) >= 0);
+
+        assert_se((fd = copy_directory_at(tfd, "abc", tfd, "qed", COPY_LOCK_BSD)) >= 0);
+        assert_se(faccessat(tfd, "qed", F_OK, 0) >= 0);
+        assert_se(faccessat(tfd, "qed/def", F_OK, 0) >= 0);
+        assert_se(xopenat_lock(tfd, "qed", 0, 0, 0, LOCK_BSD, LOCK_EX|LOCK_NB) == -EAGAIN);
+        fd = safe_close(fd);
+
+        assert_se((fd = copy_file_at(tfd, "abc/def", tfd, "poi", 0, 0644, COPY_LOCK_BSD)));
+        assert_se(read_file_at_and_streq(tfd, "poi", "abc\n"));
+        assert_se(xopenat_lock(tfd, "poi", 0, 0, 0, LOCK_BSD, LOCK_EX|LOCK_NB) == -EAGAIN);
+        fd = safe_close(fd);
+}
+
 DEFINE_TEST_MAIN(LOG_DEBUG);