diff --git a/man/tmpfiles.d.xml b/man/tmpfiles.d.xml
index 5d393f39840..bc6c8cdf432 100644
--- a/man/tmpfiles.d.xml
+++ b/man/tmpfiles.d.xml
@@ -282,11 +282,11 @@ L /tmp/foobar - - - - /dev/null
C
Recursively copy a file or directory, if the
- destination files or directories do not exist yet. Note that
- this command will not descend into subdirectories if the
- destination directory already exists. Instead, the entire
- copy operation is skipped. If the argument is omitted, files
- from the source directory
+ destination files or directories do not exist yet or the
+ destination directory is empty. Note that this command will not
+ descend into subdirectories if the destination directory already
+ exists and is not empty. Instead, the entire copy operation is
+ skipped. If the argument is omitted, files from the source directory
/usr/share/factory/ with the same name
are copied. Does not follow symlinks.
diff --git a/src/basic/copy.c b/src/basic/copy.c
index 34e01ea1cf4..46e02a37593 100644
--- a/src/basic/copy.c
+++ b/src/basic/copy.c
@@ -24,6 +24,7 @@
#include "macro.h"
#include "missing.h"
#include "mountpoint-util.h"
+#include "stat-util.h"
#include "string-util.h"
#include "strv.h"
#include "time-util.h"
@@ -501,7 +502,7 @@ static int fd_copy_directory(
_cleanup_close_ int fdf = -1, fdt = -1;
_cleanup_closedir_ DIR *d = NULL;
struct dirent *de;
- bool created;
+ bool exists, created;
int r;
assert(st);
@@ -522,13 +523,26 @@ static int fd_copy_directory(
return -errno;
fdf = -1;
- r = mkdirat(dt, to, st->st_mode & 07777);
- if (r >= 0)
- created = true;
- else if (errno == EEXIST && (copy_flags & COPY_MERGE))
+ exists = false;
+ if (copy_flags & COPY_MERGE_EMPTY) {
+ r = dir_is_empty_at(dt, to);
+ if (r < 0 && r != -ENOENT)
+ return r;
+ else if (r == 1)
+ exists = true;
+ }
+
+ if (exists)
created = false;
- else
- return -errno;
+ 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;
+ }
fdt = openat(dt, to, O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW);
if (fdt < 0)
diff --git a/src/basic/copy.h b/src/basic/copy.h
index a41b44c70ab..f6770218819 100644
--- a/src/basic/copy.h
+++ b/src/basic/copy.h
@@ -9,10 +9,11 @@
#include
typedef enum CopyFlags {
- COPY_REFLINK = 1 << 0, /* Try to reflink */
- COPY_MERGE = 1 << 1, /* Merge existing trees with our new one to copy */
- COPY_REPLACE = 1 << 2, /* Replace an existing file if there's one */
- COPY_SAME_MOUNT = 1 << 3, /* Don't descend recursively into other file systems, across mount point boundaries */
+ COPY_REFLINK = 1 << 0, /* Try to reflink */
+ COPY_MERGE = 1 << 1, /* Merge existing trees with our new one to copy */
+ COPY_REPLACE = 1 << 2, /* Replace an existing file if there's one */
+ COPY_SAME_MOUNT = 1 << 3, /* Don't descend recursively into other file systems, across mount point boundaries */
+ COPY_MERGE_EMPTY = 1 << 4, /* Merge an existing, empty directory with our new tree to copy */
} CopyFlags;
typedef int (*copy_progress_bytes_t)(uint64_t n_bytes, void *userdata);
diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c
index 0af869cf652..ea2bbc368b1 100644
--- a/src/basic/stat-util.c
+++ b/src/basic/stat-util.c
@@ -67,13 +67,22 @@ int is_device_node(const char *path) {
return !!(S_ISBLK(info.st_mode) || S_ISCHR(info.st_mode));
}
-int dir_is_empty(const char *path) {
- _cleanup_closedir_ DIR *d;
+int dir_is_empty_at(int dir_fd, const char *path) {
+ _cleanup_close_ int fd = -1;
+ _cleanup_closedir_ DIR *d = NULL;
struct dirent *de;
- d = opendir(path);
+ if (path)
+ fd = openat(dir_fd, path, O_RDONLY|O_DIRECTORY|O_CLOEXEC);
+ else
+ fd = fcntl(fd, F_DUPFD_CLOEXEC, 3);
+ if (fd < 0)
+ return -errno;
+
+ d = fdopendir(fd);
if (!d)
return -errno;
+ fd = -1;
FOREACH_DIRENT(de, d, return -errno)
return 0;
diff --git a/src/basic/stat-util.h b/src/basic/stat-util.h
index 0a08e642b54..74fb7251b38 100644
--- a/src/basic/stat-util.h
+++ b/src/basic/stat-util.h
@@ -15,7 +15,10 @@ int is_dir(const char *path, bool follow);
int is_dir_fd(int fd);
int is_device_node(const char *path);
-int dir_is_empty(const char *path);
+int dir_is_empty_at(int dir_fd, const char *path);
+static inline int dir_is_empty(const char *path) {
+ return dir_is_empty_at(AT_FDCWD, path);
+}
static inline int dir_is_populated(const char *path) {
int r;
diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c
index 19225f8cfd7..03a9e1d1f02 100644
--- a/src/tmpfiles/tmpfiles.c
+++ b/src/tmpfiles/tmpfiles.c
@@ -1463,7 +1463,7 @@ static int copy_files(Item *i) {
dfd, bn,
i->uid_set ? i->uid : UID_INVALID,
i->gid_set ? i->gid : GID_INVALID,
- COPY_REFLINK);
+ COPY_REFLINK | COPY_MERGE_EMPTY);
if (r < 0) {
struct stat a, b;
diff --git a/test/TEST-22-TMPFILES/test-02.sh b/test/TEST-22-TMPFILES/test-02.sh
index fe8b9032983..d1bf1ea04b9 100755
--- a/test/TEST-22-TMPFILES/test-02.sh
+++ b/test/TEST-22-TMPFILES/test-02.sh
@@ -6,8 +6,8 @@
set -e
set -x
-rm -fr /tmp/{d,D,e}
-mkdir /tmp/{d,D,e}
+rm -fr /tmp/{C,d,D,e}
+mkdir /tmp/{C,d,D,e}
#
# 'd'
@@ -93,3 +93,30 @@ test $(stat -c %U:%G:%a /tmp/e/3/d2) = "daemon:daemon:755"
test -f /tmp/e/3/f1
test $(stat -c %U:%G:%a /tmp/e/3/f1) = "root:root:644"
+
+#
+# 'C'
+#
+
+mkdir /tmp/C/{1,2,3}-origin
+touch /tmp/C/{1,2,3}-origin/f1
+chmod 755 /tmp/C/{1,2,3}-origin/f1
+
+mkdir /tmp/C/{2,3}
+touch /tmp/C/3/f1
+
+systemd-tmpfiles --create - <