From 094eca7076d8db4d7e2d5344101b7d8e6bd9de36 Mon Sep 17 00:00:00 2001 From: Maxim Mikityanskiy Date: Mon, 7 Sep 2020 19:20:01 +0300 Subject: [PATCH] gio: Expose g_file_build_attribute_list_for_copy Expose a function that prepares an attribute query string to be passed to g_file_query_info() to get a list of attributes normally copied with the file. This function is used by the implementation of g_file_copy_attributes, and it's useful if one needs to split g_file_copy_attributes into two stages, for example, when nautilus does a recursive move of a directory. When files are moved from the source directory, its modification time changes. To preserve the mtime on the destination directory, it has to be queried before moving files and set after doing it, hence these two stages. Signed-off-by: Maxim Mikityanskiy --- docs/reference/gio/gio-sections-common.txt | 1 + gio/gfile.c | 113 +++++++++++++-------- gio/gfile.h | 6 ++ gio/tests/file.c | 74 ++++++++++++++ 4 files changed, 149 insertions(+), 45 deletions(-) diff --git a/docs/reference/gio/gio-sections-common.txt b/docs/reference/gio/gio-sections-common.txt index 4173f7dcf..912c25507 100644 --- a/docs/reference/gio/gio-sections-common.txt +++ b/docs/reference/gio/gio-sections-common.txt @@ -199,6 +199,7 @@ g_file_replace_contents g_file_replace_contents_async g_file_replace_contents_bytes_async g_file_replace_contents_finish +g_file_build_attribute_list_for_copy g_file_copy_attributes g_file_create_readwrite g_file_create_readwrite_async diff --git a/gio/gfile.c b/gio/gfile.c index 533efa7df..6e3b524f7 100644 --- a/gio/gfile.c +++ b/gio/gfile.c @@ -2695,14 +2695,36 @@ should_copy (GFileAttributeInfo *info, return info->flags & G_FILE_ATTRIBUTE_INFO_COPY_WITH_FILE; } -static gboolean -build_attribute_list_for_copy (GFile *file, - GFileCopyFlags flags, - char **out_attributes, - GCancellable *cancellable, - GError **error) +/** + * g_file_build_attribute_list_for_copy: + * @file: a #GFile to copy attributes to + * @flags: a set of #GFileCopyFlags + * @cancellable: (nullable): optional #GCancellable object, + * %NULL to ignore + * @error: a #GError, %NULL to ignore + * + * Prepares the file attribute query string for copying to @file. + * + * This function prepares an attribute query string to be + * passed to g_file_query_info() to get a list of attributes + * normally copied with the file (see g_file_copy_attributes() + * for the detailed description). This function is used by the + * implementation of g_file_copy_attributes() and is useful + * when one needs to query and set the attributes in two + * stages (e.g., for recursive move of a directory). + * + * Returns: an attribute query string for g_file_query_info(), + * or %NULL if an error occurs. + * + * Since: 2.68 + */ +char * +g_file_build_attribute_list_for_copy (GFile *file, + GFileCopyFlags flags, + GCancellable *cancellable, + GError **error) { - gboolean ret = FALSE; + char *ret = NULL; GFileAttributeInfoList *attributes = NULL, *namespaces = NULL; GString *s = NULL; gboolean first; @@ -2710,6 +2732,10 @@ build_attribute_list_for_copy (GFile *file, gboolean copy_all_attributes; gboolean skip_perms; + g_return_val_if_fail (G_IS_FILE (file), NULL); + g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + copy_all_attributes = flags & G_FILE_COPY_ALL_METADATA; skip_perms = (flags & G_FILE_COPY_TARGET_DEFAULT_PERMS) != 0; @@ -2763,8 +2789,7 @@ build_attribute_list_for_copy (GFile *file, } } - ret = TRUE; - *out_attributes = g_string_free (s, FALSE); + ret = g_string_free (s, FALSE); s = NULL; out: if (s) @@ -2810,8 +2835,9 @@ g_file_copy_attributes (GFile *source, GFileInfo *info; gboolean source_nofollow_symlinks; - if (!build_attribute_list_for_copy (destination, flags, &attrs_to_read, - cancellable, error)) + attrs_to_read = g_file_build_attribute_list_for_copy (destination, flags, + cancellable, error); + if (!attrs_to_read) return FALSE; source_nofollow_symlinks = flags & G_FILE_COPY_NOFOLLOW_SYMLINKS; @@ -3157,6 +3183,7 @@ file_copy_fallback (GFile *source, char *attrs_to_read; gboolean do_set_attributes = FALSE; GFileCreateFlags create_flags; + GError *tmp_error = NULL; /* need to know the file type */ info = g_file_query_info (source, @@ -3198,47 +3225,43 @@ file_copy_fallback (GFile *source, goto out; in = G_INPUT_STREAM (file_in); - if (!build_attribute_list_for_copy (destination, flags, &attrs_to_read, - cancellable, error)) + attrs_to_read = g_file_build_attribute_list_for_copy (destination, flags, + cancellable, error); + if (!attrs_to_read) goto out; - if (attrs_to_read != NULL) + /* Ok, ditch the previous lightweight info (on Unix we just + * called lstat()); at this point we gather all the information + * we need about the source from the opened file descriptor. + */ + g_object_unref (info); + + info = g_file_input_stream_query_info (file_in, attrs_to_read, + cancellable, &tmp_error); + if (!info) { - GError *tmp_error = NULL; - - /* Ok, ditch the previous lightweight info (on Unix we just - * called lstat()); at this point we gather all the information - * we need about the source from the opened file descriptor. + /* Not all gvfs backends implement query_info_on_read(), we + * can just fall back to the pathname again. + * https://bugzilla.gnome.org/706254 */ - g_object_unref (info); - - info = g_file_input_stream_query_info (file_in, attrs_to_read, - cancellable, &tmp_error); - if (!info) + if (g_error_matches (tmp_error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED)) { - /* Not all gvfs backends implement query_info_on_read(), we - * can just fall back to the pathname again. - * https://bugzilla.gnome.org/706254 - */ - if (g_error_matches (tmp_error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED)) - { - g_clear_error (&tmp_error); - info = g_file_query_info (source, attrs_to_read, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, - cancellable, error); - } - else - { - g_free (attrs_to_read); - g_propagate_error (error, tmp_error); - goto out; - } + g_clear_error (&tmp_error); + info = g_file_query_info (source, attrs_to_read, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + cancellable, error); + } + else + { + g_free (attrs_to_read); + g_propagate_error (error, tmp_error); + goto out; } - g_free (attrs_to_read); - if (!info) - goto out; - - do_set_attributes = TRUE; } + g_free (attrs_to_read); + if (!info) + goto out; + + do_set_attributes = TRUE; /* In the local file path, we pass down the source info which * includes things like unix::mode, to ensure that the target file diff --git a/gio/gfile.h b/gio/gfile.h index 8b6d08385..4cff1a372 100644 --- a/gio/gfile.h +++ b/gio/gfile.h @@ -1094,6 +1094,12 @@ gboolean g_file_eject_mountable_with_operation_finish (GFile GAsyncResult *result, GError **error); +GLIB_AVAILABLE_IN_2_68 +char * g_file_build_attribute_list_for_copy (GFile *file, + GFileCopyFlags flags, + GCancellable *cancellable, + GError **error); + GLIB_AVAILABLE_IN_ALL gboolean g_file_copy_attributes (GFile *source, GFile *destination, diff --git a/gio/tests/file.c b/gio/tests/file.c index c3877af4b..e951e1fcd 100644 --- a/gio/tests/file.c +++ b/gio/tests/file.c @@ -1780,6 +1780,79 @@ test_writev_async_all_too_big_vectors (void) g_object_unref (file); } +static void +test_build_attribute_list_for_copy (void) +{ + GFile *tmpfile; + GFileIOStream *iostream; + GError *error = NULL; + const GFileCopyFlags test_flags[] = + { + G_FILE_COPY_NONE, + G_FILE_COPY_TARGET_DEFAULT_PERMS, + G_FILE_COPY_ALL_METADATA, + G_FILE_COPY_ALL_METADATA | G_FILE_COPY_TARGET_DEFAULT_PERMS, + }; + gsize i; + char *attrs; + gchar *attrs_with_commas; + + tmpfile = g_file_new_tmp ("tmp-build-attribute-list-for-copyXXXXXX", + &iostream, &error); + g_assert_no_error (error); + g_io_stream_close ((GIOStream*)iostream, NULL, &error); + g_assert_no_error (error); + g_clear_object (&iostream); + + for (i = 0; i < G_N_ELEMENTS (test_flags); i++) + { + GFileCopyFlags flags = test_flags[i]; + + attrs = g_file_build_attribute_list_for_copy (tmpfile, flags, NULL, &error); + g_test_message ("Attributes for copy: %s", attrs); + g_assert_no_error (error); + g_assert_nonnull (attrs); + attrs_with_commas = g_strconcat (",", attrs, ",", NULL); + g_free (attrs); + + /* See g_local_file_class_init for reference. */ + if (flags & G_FILE_COPY_TARGET_DEFAULT_PERMS) + g_assert_null (g_strstr_len (attrs_with_commas, -1, "," G_FILE_ATTRIBUTE_UNIX_MODE ",")); + else + g_assert_nonnull (g_strstr_len (attrs_with_commas, -1, "," G_FILE_ATTRIBUTE_UNIX_MODE ",")); +#ifdef G_OS_UNIX + if (flags & G_FILE_COPY_ALL_METADATA) + { + g_assert_nonnull (g_strstr_len (attrs_with_commas, -1, "," G_FILE_ATTRIBUTE_UNIX_UID ",")); + g_assert_nonnull (g_strstr_len (attrs_with_commas, -1, "," G_FILE_ATTRIBUTE_UNIX_GID ",")); + } + else + { + g_assert_null (g_strstr_len (attrs_with_commas, -1, "," G_FILE_ATTRIBUTE_UNIX_UID ",")); + g_assert_null (g_strstr_len (attrs_with_commas, -1, "," G_FILE_ATTRIBUTE_UNIX_GID ",")); + } +#endif +#ifdef HAVE_UTIMES + g_assert_nonnull (g_strstr_len (attrs_with_commas, -1, "," G_FILE_ATTRIBUTE_TIME_MODIFIED ",")); + g_assert_nonnull (g_strstr_len (attrs_with_commas, -1, "," G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC ",")); + if (flags & G_FILE_COPY_ALL_METADATA) + { + g_assert_nonnull (g_strstr_len (attrs_with_commas, -1, "," G_FILE_ATTRIBUTE_TIME_ACCESS ",")); + g_assert_nonnull (g_strstr_len (attrs_with_commas, -1, "," G_FILE_ATTRIBUTE_TIME_ACCESS_USEC ",")); + } + else + { + g_assert_null (g_strstr_len (attrs_with_commas, -1, "," G_FILE_ATTRIBUTE_TIME_ACCESS ",")); + g_assert_null (g_strstr_len (attrs_with_commas, -1, "," G_FILE_ATTRIBUTE_TIME_ACCESS_USEC ",")); + } +#endif + g_free (attrs_with_commas); + } + + (void) g_file_delete (tmpfile, NULL, NULL); + g_clear_object (&tmpfile); +} + int main (int argc, char *argv[]) { @@ -1817,6 +1890,7 @@ main (int argc, char *argv[]) g_test_add_func ("/file/writev/async_all-no-vectors", test_writev_async_all_no_vectors); g_test_add_func ("/file/writev/async_all-to-big-vectors", test_writev_async_all_too_big_vectors); g_test_add_func ("/file/writev/async_all-cancellation", test_writev_async_all_cancellation); + g_test_add_func ("/file/build-attribute-list-for-copy", test_build_attribute_list_for_copy); return g_test_run (); }