diff --git a/man/tmpfiles.d.xml b/man/tmpfiles.d.xml index 6b2753549c..f3cc2ea50c 100644 --- a/man/tmpfiles.d.xml +++ b/man/tmpfiles.d.xml @@ -225,7 +225,17 @@ L /tmp/foobar - - - - /dev/null C - Recursively copy a file or directory, if the destination files or directories don't exist yet. + Recursively + copy a file or directory, if + the destination files or + directories don't exist + yet. Note that this command + will not descend into + subdirectories if the + destination directory already + exists, instead the entire + copy operation is + skipped. diff --git a/src/shared/copy.c b/src/shared/copy.c index 073b7279b4..867e49bf89 100644 --- a/src/shared/copy.c +++ b/src/shared/copy.c @@ -59,12 +59,8 @@ static int fd_copy_symlink(int df, const char *from, const struct stat *st, int if (r < 0) return r; - if (symlinkat(target, dt, to) < 0) { - if (errno == EEXIST) - return 0; - + if (symlinkat(target, dt, to) < 0) return -errno; - } if (fchownat(dt, to, st->st_uid, st->st_gid, AT_SYMLINK_NOFOLLOW) < 0) return -errno; @@ -85,12 +81,8 @@ static int fd_copy_regular(int df, const char *from, const struct stat *st, int return -errno; fdt = openat(dt, to, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, st->st_mode & 07777); - if (fdt < 0) { - if (errno == EEXIST) - return 0; - + if (fdt < 0) return -errno; - } r = copy_bytes(fdf, fdt); if (r < 0) { @@ -123,12 +115,8 @@ static int fd_copy_fifo(int df, const char *from, const struct stat *st, int dt, assert(to); r = mkfifoat(dt, to, st->st_mode & 07777); - if (r < 0) { - if (errno == EEXIST) - return 0; - + if (r < 0) return -errno; - } if (fchownat(dt, to, st->st_uid, st->st_gid, AT_SYMLINK_NOFOLLOW) < 0) r = -errno; @@ -147,12 +135,8 @@ static int fd_copy_node(int df, const char *from, const struct stat *st, int dt, assert(to); r = mknodat(dt, to, st->st_mode, st->st_rdev); - if (r < 0) { - if (errno == EEXIST) - return 0; - + if (r < 0) return -errno; - } if (fchownat(dt, to, st->st_uid, st->st_gid, AT_SYMLINK_NOFOLLOW) < 0) r = -errno; @@ -163,7 +147,7 @@ static int fd_copy_node(int df, const char *from, const struct stat *st, int dt, return r; } -static int fd_copy_directory(int df, const char *from, const struct stat *st, int dt, const char *to, dev_t original_device) { +static int fd_copy_directory(int df, const char *from, const struct stat *st, int dt, const char *to, dev_t original_device, bool merge) { _cleanup_close_ int fdf = -1, fdt = -1; _cleanup_closedir_ DIR *d = NULL; struct dirent *de; @@ -186,7 +170,7 @@ static int fd_copy_directory(int df, const char *from, const struct stat *st, in r = mkdirat(dt, to, st->st_mode & 07777); if (r >= 0) created = true; - else if (errno == EEXIST) + else if (errno == EEXIST && merge) created = false; else return -errno; @@ -219,7 +203,7 @@ static int fd_copy_directory(int df, const char *from, const struct stat *st, in if (S_ISREG(buf.st_mode)) q = fd_copy_regular(dirfd(d), de->d_name, &buf, fdt, de->d_name); else if (S_ISDIR(buf.st_mode)) - q = fd_copy_directory(dirfd(d), de->d_name, &buf, fdt, de->d_name, original_device); + q = fd_copy_directory(dirfd(d), de->d_name, &buf, fdt, de->d_name, original_device, merge); else if (S_ISLNK(buf.st_mode)) q = fd_copy_symlink(dirfd(d), de->d_name, &buf, fdt, de->d_name); else if (S_ISFIFO(buf.st_mode)) @@ -229,6 +213,9 @@ static int fd_copy_directory(int df, const char *from, const struct stat *st, in else q = -ENOTSUP; + if (q == -EEXIST && merge) + q = 0; + if (q < 0) r = q; } @@ -236,7 +223,7 @@ static int fd_copy_directory(int df, const char *from, const struct stat *st, in return r; } -int copy_tree(const char *from, const char *to) { +int copy_tree(const char *from, const char *to, bool merge) { struct stat st; assert(from); @@ -248,7 +235,7 @@ int copy_tree(const char *from, const char *to) { if (S_ISREG(st.st_mode)) return fd_copy_regular(AT_FDCWD, from, &st, AT_FDCWD, to); else if (S_ISDIR(st.st_mode)) - return fd_copy_directory(AT_FDCWD, from, &st, AT_FDCWD, to, st.st_dev); + return fd_copy_directory(AT_FDCWD, from, &st, AT_FDCWD, to, st.st_dev, merge); else if (S_ISLNK(st.st_mode)) return fd_copy_symlink(AT_FDCWD, from, &st, AT_FDCWD, to); else if (S_ISFIFO(st.st_mode)) diff --git a/src/shared/copy.h b/src/shared/copy.h index 8fb057fe86..1d5e0adc53 100644 --- a/src/shared/copy.h +++ b/src/shared/copy.h @@ -22,5 +22,5 @@ ***/ int copy_file(const char *from, const char *to, int flags, mode_t mode); -int copy_tree(const char *from, const char *to); +int copy_tree(const char *from, const char *to, bool merge); int copy_bytes(int fdf, int fdt); diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index c6c8ce8fee..cbad78aea5 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -667,10 +667,29 @@ static int create_item(Item *i) { break; case COPY_FILES: - r = copy_tree(i->argument, i->path); + r = copy_tree(i->argument, i->path, false); if (r < 0) { - log_error("Failed to copy files to %s: %s", i->path, strerror(-r)); - return r; + struct stat a, b; + + if (r != -EEXIST) { + log_error("Failed to copy files to %s: %s", i->path, strerror(-r)); + return -r; + } + + if (stat(i->argument, &a) < 0) { + log_error("stat(%s) failed: %m", i->argument); + return -errno; + } + + if (stat(i->path, &b) < 0) { + log_error("stat(%s) failed: %m", i->path); + return -errno; + } + + if ((a.st_mode ^ b.st_mode) & S_IFMT) { + log_debug("Can't copy to %s, file exists already and is of different type", i->path); + return 0; + } } r = item_set_perms(i, i->path); @@ -694,19 +713,21 @@ static int create_item(Item *i) { r = mkdir_label(i->path, i->mode); } - if (r < 0 && r != -EEXIST) { - log_error("Failed to create directory %s: %s", i->path, strerror(-r)); - return r; - } + if (r < 0) { + if (r != -EEXIST) { + log_error("Failed to create directory %s: %s", i->path, strerror(-r)); + return r; + } - if (stat(i->path, &st) < 0) { - log_error("stat(%s) failed: %m", i->path); - return -errno; - } + if (stat(i->path, &st) < 0) { + log_error("stat(%s) failed: %m", i->path); + return -errno; + } - if (!S_ISDIR(st.st_mode)) { - log_error("%s is not a directory.", i->path); - return -EEXIST; + if (!S_ISDIR(st.st_mode)) { + log_debug("%s already exists and is not a directory.", i->path); + return 0; + } } r = item_set_perms(i, i->path);