summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorColin Walters <walters@verbum.org>2013-11-20 17:43:09 -0500
committerColin Walters <walters@verbum.org>2013-11-28 23:29:14 -0500
commitbfe765fc00337e3e3e276551e98a55e36c266f80 (patch)
tree4cbba2b703c2723589c234fd3db7db000ad5a83c
parentca53db5614d57969908cdd8a99bd34394c9e969d (diff)
downloadlibgsystem-bfe765fc00337e3e3e276551e98a55e36c266f80.tar.gz
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
-rw-r--r--gsystem-file-utils.c279
-rw-r--r--gsystem-file-utils.h15
-rw-r--r--gsystem-shutil.c221
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