summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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