diff --git a/glnx-fdio.c b/glnx-fdio.c index 67653c26e9dca8d1bfa1c592deaf54f2cb116973..e0a240d54d027224713bd0f0c057b91bd76468c9 100644 --- a/glnx-fdio.c +++ b/glnx-fdio.c @@ -2,6 +2,9 @@ * * Copyright (C) 2014,2015 Colin Walters <walters@verbum.org>. * + * Portions derived from systemd: + * Copyright 2010 Lennart Poettering + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either @@ -22,9 +25,19 @@ #include <string.h> #include <stdio.h> +#include <stdint.h> +#include <stdbool.h> +#include <sys/ioctl.h> +#include <sys/sendfile.h> +#include <errno.h> +/* See linux.git/fs/btrfs/ioctl.h */ +#define BTRFS_IOCTL_MAGIC 0x94 +#define BTRFS_IOC_CLONE _IOW(BTRFS_IOCTL_MAGIC, 9, int) #include <glnx-fdio.h> #include <glnx-errors.h> +#include <glnx-xattrs.h> +#include <glnx-backport-autoptr.h> #include <glnx-local-alloc.h> static guint8* @@ -223,3 +236,343 @@ glnx_file_get_contents_utf8_at (int dfd, g_free (buf); return NULL; } + +/** + * glnx_readlinkat_malloc: + * @dfd: Directory file descriptor + * @subpath: Subpath + * @cancellable: Cancellable + * @error: Error + * + * Read the value of a symlink into a dynamically + * allocated buffer. + */ +char * +glnx_readlinkat_malloc (int dfd, + const char *subpath, + GCancellable *cancellable, + GError **error) +{ + size_t l = 100; + + for (;;) + { + char *c; + ssize_t n; + + c = g_malloc (l); + n = TEMP_FAILURE_RETRY (readlinkat (dfd, subpath, c, l-1)); + if (n < 0) + { + glnx_set_error_from_errno (error); + g_free (c); + return FALSE; + } + + if ((size_t) n < l-1) + { + c[n] = 0; + return c; + } + + g_free (c); + l *= 2; + } + + g_assert_not_reached (); +} + +static gboolean +copy_symlink_at (int src_dfd, + const char *src_subpath, + const struct stat *src_stbuf, + int dest_dfd, + const char *dest_subpath, + GLnxFileCopyFlags copyflags, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + g_autofree char *buf = NULL; + + buf = glnx_readlinkat_malloc (src_dfd, src_subpath, cancellable, error); + if (!buf) + goto out; + + if (TEMP_FAILURE_RETRY (symlinkat (buf, dest_dfd, dest_subpath)) != 0) + { + glnx_set_error_from_errno (error); + goto out; + } + + if (!(copyflags & GLNX_FILE_COPY_NOXATTRS)) + { + g_autoptr(GVariant) xattrs = NULL; + + if (!glnx_dfd_name_get_all_xattrs (src_dfd, src_subpath, &xattrs, + cancellable, error)) + goto out; + + if (!glnx_dfd_name_set_all_xattrs (dest_dfd, dest_subpath, xattrs, + cancellable, error)) + goto out; + } + + if (TEMP_FAILURE_RETRY (fchownat (dest_dfd, dest_subpath, + src_stbuf->st_uid, src_stbuf->st_gid, + AT_SYMLINK_NOFOLLOW)) != 0) + { + glnx_set_error_from_errno (error); + goto out; + } + + ret = TRUE; + out: + return ret; +} + +#define COPY_BUFFER_SIZE (16*1024) + +/* From systemd */ + +static int btrfs_reflink(int infd, int outfd) { + int r; + + g_return_val_if_fail(infd >= 0, -1); + g_return_val_if_fail(outfd >= 0, -1); + + r = ioctl(outfd, BTRFS_IOC_CLONE, infd); + if (r < 0) + return -errno; + + return 0; +} + +static int loop_write(int fd, const void *buf, size_t nbytes) { + const uint8_t *p = buf; + + g_return_val_if_fail(fd >= 0, -1); + g_return_val_if_fail(buf, -1); + + errno = 0; + + while (nbytes > 0) { + ssize_t k; + + k = write(fd, p, nbytes); + if (k < 0) { + if (errno == EINTR) + continue; + + return -errno; + } + + if (k == 0) /* Can't really happen */ + return -EIO; + + p += k; + nbytes -= k; + } + + return 0; +} + +static int copy_bytes(int fdf, int fdt, off_t max_bytes, bool try_reflink) { + bool try_sendfile = true; + int r; + + g_return_val_if_fail (fdf >= 0, -1); + g_return_val_if_fail (fdt >= 0, -1); + + /* Try btrfs reflinks first. */ + if (try_reflink && max_bytes == (off_t) -1) { + r = btrfs_reflink(fdf, fdt); + if (r >= 0) + return r; + } + + for (;;) { + size_t m = COPY_BUFFER_SIZE; + 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); + if (r < 0) + return r; + } + + next: + if (max_bytes != (off_t) -1) { + g_assert(max_bytes >= n); + max_bytes -= n; + } + } + + return 0; +} + +/** + * glnx_file_copy_at: + * @src_dfd: Source directory fd + * @src_subpath: Subpath relative to @src_dfd + * @dest_dfd: Target directory fd + * @dest_subpath: Destination name + * @copyflags: Flags + * @cancellable: cancellable + * @error: Error + * + * Perform a full copy of the regular file or + * symbolic link from @src_subpath to @dest_subpath. + * + * If @src_subpath is anything other than a regular + * file or symbolic link, an error will be returned. + */ +gboolean +glnx_file_copy_at (int src_dfd, + const char *src_subpath, + struct stat *src_stbuf, + int dest_dfd, + const char *dest_subpath, + GLnxFileCopyFlags copyflags, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + int r; + int dest_open_flags; + struct timespec ts[2]; + glnx_fd_close int src_fd = -1; + glnx_fd_close int dest_fd = -1; + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + goto out; + + if (S_ISLNK (src_stbuf->st_mode)) + { + return copy_symlink_at (src_dfd, src_subpath, src_stbuf, + dest_dfd, dest_subpath, + copyflags, + cancellable, error); + } + else if (!S_ISREG (src_stbuf->st_mode)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + "Cannot copy non-regular/non-symlink file: %s", src_subpath); + goto out; + } + + src_fd = TEMP_FAILURE_RETRY (openat (src_dfd, src_subpath, O_RDONLY | O_CLOEXEC | O_NOCTTY | O_NOFOLLOW)); + if (src_fd == -1) + { + glnx_set_error_from_errno (error); + goto out; + } + + dest_open_flags = O_WRONLY | O_CREAT | O_CLOEXEC | O_NOCTTY; + if (!(copyflags & GLNX_FILE_COPY_OVERWRITE)) + dest_open_flags |= O_EXCL; + else + dest_open_flags |= O_TRUNC; + + dest_fd = TEMP_FAILURE_RETRY (openat (dest_dfd, dest_subpath, dest_open_flags)); + if (dest_fd == -1) + { + glnx_set_error_from_errno (error); + goto out; + } + + r = copy_bytes (src_fd, dest_fd, (off_t) -1, TRUE); + if (r < 0) + { + errno = -r; + glnx_set_error_from_errno (error); + goto out; + } + + if (fchown (dest_fd, src_stbuf->st_uid, src_stbuf->st_gid) != 0) + { + glnx_set_error_from_errno (error); + goto out; + } + + if (fchmod (dest_fd, src_stbuf->st_mode & 07777) != 0) + { + glnx_set_error_from_errno (error); + goto out; + } + + ts[0] = src_stbuf->st_atim; + ts[1] = src_stbuf->st_mtim; + (void) futimens (dest_fd, ts); + + if (!(copyflags & GLNX_FILE_COPY_NOXATTRS)) + { + g_autoptr(GVariant) xattrs = NULL; + + if (!glnx_fd_get_all_xattrs (src_fd, &xattrs, + cancellable, error)) + goto out; + + if (!glnx_fd_set_all_xattrs (dest_fd, xattrs, + cancellable, error)) + goto out; + } + + if (copyflags & GLNX_FILE_COPY_DATASYNC) + { + if (fdatasync (dest_fd) < 0) + { + glnx_set_error_from_errno (error); + goto out; + } + } + + r = close (dest_fd); + dest_fd = -1; + if (r < 0) + { + glnx_set_error_from_errno (error); + goto out; + } + + ret = TRUE; + out: + if (!ret) + (void) unlinkat (dest_dfd, dest_subpath, 0); + return ret; +} diff --git a/glnx-fdio.h b/glnx-fdio.h index 91e6aa602f04fc67bfc7539b9bca02c0b05f5167..688eeb252747218ee39c355f4c1661de7b556450 100644 --- a/glnx-fdio.h +++ b/glnx-fdio.h @@ -47,4 +47,26 @@ glnx_file_get_contents_utf8_at (int dfd, GCancellable *cancellable, GError **error); +char * +glnx_readlinkat_malloc (int dfd, + const char *subpath, + GCancellable *cancellable, + GError **error); + +typedef enum { + GLNX_FILE_COPY_OVERWRITE, + GLNX_FILE_COPY_NOXATTRS, + GLNX_FILE_COPY_DATASYNC +} GLnxFileCopyFlags; + +gboolean +glnx_file_copy_at (int src_dfd, + const char *src_subpath, + struct stat *src_stbuf, + int dest_dfd, + const char *dest_subpath, + GLnxFileCopyFlags copyflags, + GCancellable *cancellable, + GError **error); + G_END_DECLS