chiark / gitweb /
systemctl: refuse to edit runtime dropins when they already exist in /etc
[elogind.git] / src / shared / copy.c
index 4c227c8bee387a7a8a58b1e1c059c30934a91fa9..b4a85c7bff0a12f76835a7cce3f867be37951b30 100644 (file)
   along with systemd; If not, see <http://www.gnu.org/licenses/>.
 ***/
 
+#include <sys/sendfile.h>
+
 #include "util.h"
+#include "btrfs-util.h"
 #include "copy.h"
 
-int copy_bytes(int fdf, int fdt) {
+int copy_bytes(int fdf, int fdt, off_t max_bytes, bool try_reflink) {
+        bool try_sendfile = true;
+        int r;
+
         assert(fdf >= 0);
         assert(fdt >= 0);
 
+        /* Try btrfs reflinks first. */
+        if (try_reflink && max_bytes == (off_t) -1) {
+                r = btrfs_reflink(fdf, fdt);
+                if (r >= 0)
+                        return r;
+        }
+
         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;
+                size_t m = PIPE_BUF;
+                ssize_t n;
+
+                if (max_bytes != (off_t) -1) {
+
+                        if (max_bytes <= 0)
+                                return -EFBIG;
+
+                        if ((off_t) m > max_bytes)
+                                m = (size_t) max_bytes;
+                }
+
+                /* First try sendfile(), unless we already tried */
+                if (try_sendfile) {
+
+                        n = sendfile(fdt, fdf, NULL, m);
+                        if (n < 0) {
+                                if (errno != EINVAL && errno != ENOSYS)
+                                        return -errno;
+
+                                try_sendfile = false;
+                                /* use fallback below */
+                        } else if (n == 0) /* EOF */
+                                break;
+                        else if (n > 0)
+                                /* Succcess! */
+                                goto next;
+                }
+
+                /* As a fallback just copy bits by hand */
+                {
+                        char buf[m];
+
+                        n = read(fdf, buf, m);
+                        if (n < 0)
+                                return -errno;
+                        if (n == 0) /* EOF */
+                                break;
+
+                        r = loop_write(fdt, buf, (size_t) n, false);
+                        if (r < 0)
+                                return r;
+                }
+
+        next:
+                if (max_bytes != (off_t) -1) {
+                        assert(max_bytes >= n);
+                        max_bytes -= n;
+                }
         }
 
         return 0;
@@ -59,12 +106,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,14 +128,10 @@ 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);
+        r = copy_bytes(fdf, fdt, (off_t) -1, true);
         if (r < 0) {
                 unlinkat(dt, to, 0);
                 return r;
@@ -123,12 +162,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 +182,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,20 +194,28 @@ 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;
         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;
+        if (from)
+                fdf = openat(df, from, O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW);
+        else
+                fdf = fcntl(df, F_DUPFD_CLOEXEC, 3);
 
         d = fdopendir(fdf);
         if (!d)
@@ -186,7 +225,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;
@@ -195,6 +234,8 @@ static int fd_copy_directory(int df, const char *from, const struct stat *st, in
         if (fdt < 0)
                 return -errno;
 
+        r = 0;
+
         if (created) {
                 if (fchown(fdt, st->st_uid, st->st_gid) < 0)
                         r = -errno;
@@ -218,7 +259,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))
@@ -228,6 +269,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;
         }
@@ -235,7 +279,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);
@@ -247,7 +291,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))
@@ -258,34 +302,55 @@ int copy_tree(const char *from, const char *to) {
                 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;
+int copy_tree_fd(int dirfd, const char *to, bool merge) {
 
-        assert(from);
+        struct stat st;
+
+        assert(dirfd >= 0);
         assert(to);
 
+        if (fstat(dirfd, &st) < 0)
+                return -errno;
+
+        if (!S_ISDIR(st.st_mode))
+                return -ENOTDIR;
+
+        return fd_copy_directory(dirfd, NULL, &st, AT_FDCWD, to, st.st_dev, merge);
+}
+
+int copy_file_fd(const char *from, int fdt, bool try_reflink) {
+        _cleanup_close_ int fdf = -1;
+
+        assert(from);
+        assert(fdt >= 0);
+
         fdf = open(from, O_RDONLY|O_CLOEXEC|O_NOCTTY);
         if (fdf < 0)
                 return -errno;
 
+        return copy_bytes(fdf, fdt, (off_t) -1, try_reflink);
+}
+
+int copy_file(const char *from, const char *to, int flags, mode_t mode) {
+        int fdt, r;
+
+        assert(from);
+        assert(to);
+
         fdt = open(to, flags|O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, mode);
         if (fdt < 0)
                 return -errno;
 
-        r = copy_bytes(fdf, fdt);
+        r = copy_file_fd(from, fdt, true);
         if (r < 0) {
+                close(fdt);
                 unlink(to);
                 return r;
         }
 
-        r = close(fdt);
-        fdt = -1;
-
-        if (r < 0) {
-                r = -errno;
-                unlink(to);
-                return r;
+        if (close(fdt) < 0) {
+                unlink_noerrno(to);
+                return -errno;
         }
 
         return 0;