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 - <