[libglnx] fdio: Add glnx_file_copy_at()
- From: Colin Walters <walters src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [libglnx] fdio: Add glnx_file_copy_at()
- Date: Tue, 3 Mar 2015 16:46:12 +0000 (UTC)
commit 162d1f6b58c9b501f47080e99aa1fd36864a89f9
Author: Colin Walters <walters verbum org>
Date: Wed Feb 25 21:28:24 2015 -0500
fdio: Add glnx_file_copy_at()
This will allow deleting some code from OSTree for the config file
merging. We're reusing some code from systemd, which a nice modern
clean codebase, and among other things this gets us BTRFS reflinking
(if available) again.
glnx-fdio.c | 353 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
glnx-fdio.h | 22 ++++
2 files changed, 375 insertions(+), 0 deletions(-)
---
diff --git a/glnx-fdio.c b/glnx-fdio.c
index 67653c2..e0a240d 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 91e6aa6..688eeb2 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
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]