chiark / gitweb /
copy: teach copy_bytes() sendfile() support, and then replace sendfile_full() by it
authorLennart Poettering <lennart@poettering.net>
Thu, 6 Nov 2014 20:19:20 +0000 (21:19 +0100)
committerLennart Poettering <lennart@poettering.net>
Fri, 7 Nov 2014 00:19:56 +0000 (01:19 +0100)
src/shared/copy.c
src/shared/copy.h
src/shared/fileio.c
src/shared/fileio.h
src/systemctl/systemctl.c
src/test/test-copy.c
src/test/test-fileio.c

index 3744797b9502788b8d16335085ec3ca89a7492f5..a863246b2bf47c5ed821f2603f8ddb9f0ea3c38a 100644 (file)
   along with systemd; If not, see <http://www.gnu.org/licenses/>.
 ***/
 
+#include <sys/sendfile.h>
+
 #include "util.h"
 #include "copy.h"
 
 int copy_bytes(int fdf, int fdt, off_t max_bytes) {
+        bool try_sendfile = true;
+
         assert(fdf >= 0);
         assert(fdt >= 0);
 
         for (;;) {
-                char buf[PIPE_BUF];
-                ssize_t n, k;
-                size_t m = sizeof(buf);
+                size_t m = PIPE_BUF;
+                ssize_t n;
 
                 if (max_bytes != (off_t) -1) {
 
@@ -40,19 +43,44 @@ int copy_bytes(int fdf, int fdt, off_t max_bytes) {
                                 m = (size_t) max_bytes;
                 }
 
-                n = read(fdf, buf, m);
-                if (n < 0)
-                        return -errno;
-                if (n == 0)
-                        break;
+                /* 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];
+                        ssize_t k;
 
-                errno = 0;
-                k = loop_write(fdt, buf, n, false);
-                if (k < 0)
-                        return k;
-                if (k != n)
-                        return errno ? -errno : -EIO;
+                        n = read(fdf, buf, m);
+                        if (n < 0)
+                                return -errno;
+                        if (n == 0) /* EOF */
+                                break;
+
+                        errno = 0;
+                        k = loop_write(fdt, buf, n, false);
+                        if (k < 0)
+                                return k;
+                        if (k != n)
+                                return errno ? -errno : -EIO;
+
+                }
 
+        next:
                 if (max_bytes != (off_t) -1) {
                         assert(max_bytes >= n);
                         max_bytes -= n;
@@ -262,34 +290,39 @@ int copy_tree(const char *from, const char *to, bool merge) {
                 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_file_fd(const char *from, int fdt) {
+        _cleanup_close_ int fdf = -1;
 
         assert(from);
-        assert(to);
+        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);
+}
+
+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, (off_t) -1);
+        r = copy_file_fd(from, fdt);
         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;
index 6b93107fabe26e5170dba9183d427d18ec7ef0b6..62932112afe571d904e988b5729846f79c633262 100644 (file)
@@ -24,6 +24,7 @@
 #include <stdbool.h>
 #include <sys/types.h>
 
+int copy_file_fd(const char *from, int to);
 int copy_file(const char *from, const char *to, int flags, mode_t mode);
 int copy_tree(const char *from, const char *to, bool merge);
 int copy_bytes(int fdf, int fdt, off_t max_bytes);
index 38028b972ed2ed0e34ac10139e2bd0e8125fb599..f4efc4c9f1a21e19713da4b6eec05ad07ace5c72 100644 (file)
 ***/
 
 #include <unistd.h>
-#include <sys/sendfile.h>
-#include "fileio.h"
+
 #include "util.h"
 #include "strv.h"
 #include "utf8.h"
 #include "ctype.h"
+#include "fileio.h"
 
 int write_string_stream(FILE *f, const char *line) {
         assert(f);
@@ -144,77 +144,6 @@ int read_one_line_file(const char *fn, char **line) {
         return 0;
 }
 
-ssize_t sendfile_full(int out_fd, const char *fn) {
-        _cleanup_fclose_ FILE *f;
-        struct stat st;
-        int r;
-        ssize_t s;
-
-        size_t n, l;
-        _cleanup_free_ char *buf = NULL;
-
-        assert(out_fd > 0);
-        assert(fn);
-
-        f = fopen(fn, "re");
-        if (!f)
-                return -errno;
-
-        r = fstat(fileno(f), &st);
-        if (r < 0)
-                return -errno;
-
-        s = sendfile(out_fd, fileno(f), NULL, st.st_size);
-        if (s < 0)
-                if (errno == EINVAL || errno == ENOSYS) {
-                        /* continue below */
-                } else
-                        return -errno;
-        else
-                return s;
-
-        /* sendfile() failed, fall back to read/write */
-
-        /* Safety check */
-        if (st.st_size > 4*1024*1024)
-                return -E2BIG;
-
-        n = st.st_size > 0 ? st.st_size : LINE_MAX;
-        l = 0;
-
-        while (true) {
-                char *t;
-                size_t k;
-
-                t = realloc(buf, n);
-                if (!t)
-                        return -ENOMEM;
-
-                buf = t;
-                k = fread(buf + l, 1, n - l, f);
-
-                if (k <= 0) {
-                        if (ferror(f))
-                                return -errno;
-
-                        break;
-                }
-
-                l += k;
-                n *= 2;
-
-                /* Safety check */
-                if (n > 4*1024*1024)
-                        return -E2BIG;
-        }
-
-        r = write(out_fd, buf, l);
-        if (r < 0)
-                return -errno;
-
-        return (ssize_t) l;
-}
-
 int read_full_stream(FILE *f, char **contents, size_t *size) {
         size_t n, l;
         _cleanup_free_ char *buf = NULL;
index c256915799a927334f5e1d9351bf5327707a23ed..5ae51c1e28a9daa56d4fee59f25a11bf2f06922d 100644 (file)
@@ -33,7 +33,6 @@ int write_string_file_atomic(const char *fn, const char *line);
 int read_one_line_file(const char *fn, char **line);
 int read_full_file(const char *fn, char **contents, size_t *size);
 int read_full_stream(FILE *f, char **contents, size_t *size);
-ssize_t sendfile_full(int out_fd, const char *fn);
 
 int parse_env_file(const char *fname, const char *separator, ...) _sentinel_;
 int load_env_file(FILE *f, const char *fname, const char *separator, char ***l);
index d9e9c2a6c32087b1c9b955fe3461f0242b7fa934..c903c54e9b7ae09274595ebb05a5048ed8b5d060 100644 (file)
@@ -67,6 +67,7 @@
 #include "logs-show.h"
 #include "socket-util.h"
 #include "fileio.h"
+#include "copy.h"
 #include "env-util.h"
 #include "bus-util.h"
 #include "bus-message.h"
@@ -4647,7 +4648,7 @@ static int cat(sd_bus *bus, char **args) {
                                ansi_highlight_off());
                         fflush(stdout);
 
-                        r = sendfile_full(STDOUT_FILENO, fragment_path);
+                        r = copy_file_fd(fragment_path, STDOUT_FILENO);
                         if (r < 0) {
                                 log_warning("Failed to cat %s: %s", fragment_path, strerror(-r));
                                 continue;
@@ -4662,7 +4663,7 @@ static int cat(sd_bus *bus, char **args) {
                                ansi_highlight_off());
                         fflush(stdout);
 
-                        r = sendfile_full(STDOUT_FILENO, *path);
+                        r = copy_file_fd(*path, STDOUT_FILENO);
                         if (r < 0) {
                                 log_warning("Failed to cat %s: %s", *path, strerror(-r));
                                 continue;
index 6aa86a03be2e43a5784c5aad04e4b938e26221ee..d2cad08cb69c86b40b2a817d96535874b05eec32 100644 (file)
@@ -48,11 +48,36 @@ static void test_copy_file(void) {
 
         assert_se(read_full_file(fn_copy, &buf, &sz) == 0);
         assert_se(streq(buf, "foo bar bar bar foo\n"));
+        assert_se(sz == 20);
 
         unlink(fn);
         unlink(fn_copy);
 }
 
+static void test_copy_file_fd(void) {
+        char in_fn[] = "/tmp/test-copy-file-fd-XXXXXX";
+        char out_fn[] = "/tmp/test-copy-file-fd-XXXXXX";
+        _cleanup_close_ int in_fd = -1, out_fd = -1;
+        char text[] = "boohoo\nfoo\n\tbar\n";
+        char buf[64] = {0};
+
+        in_fd = mkostemp_safe(in_fn, O_RDWR);
+        assert_se(in_fd >= 0);
+        out_fd = mkostemp_safe(out_fn, O_RDWR);
+        assert_se(out_fd >= 0);
+
+        assert_se(write_string_file(in_fn, text) == 0);
+        assert_se(copy_file_fd("/a/file/which/does/not/exist/i/guess", out_fd) < 0);
+        assert_se(copy_file_fd(in_fn, out_fd) >= 0);
+        assert_se(lseek(out_fd, SEEK_SET, 0) == 0);
+
+        assert_se(read(out_fd, buf, sizeof(buf)) == sizeof(text) - 1);
+        assert_se(streq(buf, text));
+
+        unlink(in_fn);
+        unlink(out_fn);
+}
+
 static void test_copy_tree(void) {
         char original_dir[] = "/tmp/test-copy_tree/";
         char copy_dir[] = "/tmp/test-copy_tree-copy/";
@@ -109,6 +134,7 @@ static void test_copy_tree(void) {
 
 int main(int argc, char *argv[]) {
         test_copy_file();
+        test_copy_file_fd();
         test_copy_tree();
 
         return 0;
index c26a6facb43e4aa7509b853424db2ef46c973705..cdf1973ea597826f6789db563e72b0cd83463c43 100644 (file)
@@ -348,30 +348,6 @@ static void test_write_string_file_no_create(void) {
         unlink(fn);
 }
 
-static void test_sendfile_full(void) {
-        char in_fn[] = "/tmp/test-sendfile_full-XXXXXX";
-        char out_fn[] = "/tmp/test-sendfile_full-XXXXXX";
-        _cleanup_close_ int in_fd, out_fd;
-        char text[] = "boohoo\nfoo\n\tbar\n";
-        char buf[64] = {0};
-
-        in_fd = mkostemp_safe(in_fn, O_RDWR);
-        assert_se(in_fd >= 0);
-        out_fd = mkostemp_safe(out_fn, O_RDWR);
-        assert_se(out_fd >= 0);
-
-        assert_se(write_string_file(in_fn, text) == 0);
-        assert_se(sendfile_full(out_fd, "/a/file/which/does/not/exist/i/guess") < 0);
-        assert_se(sendfile_full(out_fd, in_fn) == sizeof(text) - 1);
-        assert_se(lseek(out_fd, SEEK_SET, 0) == 0);
-
-        assert_se(read(out_fd, buf, sizeof(buf)) == sizeof(text) - 1);
-        assert_se(streq(buf, text));
-
-        unlink(in_fn);
-        unlink(out_fn);
-}
-
 static void test_load_env_file_pairs(void) {
         char fn[] = "/tmp/test-load_env_file_pairs-XXXXXX";
         int fd;
@@ -428,7 +404,6 @@ int main(int argc, char *argv[]) {
         test_write_string_stream();
         test_write_string_file();
         test_write_string_file_no_create();
-        test_sendfile_full();
         test_load_env_file_pairs();
 
         return 0;