From cda134ab1eac84f874aacf8e885a07112a7fd5ce Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 6 Nov 2014 21:19:20 +0100 Subject: [PATCH] copy: teach copy_bytes() sendfile() support, and then replace sendfile_full() by it --- src/shared/copy.c | 85 +++++++++++++++++++++++++++------------ src/shared/copy.h | 1 + src/shared/fileio.c | 75 +--------------------------------- src/shared/fileio.h | 1 - src/systemctl/systemctl.c | 5 ++- src/test/test-copy.c | 26 ++++++++++++ src/test/test-fileio.c | 25 ------------ 7 files changed, 91 insertions(+), 127 deletions(-) diff --git a/src/shared/copy.c b/src/shared/copy.c index 3744797b9..a863246b2 100644 --- a/src/shared/copy.c +++ b/src/shared/copy.c @@ -19,17 +19,20 @@ along with systemd; If not, see . ***/ +#include + #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; diff --git a/src/shared/copy.h b/src/shared/copy.h index 6b93107fa..62932112a 100644 --- a/src/shared/copy.h +++ b/src/shared/copy.h @@ -24,6 +24,7 @@ #include #include +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); diff --git a/src/shared/fileio.c b/src/shared/fileio.c index 38028b972..f4efc4c9f 100644 --- a/src/shared/fileio.c +++ b/src/shared/fileio.c @@ -20,12 +20,12 @@ ***/ #include -#include -#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; diff --git a/src/shared/fileio.h b/src/shared/fileio.h index c25691579..5ae51c1e2 100644 --- a/src/shared/fileio.h +++ b/src/shared/fileio.h @@ -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); diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index d9e9c2a6c..c903c54e9 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -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; diff --git a/src/test/test-copy.c b/src/test/test-copy.c index 6aa86a03b..d2cad08cb 100644 --- a/src/test/test-copy.c +++ b/src/test/test-copy.c @@ -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; diff --git a/src/test/test-fileio.c b/src/test/test-fileio.c index c26a6facb..cdf1973ea 100644 --- a/src/test/test-fileio.c +++ b/src/test/test-fileio.c @@ -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; -- 2.30.2