[libglnx] fdio: Add glnx_file_copy_at()



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]