chiark / gitweb /
tmpfiles: add new "C" line for copying files or directories
authorLennart Poettering <lennart@poettering.net>
Tue, 10 Jun 2014 21:02:40 +0000 (23:02 +0200)
committerLennart Poettering <lennart@poettering.net>
Tue, 10 Jun 2014 21:02:40 +0000 (23:02 +0200)
Makefile.am
man/tmpfiles.d.xml
src/nspawn/nspawn.c
src/shared/copy.c [new file with mode: 0644]
src/shared/copy.h [new file with mode: 0644]
src/shared/util.c
src/shared/util.h
src/tmpfiles/tmpfiles.c

index 4ff9f5a4fd53be55a2f12f4ee4a22f64135e170d..c67f0634b19ef6c37734b8e641f197859630cb22 100644 (file)
@@ -808,7 +808,9 @@ libsystemd_shared_la_SOURCES = \
        src/shared/async.c \
        src/shared/async.h \
        src/shared/eventfd-util.c \
-       src/shared/eventfd-util.h
+       src/shared/eventfd-util.h \
+       src/shared/copy.c \
+       src/shared/copy.h
 
 nodist_libsystemd_shared_la_SOURCES = \
        src/shared/errno-from-name.h \
index e54f1ba5c36cb920925c5d301a5367401266f721..20ed803a527653565f0c15dfdc02641d4f76c5df 100644 (file)
@@ -183,6 +183,11 @@ L    /tmp/foobar -    -    -    -   /dev/null</programlisting>
                                         <listitem><para>Create a block device node if it does not exist yet.</para></listitem>
                                 </varlistentry>
 
+                                <varlistentry>
+                                        <term><varname>C</varname></term>
+                                        <listitem><para>Recursively copy a file or directory, if the destination files or directories don't exist yet.</para></listitem>
+                                </varlistentry>
+
                                 <varlistentry>
                                         <term><varname>m</varname></term>
                                         <listitem><para>If the
@@ -446,8 +451,10 @@ r! /tmp/.X[0-9]*-lock</programlisting>
                         <varname>f</varname>, <varname>F</varname>,
                         and <varname>w</varname> may be used to
                         specify a short string that is written to the
-                        file, suffixed by a newline. Ignored for all
-                        other lines.</para>
+                        file, suffixed by a newline. For
+                        <varname>C</varname> specifies the source file
+                        or directory. Ignored for all other
+                        lines.</para>
                 </refsect2>
 
         </refsect1>
index 26ac1bf7df2369cce29bfdc2d0bf0573923b7fc5..867cf1926783ff07c9b7467e5ab4082931d5446e 100644 (file)
@@ -88,6 +88,7 @@
 #include "blkid-util.h"
 #include "gpt.h"
 #include "siphash24.h"
+#include "copy.h"
 
 #ifdef HAVE_SECCOMP
 #include "seccomp-util.h"
@@ -773,7 +774,7 @@ static int setup_resolv_conf(const char *dest) {
 
         /* We don't really care for the results of this really. If it
          * fails, it fails, but meh... */
-        copy_file("/etc/resolv.conf", where, O_TRUNC|O_NOFOLLOW);
+        copy_file("/etc/resolv.conf", where, O_TRUNC|O_NOFOLLOW, 0644);
 
         return 0;
 }
diff --git a/src/shared/copy.c b/src/shared/copy.c
new file mode 100644 (file)
index 0000000..4dfc2f3
--- /dev/null
@@ -0,0 +1,292 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2014 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "util.h"
+#include "copy.h"
+
+static int stream_bytes(int fdf, int fdt) {
+        assert(fdf >= 0);
+        assert(fdt >= 0);
+
+        for (;;) {
+                char buf[PIPE_BUF];
+                ssize_t n, k;
+
+                n = read(fdf, buf, sizeof(buf));
+                if (n < 0)
+                        return -errno;
+                if (n == 0)
+                        break;
+
+                errno = 0;
+                k = loop_write(fdt, buf, n, false);
+                if (k < 0)
+                        return k;
+                if (k != n)
+                        return errno ? -errno : -EIO;
+        }
+
+        return 0;
+}
+
+static int fd_copy_symlink(int df, const char *from, const struct stat *st, int dt, const char *to) {
+        _cleanup_free_ char *target = NULL;
+        int r;
+
+        assert(from);
+        assert(st);
+        assert(to);
+
+        r = readlinkat_malloc(df, from, &target);
+        if (r < 0)
+                return r;
+
+        if (symlinkat(target, dt, to) < 0) {
+                if (errno == EEXIST)
+                        return 0;
+
+                return -errno;
+        }
+
+        if (fchownat(dt, to, st->st_uid, st->st_gid, AT_SYMLINK_NOFOLLOW) < 0)
+                return -errno;
+
+        return 0;
+}
+
+static int fd_copy_regular(int df, const char *from, const struct stat *st, int dt, const char *to) {
+        _cleanup_close_ int fdf = -1, fdt = -1;
+        int r, q;
+
+        assert(from);
+        assert(st);
+        assert(to);
+
+        fdf = openat(df, from, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW);
+        if (fdf < 0)
+                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;
+
+                return -errno;
+        }
+
+        r = stream_bytes(fdf, fdt);
+        if (r < 0) {
+                unlinkat(dt, to, 0);
+                return r;
+        }
+
+        if (fchown(fdt, st->st_uid, st->st_gid) < 0)
+                r = -errno;
+
+        if (fchmod(fdt, st->st_mode & 07777) < 0)
+                r = -errno;
+
+        q = close(fdt);
+        fdt = -1;
+
+        if (q < 0) {
+                r = -errno;
+                unlinkat(dt, to, 0);
+        }
+
+        return r;
+}
+
+static int fd_copy_fifo(int df, const char *from, const struct stat *st, int dt, const char *to) {
+        int r;
+
+        assert(from);
+        assert(st);
+        assert(to);
+
+        r = mkfifoat(dt, to, st->st_mode & 07777);
+        if (r < 0) {
+                if (errno == EEXIST)
+                        return 0;
+
+                return -errno;
+        }
+
+        if (fchownat(dt, to, st->st_uid, st->st_gid, AT_SYMLINK_NOFOLLOW) < 0)
+                r = -errno;
+
+        if (fchmodat(dt, to, st->st_mode & 07777, 0) < 0)
+                r = -errno;
+
+        return r;
+}
+
+static int fd_copy_node(int df, const char *from, const struct stat *st, int dt, const char *to) {
+        int r;
+
+        assert(from);
+        assert(st);
+        assert(to);
+
+        r = mknodat(dt, to, st->st_mode, st->st_rdev);
+        if (r < 0) {
+                if (errno == EEXIST)
+                        return 0;
+
+                return -errno;
+        }
+
+        if (fchownat(dt, to, st->st_uid, st->st_gid, AT_SYMLINK_NOFOLLOW) < 0)
+                r = -errno;
+
+        if (fchmodat(dt, to, st->st_mode & 07777, 0) < 0)
+                r = -errno;
+
+        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) {
+        _cleanup_close_ int fdf = -1, fdt = -1;
+        _cleanup_closedir_ DIR *d = NULL;
+        struct dirent *de;
+        bool created;
+        int r;
+
+        assert(from);
+        assert(st);
+        assert(to);
+
+        fdf = openat(df, from, O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW);
+        if (fdf < 0)
+                return -errno;
+
+        d = fdopendir(fdf);
+        if (!d)
+                return -errno;
+        fdf = -1;
+
+        r = mkdirat(dt, to, st->st_mode & 07777);
+        if (r >= 0)
+                created = true;
+        else if (errno == EEXIST)
+                created = false;
+        else
+                return -errno;
+
+        fdt = openat(dt, to, O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW);
+        if (fdt < 0)
+                return -errno;
+
+        if (created) {
+                if (fchown(fdt, st->st_uid, st->st_gid) < 0)
+                        r = -errno;
+
+                if (fchmod(fdt, st->st_mode & 07777) < 0)
+                        r = -errno;
+        }
+
+        FOREACH_DIRENT(de, d, return -errno) {
+                struct stat buf;
+                int q;
+
+                if (fstatat(dirfd(d), de->d_name, &buf, AT_SYMLINK_NOFOLLOW) < 0) {
+                        r = -errno;
+                        continue;
+                }
+
+                if (buf.st_dev != original_device)
+                        continue;
+
+                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);
+                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))
+                        q = fd_copy_fifo(dirfd(d), de->d_name, &buf, fdt, de->d_name);
+                else if (S_ISBLK(buf.st_mode) || S_ISCHR(buf.st_mode))
+                        q = fd_copy_node(dirfd(d), de->d_name, &buf, fdt, de->d_name);
+                else
+                        q = -ENOTSUP;
+
+                if (q < 0)
+                        r = q;
+        }
+
+        return r;
+}
+
+int copy_tree(const char *from, const char *to) {
+        struct stat st;
+
+        assert(from);
+        assert(to);
+
+        if (lstat(from, &st) < 0)
+                return -errno;
+
+        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);
+        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))
+                return fd_copy_fifo(AT_FDCWD, from, &st, AT_FDCWD, to);
+        else if (S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode))
+                return fd_copy_node(AT_FDCWD, from, &st, AT_FDCWD, to);
+        else
+                return -ENOTSUP;
+}
+
+int copy_file(const char *from, const char *to, int flags, mode_t mode) {
+        _cleanup_close_ int fdf = -1, fdt = -1;
+        int r;
+
+        assert(from);
+        assert(to);
+
+        fdf = open(from, O_RDONLY|O_CLOEXEC|O_NOCTTY);
+        if (fdf < 0)
+                return -errno;
+
+        fdt = open(to, flags|O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, mode);
+        if (fdt < 0)
+                return -errno;
+
+        r = stream_bytes(fdf, fdt);
+        if (r < 0) {
+                unlink(to);
+                return r;
+        }
+
+        r = close(fdt);
+        fdt = -1;
+
+        if (r < 0) {
+                r = -errno;
+                unlink(to);
+                return r;
+        }
+
+        return 0;
+}
diff --git a/src/shared/copy.h b/src/shared/copy.h
new file mode 100644 (file)
index 0000000..5b56954
--- /dev/null
@@ -0,0 +1,25 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+  This file is part of systemd.
+
+  Copyright 2014 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+int copy_file(const char *from, const char *to, int flags, mode_t mode);
+int copy_tree(const char *from, const char *to);
index 2e832ca3841ecffa4c61d71e938acbae9137f4b7..91cbf2045444cffdc78f7c188382ffefc2ee2bae 100644 (file)
@@ -804,7 +804,7 @@ char *strappend(const char *s, const char *suffix) {
         return strnappend(s, suffix, suffix ? strlen(suffix) : 0);
 }
 
-int readlink_malloc(const char *p, char **ret) {
+int readlinkat_malloc(int fd, const char *p, char **ret) {
         size_t l = 100;
         int r;
 
@@ -819,7 +819,7 @@ int readlink_malloc(const char *p, char **ret) {
                 if (!c)
                         return -ENOMEM;
 
-                n = readlink(p, c, l-1);
+                n = readlinkat(fd, p, c, l-1);
                 if (n < 0) {
                         r = -errno;
                         free(c);
@@ -837,6 +837,10 @@ int readlink_malloc(const char *p, char **ret) {
         }
 }
 
+int readlink_malloc(const char *p, char **ret) {
+        return readlinkat_malloc(AT_FDCWD, p, ret);
+}
+
 int readlink_and_make_absolute(const char *p, char **r) {
         _cleanup_free_ char *target = NULL;
         char *k;
@@ -4128,60 +4132,6 @@ int vt_disallocate(const char *name) {
         return 0;
 }
 
-int copy_file(const char *from, const char *to, int flags) {
-        _cleanup_close_ int fdf = -1;
-        int r, fdt;
-
-        assert(from);
-        assert(to);
-
-        fdf = open(from, O_RDONLY|O_CLOEXEC|O_NOCTTY);
-        if (fdf < 0)
-                return -errno;
-
-        fdt = open(to, flags|O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, 0644);
-        if (fdt < 0)
-                return -errno;
-
-        for (;;) {
-                char buf[PIPE_BUF];
-                ssize_t n, k;
-
-                n = read(fdf, buf, sizeof(buf));
-                if (n < 0) {
-                        r = -errno;
-
-                        close_nointr(fdt);
-                        unlink(to);
-
-                        return r;
-                }
-
-                if (n == 0)
-                        break;
-
-                errno = 0;
-                k = loop_write(fdt, buf, n, false);
-                if (n != k) {
-                        r = k < 0 ? k : (errno ? -errno : -EIO);
-
-                        close_nointr(fdt);
-                        unlink(to);
-
-                        return r;
-                }
-        }
-
-        r = close_nointr(fdt);
-
-        if (r < 0) {
-                unlink(to);
-                return r;
-        }
-
-        return 0;
-}
-
 int symlink_atomic(const char *from, const char *to) {
         char *x;
         _cleanup_free_ char *t;
index 7618aefad5615d4778d65b0b3e6184535805845c..0f8c39335334f8cf458b00def6207bef59fdb5e9 100644 (file)
@@ -252,6 +252,7 @@ char *strnappend(const char *s, const char *suffix, size_t length);
 char *replace_env(const char *format, char **env);
 char **replace_env_argv(char **argv, char **env);
 
+int readlinkat_malloc(int fd, const char *p, char **ret);
 int readlink_malloc(const char *p, char **r);
 int readlink_and_make_absolute(const char *p, char **r);
 int readlink_and_canonicalize(const char *p, char **r);
@@ -521,8 +522,6 @@ int terminal_vhangup(const char *name);
 
 int vt_disallocate(const char *name);
 
-int copy_file(const char *from, const char *to, int flags);
-
 int symlink_atomic(const char *from, const char *to);
 
 int fchmod_umask(int fd, mode_t mode);
index 48dc619d94f4e1717368d1c51b2bbbb82b874079..6745c23a1527ae0b0e3947c012bd919741605288 100644 (file)
@@ -53,6 +53,7 @@
 #include "capability.h"
 #include "specifier.h"
 #include "build.h"
+#include "copy.h"
 
 /* This reads all files listed in /etc/tmpfiles.d/?*.conf and creates
  * them in the file system. This is intended to be used to create
@@ -69,6 +70,7 @@ typedef enum ItemType {
         CREATE_SYMLINK = 'L',
         CREATE_CHAR_DEVICE = 'c',
         CREATE_BLOCK_DEVICE = 'b',
+        COPY_FILES = 'C',
         ADJUST_MODE = 'm',
 
         /* These ones take globs */
@@ -671,6 +673,19 @@ static int create_item(Item *i) {
                         return r;
                 break;
 
+        case COPY_FILES:
+                r = copy_tree(i->argument, i->path);
+                if (r < 0) {
+                        log_error("Failed to copy files: %s", strerror(-r));
+                        return r;
+                }
+
+                r = item_set_perms(i, i->path);
+                if (r < 0)
+                        return r;
+
+                break;
+
         case WRITE_FILE:
                 r = glob_item(i, write_one_file);
                 if (r < 0)
@@ -849,6 +864,7 @@ static int remove_item_instance(Item *i, const char *instance) {
         case RELABEL_PATH:
         case RECURSIVE_RELABEL_PATH:
         case WRITE_FILE:
+        case COPY_FILES:
         case ADJUST_MODE:
                 break;
 
@@ -895,6 +911,7 @@ static int remove_item(Item *i) {
         case RELABEL_PATH:
         case RECURSIVE_RELABEL_PATH:
         case WRITE_FILE:
+        case COPY_FILES:
         case ADJUST_MODE:
                 break;
 
@@ -967,6 +984,7 @@ static int clean_item(Item *i) {
         case CREATE_DIRECTORY:
         case TRUNCATE_DIRECTORY:
         case IGNORE_PATH:
+        case COPY_FILES:
                 clean_item_instance(i, i->path);
                 break;
         case IGNORE_DIRECTORY_PATH:
@@ -1036,7 +1054,8 @@ static bool item_equal(Item *a, Item *b) {
         if ((a->type == CREATE_FILE ||
              a->type == TRUNCATE_FILE ||
              a->type == WRITE_FILE ||
-             a->type == CREATE_SYMLINK) &&
+             a->type == CREATE_SYMLINK ||
+             a->type == COPY_FILES) &&
             !streq_ptr(a->argument, b->argument))
                 return false;
 
@@ -1159,6 +1178,20 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
                 }
                 break;
 
+        case COPY_FILES:
+                if (!i->argument) {
+                        log_error("[%s:%u] Copy files requires argument.", fname, line);
+                        return -EBADMSG;
+                }
+
+                if (!path_is_absolute(i->argument)) {
+                        log_error("[%s:%u] Source path is not absolute.", fname, line);
+                        return -EBADMSG;
+                }
+
+                path_kill_slashes(i->argument);
+                break;
+
         case CREATE_CHAR_DEVICE:
         case CREATE_BLOCK_DEVICE: {
                 unsigned major, minor;