]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
copy: Respect COPY_REPLACE flag for copy_tree
authorAlexander Wilson <rdtscp@fb.com>
Fri, 22 Jul 2022 11:13:31 +0000 (04:13 -0700)
committerAlexander Wilson <rdtscp@fb.com>
Wed, 27 Jul 2022 15:40:59 +0000 (08:40 -0700)
- Add a test that asserts that copy_tree on an existing file will fail without COPY_REPLACE
- Add a test that asserts that copy_tree with COPY_MERGE and COPY_REPLACE on an existing directory will overwrite files that already exist.

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

index 3c5f296d2d20f233669ea3b3e6e4e981f566443c..182544a206b755666014d88a983d91dc1596e242 100644 (file)
@@ -1090,9 +1090,17 @@ static int fd_copy_tree_generic(
         int r;
 
         if (S_ISDIR(st->st_mode))
-                r = fd_copy_directory(df, from, st, dt, to, original_device, depth_left-1, override_uid, override_gid, copy_flags, hardlink_context, display_path, progress_path, progress_bytes, userdata);
-        else
+                return fd_copy_directory(df, from, st, dt, to, original_device, depth_left-1, override_uid, override_gid, copy_flags, hardlink_context, display_path, progress_path, progress_bytes, userdata);
+
+        r = fd_copy_leaf(df, from, st, dt, to, override_uid, override_gid, copy_flags, hardlink_context, display_path, progress_bytes, userdata);
+        /* We just tried to copy a leaf node of the tree. If it failed because the node already exists *and* the COPY_REPLACE flag has been provided, we should unlink the node and re-copy. */
+        if (r == -EEXIST && (copy_flags & COPY_REPLACE)) {
+                /* This codepath is us trying to address an error to copy, if the unlink fails, lets just return the original error. */
+                if (unlinkat(dt, to, 0) < 0)
+                        return r;
+
                 r = fd_copy_leaf(df, from, st, dt, to, override_uid, override_gid, copy_flags, hardlink_context, display_path, progress_bytes, userdata);
+        }
 
         return r;
 }
index a669df644baf7450a6c5303473b73629fab058df..09e78b920e9ada79906f166d208cdca68a9eb846 100644 (file)
@@ -50,6 +50,75 @@ TEST(copy_file) {
         unlink(fn_copy);
 }
 
+static bool read_file_and_streq(const char* filepath, const char* expected_contents) {
+        _cleanup_free_ char *buf = NULL;
+
+        assert_se(read_full_file(filepath, &buf, NULL) == 0);
+        return streq(buf, expected_contents);
+}
+
+TEST(copy_tree_replace_file) {
+        _cleanup_free_ char *src = NULL, *dst = NULL;
+
+        assert_se(tempfn_random("/tmp/test-copy_file.XXXXXX", NULL, &src) >= 0);
+        assert_se(tempfn_random("/tmp/test-copy_file.XXXXXX", NULL, &dst) >= 0);
+
+        assert_se(write_string_file(src, "bar bar", WRITE_STRING_FILE_CREATE) == 0);
+        assert_se(write_string_file(dst, "foo foo foo", WRITE_STRING_FILE_CREATE) == 0);
+
+        /* The file exists- now overwite original contents, and test the COPY_REPLACE flag. */
+
+        assert_se(copy_tree(src, dst, UID_INVALID, GID_INVALID, COPY_REFLINK) == -EEXIST);
+
+        assert_se(read_file_and_streq(dst, "foo foo foo\n"));
+
+        assert_se(copy_tree(src, dst, UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE) == 0);
+
+        assert_se(read_file_and_streq(dst, "bar bar\n"));
+}
+
+TEST(copy_tree_replace_dirs) {
+        _cleanup_free_ char *src_path1 = NULL, *src_path2 = NULL, *dst_path1 = NULL, *dst_path2 = NULL;
+        _cleanup_(rm_rf_physical_and_freep) char *src_directory = NULL, *dst_directory = NULL;
+        const char *file1 = "foo_file", *file2 = "bar_file";
+
+        /* Create the random source/destination directories */
+        assert_se(mkdtemp_malloc("/tmp/dirXXXXXX", &src_directory) >= 0);
+        assert_se(mkdtemp_malloc("/tmp/dirXXXXXX", &dst_directory) >= 0);
+
+        /* Construct the source/destination filepaths (should have different dir name, but same file names within) */
+        assert_se(src_path1 = path_join(src_directory, file1));
+        assert_se(src_path2 = path_join(src_directory, file2));
+        assert_se(dst_path1 = path_join(dst_directory, file1));
+        assert_se(dst_path2 = path_join(dst_directory, file2));
+
+        /* Populate some data to differentiate the files. */
+        assert_se(write_string_file(src_path1, "src file 1", WRITE_STRING_FILE_CREATE) == 0);
+        assert_se(write_string_file(src_path2, "src file 2", WRITE_STRING_FILE_CREATE) == 0);
+
+        assert_se(write_string_file(dst_path1, "dest file 1", WRITE_STRING_FILE_CREATE) == 0);
+        assert_se(write_string_file(dst_path2, "dest file 2", WRITE_STRING_FILE_CREATE) == 0);
+
+        /* Copying without COPY_REPLACE should fail because the destination file already exists. */
+        assert_se(copy_tree(src_directory, dst_directory, UID_INVALID, GID_INVALID, COPY_REFLINK) == -EEXIST);
+
+        {
+                assert_se(read_file_and_streq(src_path1,  "src file 1\n"));
+                assert_se(read_file_and_streq(src_path2,  "src file 2\n"));
+                assert_se(read_file_and_streq(dst_path1,  "dest file 1\n"));
+                assert_se(read_file_and_streq(dst_path2,  "dest file 2\n"));
+        }
+
+        assert_se(copy_tree(src_directory, dst_directory, UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE|COPY_MERGE) == 0);
+
+        {
+                assert_se(read_file_and_streq(src_path1,  "src file 1\n"));
+                assert_se(read_file_and_streq(src_path2,  "src file 2\n"));
+                assert_se(read_file_and_streq(dst_path1,  "src file 1\n"));
+                assert_se(read_file_and_streq(dst_path2,  "src file 2\n"));
+        }
+}
+
 TEST(copy_file_fd) {
         char in_fn[] = "/tmp/test-copy-file-fd-XXXXXX";
         char out_fn[] = "/tmp/test-copy-file-fd-XXXXXX";