[libgsystem] Import xattr code from OSTree, use it to make gs_shutil_cp_a() copy xattrs



commit bfe765fc00337e3e3e276551e98a55e36c266f80
Author: Colin Walters <walters verbum org>
Date:   Wed Nov 20 17:43:09 2013 -0500

    Import xattr code from OSTree, use it to make gs_shutil_cp_a() copy xattrs
    
    For SELinux, it's crucial that we actually copy the "security.selinux"
    xattr which provides the security context.  the "cp_a" name kind of
    strongly implies that we do what coreutils "cp -a" does, and this patch
    moves us a lot closer to what it says on the tin.
    
    Concretely, we now match directory modes (and ownership), and we copy
    all xattrs for directories.
    
    We're not (yet) copying xattrs for files, but sadly this is a GLib bug.
    
    This patch will allow OSTree to simply use gs_shutil_cp_a() for merging
    configuration.
    
    Still TODO:
    
    * Timestamps
    
    https://bugzilla.gnome.org/show_bug.cgi?id=711058

 gsystem-file-utils.c |  279 ++++++++++++++++++++++++++++++++++++++++++++++++++
 gsystem-file-utils.h |   15 +++
 gsystem-shutil.c     |  221 +++++++++++++++++++++++----------------
 3 files changed, 425 insertions(+), 90 deletions(-)
---
diff --git a/gsystem-file-utils.c b/gsystem-file-utils.c
index 96b317a..639caca 100644
--- a/gsystem-file-utils.c
+++ b/gsystem-file-utils.c
@@ -37,6 +37,9 @@
 #include <glib-unix.h>
 #include <limits.h>
 #include <dirent.h>
+#ifdef GSYSTEM_CONFIG_XATTRS
+#include <attr/xattr.h>
+#endif
 
 static int
 close_nointr (int fd)
@@ -1339,3 +1342,279 @@ gs_file_realpath (GFile *file)
   g_free (path);
   return g_file_new_for_path (path_real);
 }
+
+#ifdef GSYSTEM_CONFIG_XATTRS
+static char *
+canonicalize_xattrs (char    *xattr_string,
+                     size_t   len)
+{
+  char *p;
+  GSList *xattrs = NULL;
+  GSList *iter;
+  GString *result;
+
+  result = g_string_new (0);
+
+  p = xattr_string;
+  while (p < xattr_string+len)
+    {
+      xattrs = g_slist_prepend (xattrs, p);
+      p += strlen (p) + 1;
+    }
+
+  xattrs = g_slist_sort (xattrs, (GCompareFunc) strcmp);
+  for (iter = xattrs; iter; iter = iter->next) {
+    g_string_append (result, iter->data);
+    g_string_append_c (result, '\0');
+  }
+
+  g_slist_free (xattrs);
+  return g_string_free (result, FALSE);
+}
+
+static GVariant *
+variant_new_ay_bytes (GBytes *bytes)
+{
+  gsize size;
+  gconstpointer data;
+  data = g_bytes_get_data (bytes, &size);
+  g_bytes_ref (bytes);
+  return g_variant_new_from_data (G_VARIANT_TYPE ("ay"), data, size,
+                                  TRUE, (GDestroyNotify)g_bytes_unref, bytes);
+}
+
+static gboolean
+read_xattr_name_array (const char *path,
+                       const char *xattrs,
+                       size_t      len,
+                       GVariantBuilder *builder,
+                       GError  **error)
+{
+  gboolean ret = FALSE;
+  const char *p;
+
+  p = xattrs;
+  while (p < xattrs+len)
+    {
+      ssize_t bytes_read;
+      char *buf;
+      GBytes *bytes = NULL;
+
+      bytes_read = lgetxattr (path, p, NULL, 0);
+      if (bytes_read < 0)
+        {
+          _set_error_from_errno (error);
+          g_prefix_error (error, "lgetxattr (%s, %s) failed: ", path, p);
+          goto out;
+        }
+      if (bytes_read == 0)
+        continue;
+
+      buf = g_malloc (bytes_read);
+      bytes = g_bytes_new_take (buf, bytes_read);
+      if (lgetxattr (path, p, buf, bytes_read) < 0)
+        {
+          g_bytes_unref (bytes);
+          _set_error_from_errno (error);
+          g_prefix_error (error, "lgetxattr (%s, %s) failed: ", path, p);
+          goto out;
+        }
+      
+      g_variant_builder_add (builder, "(@ay ay)",
+                             g_variant_new_bytestring (p),
+                             variant_new_ay_bytes (bytes));
+
+      p = p + strlen (p) + 1;
+      g_bytes_unref (bytes);
+    }
+  
+  ret = TRUE;
+ out:
+  return ret;
+}
+
+#endif
+
+/**
+ * gs_file_get_all_xattrs:
+ * @f: a #GFile
+ * @out_xattrs: (out): A new #GVariant containing the extended attributes
+ * @cancellable: Cancellable
+ * @error: Error
+ *
+ * Read all extended attributes of @f in a canonical sorted order, and
+ * set @out_xattrs with the result.
+ *
+ * If the filesystem does not support extended attributes, @out_xattrs
+ * will have 0 elements, and this function will return successfully.
+ */
+gboolean
+gs_file_get_all_xattrs (GFile         *f,
+                        GVariant     **out_xattrs,
+                        GCancellable  *cancellable,
+                        GError       **error)
+{
+  gboolean ret = FALSE;
+  const char *path;
+  ssize_t bytes_read;
+  GVariant *ret_xattrs = NULL;
+  char *xattr_names = NULL;
+  char *xattr_names_canonical = NULL;
+  GVariantBuilder builder;
+  gboolean builder_initialized = FALSE;
+
+  path = gs_file_get_path_cached (f);
+
+  g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ayay)"));
+  builder_initialized = TRUE;
+
+#ifdef GSYSTEM_CONFIG_XATTRS
+  bytes_read = llistxattr (path, NULL, 0);
+
+  if (bytes_read < 0)
+    {
+      if (errno != ENOTSUP)
+        {
+          _set_error_from_errno (error);
+          g_prefix_error (error, "llistxattr (%s) failed: ", path);
+          goto out;
+        }
+    }
+  else if (bytes_read > 0)
+    {
+      xattr_names = g_malloc (bytes_read);
+      if (llistxattr (path, xattr_names, bytes_read) < 0)
+        {
+          _set_error_from_errno (error);
+          g_prefix_error (error, "llistxattr (%s) failed: ", path);
+          goto out;
+        }
+      xattr_names_canonical = canonicalize_xattrs (xattr_names, bytes_read);
+      
+      if (!read_xattr_name_array (path, xattr_names_canonical, bytes_read, &builder, error))
+        goto out;
+    }
+
+#endif
+
+  ret_xattrs = g_variant_builder_end (&builder);
+  g_variant_ref_sink (ret_xattrs);
+  
+  ret = TRUE;
+  gs_transfer_out_value (out_xattrs, &ret_xattrs);
+ out:
+  g_clear_pointer (&ret_xattrs, g_variant_unref);
+  g_clear_pointer (&xattr_names, g_free);
+  g_clear_pointer (&xattr_names_canonical, g_free);
+  if (!builder_initialized)
+    g_variant_builder_clear (&builder);
+  return ret;
+}
+
+/**
+ * gs_fd_set_all_xattrs:
+ * @fd: File descriptor
+ * @xattrs: Extended attributes
+ * @cancellable: Cancellable
+ * @error: Error
+ *
+ * For each attribute in @xattrs, set its value on the file or
+ * directory referred to by @fd.  This function does not remove any
+ * attributes not in @xattrs.
+ */
+gboolean
+gs_fd_set_all_xattrs (int            fd,
+                      GVariant      *xattrs,
+                      GCancellable  *cancellable,
+                      GError       **error)
+{
+#ifdef GSYSTEM_CONFIG_XATTRS
+  gboolean ret = FALSE;
+  int i, n;
+
+  n = g_variant_n_children (xattrs);
+  for (i = 0; i < n; i++)
+    {
+      const guint8* name;
+      const guint8* value_data;
+      GVariant *value = NULL;
+      gsize value_len;
+      int res;
+
+      g_variant_get_child (xattrs, i, "(^&ay ay)",
+                           &name, &value);
+      value_data = g_variant_get_fixed_array (value, &value_len, 1);
+      
+      do
+        res = fsetxattr (fd, (char*)name, (char*)value_data, value_len, 0);
+      while (G_UNLIKELY (res == -1 && errno == EINTR));
+      g_variant_unref (value);
+      if (G_UNLIKELY (res == -1))
+        {
+          _set_error_from_errno (error);
+          goto out;
+        }
+    }
+
+  ret = TRUE;
+ out:
+  return ret;
+#else
+  return TRUE;
+#endif
+}
+
+/**
+ * gs_file_set_all_xattrs:
+ * @file: File descriptor
+ * @xattrs: Extended attributes
+ * @cancellable: Cancellable
+ * @error: Error
+ *
+ * For each attribute in @xattrs, set its value on the file or
+ * directory referred to by @file.  This function does not remove any
+ * attributes not in @xattrs.
+ */
+gboolean
+gs_file_set_all_xattrs (GFile         *file,
+                        GVariant      *xattrs,
+                        GCancellable  *cancellable,
+                        GError       **error)
+{
+#ifdef GSYSTEM_CONFIG_XATTRS
+  gboolean ret = FALSE;
+  const char *path;
+  int i, n;
+
+  path = gs_file_get_path_cached (file);
+
+  n = g_variant_n_children (xattrs);
+  for (i = 0; i < n; i++)
+    {
+      const guint8* name;
+      GVariant *value;
+      const guint8* value_data;
+      gsize value_len;
+      gboolean loop_err;
+
+      g_variant_get_child (xattrs, i, "(^&ay ay)",
+                           &name, &value);
+      value_data = g_variant_get_fixed_array (value, &value_len, 1);
+      
+      loop_err = lsetxattr (path, (char*)name, (char*)value_data, value_len, 0) < 0;
+      g_clear_pointer (&value, (GDestroyNotify) g_variant_unref);
+      if (loop_err)
+        {
+          _set_error_from_errno (error);
+          g_prefix_error (error, "lsetxattr (%s, %s) failed: ", path, name);
+          goto out;
+        }
+    }
+
+  ret = TRUE;
+ out:
+  return ret;
+#else
+  return TRUE;
+#endif
+}
diff --git a/gsystem-file-utils.h b/gsystem-file-utils.h
index c364052..021aebb 100644
--- a/gsystem-file-utils.h
+++ b/gsystem-file-utils.h
@@ -152,6 +152,21 @@ gchar *gs_file_get_relpath (GFile *one,
 
 GFile * gs_file_realpath (GFile *file);
 
+gboolean gs_file_get_all_xattrs (GFile         *f,
+                                 GVariant     **out_xattrs,
+                                 GCancellable  *cancellable,
+                                 GError       **error);
+
+gboolean gs_fd_set_all_xattrs (int            fd,
+                               GVariant      *xattrs,
+                               GCancellable  *cancellable,
+                               GError       **error);
+
+gboolean gs_file_set_all_xattrs (GFile         *file,
+                                 GVariant      *xattrs,
+                                 GCancellable  *cancellable,
+                                 GError       **error);
+
 G_END_DECLS
 
 #endif
diff --git a/gsystem-shutil.c b/gsystem-shutil.c
index 8416033..095bedd 100644
--- a/gsystem-shutil.c
+++ b/gsystem-shutil.c
@@ -40,107 +40,57 @@ union dirent_storage {
                         ((NAME_MAX + 1 + sizeof(long)) & ~(sizeof(long) - 1))];
 };
 
-static gboolean
-cp_internal (GFile         *src,
-             GFile         *dest,
-             gboolean       use_hardlinks,
-             GCancellable  *cancellable,
-             GError       **error);
+static inline void
+_set_error_from_errno (GError **error)
+{
+  int errsv = errno;
+  g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
+                       g_strerror (errsv));
+}
 
 static gboolean
-cp_internal_one_item (GFile         *src,
-                      GFile         *dest,
-                      GFileInfo     *file_info,
-                      gboolean       use_hardlinks,
-                      GCancellable  *cancellable,
-                      GError       **error)
+copy_xattrs_from_file_to_fd (GFile         *src,
+                             int            dest_fd,
+                             GCancellable  *cancellable,
+                             GError       **error)
 {
   gboolean ret = FALSE;
-  const char *name = g_file_info_get_name (file_info);
-  GFile *src_child = g_file_get_child (src, name);
-  GFile *dest_child = g_file_get_child (dest, name);
-
-  if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_DIRECTORY)
-    {
-      if (!gs_file_ensure_directory (dest_child, FALSE, cancellable, error))
-        goto out;
-
-      /* Can't do this even though we'd like to; it fails with an error about
-       * setting standard::type not being supported =/
-       *
-       if (!g_file_set_attributes_from_info (dest_child, file_info, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
-       cancellable, error))
-       goto out;
-      */
-      if (chmod (gs_file_get_path_cached (dest_child),
-                 g_file_info_get_attribute_uint32 (file_info, "unix::mode")) == -1)
-        {
-          int errsv = errno;
-          g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
-                               g_strerror (errsv));
-          goto out;
-        }
+  GVariant *src_xattrs = NULL;
 
-      if (chown (gs_file_get_path_cached (dest_child),
-                 g_file_info_get_attribute_uint32 (file_info, "unix::uid"),
-                 g_file_info_get_attribute_uint32 (file_info, "unix::gid")) == -1)
-        {
-          int errsv = errno;
-          g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
-                               g_strerror (errsv));
-          goto out;
-        }
+  if (!gs_file_get_all_xattrs (src, &src_xattrs, cancellable, error))
+    goto out;
 
-      if (!cp_internal (src_child, dest_child, use_hardlinks, cancellable, error))
-        goto out;
-    }
-  else
+  if (src_xattrs)
     {
-      gboolean did_link = FALSE;
-      (void) unlink (gs_file_get_path_cached (dest_child));
-      if (use_hardlinks)
-        {
-          if (link (gs_file_get_path_cached (src_child), gs_file_get_path_cached (dest_child)) == -1)
-            {
-              if (!(errno == EMLINK || errno == EXDEV))
-                {
-                  int errsv = errno;
-                  g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
-                                       g_strerror (errsv));
-                  goto out;
-                }
-              use_hardlinks = FALSE;
-            }
-          else
-            did_link = TRUE;
-        }
-      if (!did_link)
-        {
-          if (!g_file_copy (src_child, dest_child,
-                            G_FILE_COPY_OVERWRITE | G_FILE_COPY_ALL_METADATA | G_FILE_COPY_NOFOLLOW_SYMLINKS,
-                            cancellable, NULL, NULL, error))
-            goto out;
-        }
+      if (!gs_fd_set_all_xattrs (dest_fd, src_xattrs, cancellable, error))
+        goto out;
     }
 
   ret = TRUE;
  out:
-  g_clear_object (&src_child);
-  g_clear_object (&dest_child);
+  g_clear_pointer (&src_xattrs, g_variant_unref);
   return ret;
 }
 
+typedef enum {
+  GS_CP_MODE_NONE,
+  GS_CP_MODE_HARDLINK,
+  GS_CP_MODE_COPY_ALL
+} GsCpMode;
+
 static gboolean
 cp_internal (GFile         *src,
              GFile         *dest,
-             gboolean       use_hardlinks,
+             GsCpMode       mode,
              GCancellable  *cancellable,
              GError       **error)
 {
   gboolean ret = FALSE;
   GFileEnumerator *enumerator = NULL;
-  GFileInfo *file_info = NULL;
-  GError *temp_error = NULL;
+  GFileInfo *src_info = NULL;
+  GFile *dest_child = NULL;
+  int dest_dfd = -1;
+  int r;
 
   enumerator = g_file_enumerate_children (src, 
"standard::type,standard::name,unix::uid,unix::gid,unix::mode",
                                           G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
@@ -148,26 +98,115 @@ cp_internal (GFile         *src,
   if (!enumerator)
     goto out;
 
-  if (!gs_file_ensure_directory (dest, FALSE, cancellable, error))
+  src_info = g_file_query_info (src, "standard::name,unix::mode,unix::uid,unix::gid," \
+                                "time::modified,time::modified-usec,time::access,time::access-usec",
+                                G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                cancellable, error);
+  if (!src_info)
     goto out;
 
-  while ((file_info = g_file_enumerator_next_file (enumerator, cancellable, &temp_error)) != NULL)
+  do
+    r = mkdir (gs_file_get_path_cached (dest), 0755);
+  while (G_UNLIKELY (r == -1 && errno == EINTR));
+  if (r == -1)
+    {
+      _set_error_from_errno (error);
+      goto out;
+    }
+
+  if (mode != GS_CP_MODE_NONE)
     {
-      if (!cp_internal_one_item (src, dest, file_info, use_hardlinks,
-                                 cancellable, error))
+      if (!gs_file_open_dir_fd (dest, &dest_dfd,
+                                cancellable, error))
+        goto out;
+
+      do
+        r = fchown (dest_dfd,
+                    g_file_info_get_attribute_uint32 (src_info, "unix::uid"),
+                    g_file_info_get_attribute_uint32 (src_info, "unix::gid"));
+      while (G_UNLIKELY (r == -1 && errno == EINTR));
+      if (r == -1)
+        {
+          _set_error_from_errno (error);
+          goto out;
+        }
+
+      do
+        r = fchmod (dest_dfd, g_file_info_get_attribute_uint32 (src_info, "unix::mode"));
+      while (G_UNLIKELY (r == -1 && errno == EINTR));
+
+      if (!copy_xattrs_from_file_to_fd (src, dest_dfd, cancellable, error))
         goto out;
-      g_clear_object (&file_info);
+
+      if (dest_dfd != -1)
+        {
+          (void) close (dest_dfd);
+          dest_dfd = -1;
+        }
     }
-  if (temp_error)
+
+  while (TRUE)
     {
-      g_propagate_error (error, temp_error);
-      goto out;
+      GFileInfo *file_info = NULL;
+      GFile *src_child = NULL;
+
+      if (!gs_file_enumerator_iterate (enumerator, &file_info, &src_child,
+                                       cancellable, error))
+        goto out;
+      if (!file_info)
+        break;
+
+      if (dest_child) g_object_unref (dest_child);
+      dest_child = g_file_get_child (dest, g_file_info_get_name (file_info));
+
+      if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_DIRECTORY)
+        {
+          if (!cp_internal (src_child, dest_child, mode,
+                            cancellable, error))
+            goto out;
+        }
+      else
+        {
+          gboolean did_link = FALSE;
+          (void) unlink (gs_file_get_path_cached (dest_child));
+          if (mode == GS_CP_MODE_HARDLINK)
+            {
+              if (link (gs_file_get_path_cached (src_child), gs_file_get_path_cached (dest_child)) == -1)
+                {
+                  if (!(errno == EMLINK || errno == EXDEV))
+                    {
+                      int errsv = errno;
+                      g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
+                                           g_strerror (errsv));
+                      goto out;
+                    }
+                  /* We failed to hardlink; fall back to copying all; this will
+                   * affect subsequent directory copies too.
+                   */
+                  mode = GS_CP_MODE_COPY_ALL;
+                }
+              else
+                did_link = TRUE;
+            }
+          if (!did_link)
+            {
+              GFileCopyFlags copyflags = G_FILE_COPY_OVERWRITE | G_FILE_COPY_NOFOLLOW_SYMLINKS;
+              if (mode == GS_CP_MODE_COPY_ALL)
+                copyflags |= G_FILE_COPY_ALL_METADATA;
+              if (!g_file_copy (src_child, dest_child, copyflags,
+                                cancellable, NULL, NULL, error))
+                goto out;
+            }
+        }
     }
 
   ret = TRUE;
  out:
+  if (dest_dfd != -1)
+    (void) close (dest_dfd);
+  g_clear_object (&src_info);
   g_clear_object (&enumerator);
-  g_clear_object (&file_info);
+  g_clear_object (&dest_child);
   return ret;
 }
 
@@ -191,7 +230,8 @@ gs_shutil_cp_al_or_fallback (GFile         *src,
                              GCancellable  *cancellable,
                              GError       **error)
 {
-  return cp_internal (src, dest, TRUE, cancellable, error);
+  return cp_internal (src, dest, GS_CP_MODE_HARDLINK,
+                      cancellable, error);
 }
 
 /**
@@ -212,7 +252,8 @@ gs_shutil_cp_a (GFile         *src,
                 GCancellable  *cancellable,
                 GError       **error)
 {
-  return cp_internal (src, dest, FALSE, cancellable, error);
+  return cp_internal (src, dest, GS_CP_MODE_COPY_ALL,
+                      cancellable, error);
 }
 
 static unsigned char


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]