diff options
-rw-r--r-- | gsystem-file-utils.c | 279 | ||||
-rw-r--r-- | gsystem-file-utils.h | 15 | ||||
-rw-r--r-- | 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 |