X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Fshared%2Fcopy.c;h=1282cb88be7f076b3e390a3131a301da6c7304c0;hp=7c3fab6901020c0db85520dce2224a85829137c2;hb=01c94c5d0aff09b4c0e429d483c8eeba40017071;hpb=2c455af4c765463c75df79acba34d2347c03cbff diff --git a/src/shared/copy.c b/src/shared/copy.c index 7c3fab690..1282cb88b 100644 --- a/src/shared/copy.c +++ b/src/shared/copy.c @@ -19,29 +19,79 @@ along with systemd; If not, see . ***/ +#include +#include + #include "util.h" +#include "btrfs-util.h" #include "copy.h" -int copy_bytes(int fdf, int fdt) { +#define COPY_BUFFER_SIZE (16*1024) + +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; + size_t m = COPY_BUFFER_SIZE; + ssize_t n; - n = read(fdf, buf, sizeof(buf)); - if (n < 0) - return -errno; - if (n == 0) - break; + 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]; - 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; + + 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; @@ -70,6 +120,7 @@ static int fd_copy_symlink(int df, const char *from, const struct stat *st, int 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; + struct timespec ts[2]; int r, q; assert(from); @@ -84,7 +135,7 @@ static int fd_copy_regular(int df, const char *from, const struct stat *st, int 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; @@ -96,6 +147,12 @@ static int fd_copy_regular(int df, const char *from, const struct stat *st, int if (fchmod(fdt, st->st_mode & 07777) < 0) r = -errno; + ts[0] = st->st_atim; + ts[1] = st->st_mtim; + (void) futimens(fdt, ts); + + (void) copy_xattr(fdf, fdt); + q = close(fdt); fdt = -1; @@ -147,20 +204,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, bool merge) { +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) @@ -182,11 +247,19 @@ static int fd_copy_directory(int df, const char *from, const struct stat *st, in r = 0; if (created) { + struct timespec ut[2] = { + st->st_atim, + st->st_mtim + }; + if (fchown(fdt, st->st_uid, st->st_gid) < 0) r = -errno; if (fchmod(fdt, st->st_mode & 07777) < 0) r = -errno; + + (void) futimens(fdt, ut); + (void) copy_xattr(dirfd(d), fdt); } FOREACH_DIRENT(de, d, return -errno) { @@ -212,7 +285,7 @@ static int fd_copy_directory(int df, const char *from, const struct stat *st, in 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; + q = -EOPNOTSUPP; if (q == -EEXIST && merge) q = 0; @@ -224,58 +297,211 @@ 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, bool merge) { +int copy_tree_at(int fdf, const char *from, int fdt, const char *to, bool merge) { struct stat st; assert(from); assert(to); - if (lstat(from, &st) < 0) + if (fstatat(fdf, from, &st, AT_SYMLINK_NOFOLLOW) < 0) return -errno; if (S_ISREG(st.st_mode)) - return fd_copy_regular(AT_FDCWD, from, &st, AT_FDCWD, to); + return fd_copy_regular(fdf, from, &st, fdt, to); else if (S_ISDIR(st.st_mode)) - return fd_copy_directory(AT_FDCWD, from, &st, AT_FDCWD, to, st.st_dev, merge); + return fd_copy_directory(fdf, from, &st, fdt, to, st.st_dev, merge); else if (S_ISLNK(st.st_mode)) - return fd_copy_symlink(AT_FDCWD, from, &st, AT_FDCWD, to); + return fd_copy_symlink(fdf, from, &st, fdt, to); else if (S_ISFIFO(st.st_mode)) - return fd_copy_fifo(AT_FDCWD, from, &st, AT_FDCWD, to); + return fd_copy_fifo(fdf, from, &st, fdt, to); else if (S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode)) - return fd_copy_node(AT_FDCWD, from, &st, AT_FDCWD, to); + return fd_copy_node(fdf, from, &st, fdt, to); else - return -ENOTSUP; + return -EOPNOTSUPP; } -int copy_file(const char *from, const char *to, int flags, mode_t mode) { - _cleanup_close_ int fdf = -1, fdt = -1; +int copy_tree(const char *from, const char *to, bool merge) { + return copy_tree_at(AT_FDCWD, from, AT_FDCWD, to, merge); +} + +int copy_directory_fd(int dirfd, const char *to, bool merge) { + + 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; int r; assert(from); - assert(to); + assert(fdt >= 0); 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 = copy_bytes(fdf, fdt, (off_t) -1, try_reflink); + + (void) copy_times(fdf, fdt); + (void) copy_xattr(fdf, fdt); + + return r; +} + +int copy_file(const char *from, const char *to, int flags, mode_t mode, unsigned chattr_flags) { + int fdt = -1, r; + + assert(from); + assert(to); + + RUN_WITH_UMASK(0000) { + fdt = open(to, flags|O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, mode); + if (fdt < 0) + return -errno; + } - r = copy_bytes(fdf, fdt); + if (chattr_flags != 0) + (void) chattr_fd(fdt, chattr_flags, (unsigned) -1); + + r = copy_file_fd(from, fdt, true); if (r < 0) { + close(fdt); unlink(to); return r; } - r = close(fdt); - fdt = -1; + if (close(fdt) < 0) { + unlink_noerrno(to); + return -errno; + } + + return 0; +} + +int copy_file_atomic(const char *from, const char *to, mode_t mode, bool replace, unsigned chattr_flags) { + _cleanup_free_ char *t = NULL; + int r; + + assert(from); + assert(to); + + r = tempfn_random(to, &t); + if (r < 0) + return r; + r = copy_file(from, t, O_NOFOLLOW|O_EXCL, mode, chattr_flags); + if (r < 0) + return r; + + if (replace) { + r = renameat(AT_FDCWD, t, AT_FDCWD, to); + if (r < 0) + r = -errno; + } else + r = rename_noreplace(AT_FDCWD, t, AT_FDCWD, to); if (r < 0) { - r = -errno; - unlink(to); + (void) unlink_noerrno(t); return r; } return 0; } + +int copy_times(int fdf, int fdt) { + struct timespec ut[2]; + struct stat st; + usec_t crtime = 0; + + assert(fdf >= 0); + assert(fdt >= 0); + + if (fstat(fdf, &st) < 0) + return -errno; + + ut[0] = st.st_atim; + ut[1] = st.st_mtim; + + if (futimens(fdt, ut) < 0) + return -errno; + + if (fd_getcrtime(fdf, &crtime) >= 0) + (void) fd_setcrtime(fdt, crtime); + + return 0; +} + +int copy_xattr(int fdf, int fdt) { + _cleanup_free_ char *bufa = NULL, *bufb = NULL; + size_t sza = 100, szb = 100; + ssize_t n; + int ret = 0; + const char *p; + + for (;;) { + bufa = malloc(sza); + if (!bufa) + return -ENOMEM; + + n = flistxattr(fdf, bufa, sza); + if (n == 0) + return 0; + if (n > 0) + break; + if (errno != ERANGE) + return -errno; + + sza *= 2; + + free(bufa); + bufa = NULL; + } + + p = bufa; + while (n > 0) { + size_t l; + + l = strlen(p); + assert(l < (size_t) n); + + if (startswith(p, "user.")) { + ssize_t m; + + if (!bufb) { + bufb = malloc(szb); + if (!bufb) + return -ENOMEM; + } + + m = fgetxattr(fdf, p, bufb, szb); + if (m < 0) { + if (errno == ERANGE) { + szb *= 2; + free(bufb); + bufb = NULL; + continue; + } + + return -errno; + } + + if (fsetxattr(fdt, p, bufb, m, 0) < 0) + ret = -errno; + } + + p += l + 1; + n -= l + 1; + } + + return ret; +}