chiark / gitweb /
shared: add new btrfs-util.[ch] helpers for doing common btrfs operation
[elogind.git] / src / shared / copy.c
index 073b7279b43ac3b96e95a2d71b3fdd2eb2216b54..f22a9409918a84c0eb3fb28f94db26077b844615 100644 (file)
   along with systemd; If not, see <http://www.gnu.org/licenses/>.
 ***/
 
   along with systemd; If not, see <http://www.gnu.org/licenses/>.
 ***/
 
+#include <sys/sendfile.h>
+
 #include "util.h"
 #include "util.h"
+#include "btrfs-util.h"
 #include "copy.h"
 
 #include "copy.h"
 
-int copy_bytes(int fdf, int fdt) {
+int copy_bytes(int fdf, int fdt, off_t max_bytes) {
+        bool try_sendfile = true;
+
         assert(fdf >= 0);
         assert(fdt >= 0);
 
         for (;;) {
         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;
+                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];
+                        int r;
+
+                        n = read(fdf, buf, m);
+                        if (n < 0)
+                                return -errno;
+                        if (n == 0) /* EOF */
+                                break;
+
+                        r = loop_write(fdt, buf, n, false);
+                        if (r < 0)
+                                return r;
+
+                }
+
+        next:
+                if (max_bytes != (off_t) -1) {
+                        assert(max_bytes >= n);
+                        max_bytes -= n;
+                }
         }
 
         return 0;
         }
 
         return 0;
@@ -59,12 +100,8 @@ static int fd_copy_symlink(int df, const char *from, const struct stat *st, int
         if (r < 0)
                 return r;
 
         if (r < 0)
                 return r;
 
-        if (symlinkat(target, dt, to) < 0) {
-                if (errno == EEXIST)
-                        return 0;
-
+        if (symlinkat(target, dt, to) < 0)
                 return -errno;
                 return -errno;
-        }
 
         if (fchownat(dt, to, st->st_uid, st->st_gid, AT_SYMLINK_NOFOLLOW) < 0)
                 return -errno;
 
         if (fchownat(dt, to, st->st_uid, st->st_gid, AT_SYMLINK_NOFOLLOW) < 0)
                 return -errno;
@@ -85,14 +122,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);
                 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;
                 return -errno;
-        }
 
 
-        r = copy_bytes(fdf, fdt);
+        r = copy_bytes(fdf, fdt, (off_t) -1);
         if (r < 0) {
                 unlinkat(dt, to, 0);
                 return r;
         if (r < 0) {
                 unlinkat(dt, to, 0);
                 return r;
@@ -123,12 +156,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);
         assert(to);
 
         r = mkfifoat(dt, to, st->st_mode & 07777);
-        if (r < 0) {
-                if (errno == EEXIST)
-                        return 0;
-
+        if (r < 0)
                 return -errno;
                 return -errno;
-        }
 
         if (fchownat(dt, to, st->st_uid, st->st_gid, AT_SYMLINK_NOFOLLOW) < 0)
                 r = -errno;
 
         if (fchownat(dt, to, st->st_uid, st->st_gid, AT_SYMLINK_NOFOLLOW) < 0)
                 r = -errno;
@@ -147,12 +176,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);
         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;
                 return -errno;
-        }
 
         if (fchownat(dt, to, st->st_uid, st->st_gid, AT_SYMLINK_NOFOLLOW) < 0)
                 r = -errno;
 
         if (fchownat(dt, to, st->st_uid, st->st_gid, AT_SYMLINK_NOFOLLOW) < 0)
                 r = -errno;
@@ -163,20 +188,28 @@ static int fd_copy_node(int df, const char *from, const struct stat *st, int dt,
         return r;
 }
 
         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;
 
         _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);
 
         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)
 
         d = fdopendir(fdf);
         if (!d)
@@ -186,7 +219,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;
         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;
                 created = false;
         else
                 return -errno;
@@ -195,6 +228,8 @@ static int fd_copy_directory(int df, const char *from, const struct stat *st, in
         if (fdt < 0)
                 return -errno;
 
         if (fdt < 0)
                 return -errno;
 
+        r = 0;
+
         if (created) {
                 if (fchown(fdt, st->st_uid, st->st_gid) < 0)
                         r = -errno;
         if (created) {
                 if (fchown(fdt, st->st_uid, st->st_gid) < 0)
                         r = -errno;
@@ -203,7 +238,6 @@ static int fd_copy_directory(int df, const char *from, const struct stat *st, in
                         r = -errno;
         }
 
                         r = -errno;
         }
 
-        r = 0;
         FOREACH_DIRENT(de, d, return -errno) {
                 struct stat buf;
                 int q;
         FOREACH_DIRENT(de, d, return -errno) {
                 struct stat buf;
                 int q;
@@ -219,7 +253,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))
                 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))
                 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 +263,9 @@ static int fd_copy_directory(int df, const char *from, const struct stat *st, in
                 else
                         q = -ENOTSUP;
 
                 else
                         q = -ENOTSUP;
 
+                if (q == -EEXIST && merge)
+                        q = 0;
+
                 if (q < 0)
                         r = q;
         }
                 if (q < 0)
                         r = q;
         }
@@ -236,7 +273,7 @@ static int fd_copy_directory(int df, const char *from, const struct stat *st, in
         return r;
 }
 
         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);
         struct stat st;
 
         assert(from);
@@ -248,7 +285,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))
         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))
         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))
@@ -259,34 +296,55 @@ int copy_tree(const char *from, const char *to) {
                 return -ENOTSUP;
 }
 
                 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);
 
         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) {
+        _cleanup_close_ int fdf = -1;
+
+        assert(from);
+        assert(fdt >= 0);
+
         fdf = open(from, O_RDONLY|O_CLOEXEC|O_NOCTTY);
         if (fdf < 0)
                 return -errno;
 
         fdf = open(from, O_RDONLY|O_CLOEXEC|O_NOCTTY);
         if (fdf < 0)
                 return -errno;
 
+        return copy_bytes(fdf, fdt, (off_t) -1);
+}
+
+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;
 
         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);
         if (r < 0) {
         if (r < 0) {
+                close(fdt);
                 unlink(to);
                 return r;
         }
 
                 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;
         }
 
         return 0;