From 02aaef5a4dd92fe3a3f3a7c4ccebf22ca1ba2644 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Thu, 9 May 2013 00:01:59 +0100 Subject: [PATCH] g_file_copy(): Clean up logic for info query Previously, we called g_file_query_info() *again* on the source at the very end of the copy. This has the lame semantics that if the source happened to be deleted, we would fail to apply attributes to the destination. This could even be a security flaw. This commit changes things so that we query info from the source *stream* after opening - i.e. on Unix we use the proper fstat() and friends. That way we operate more atomically. https://bugzilla.gnome.org/show_bug.cgi?id=699959 --- gio/gfile.c | 128 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 88 insertions(+), 40 deletions(-) diff --git a/gio/gfile.c b/gio/gfile.c index 961d40f79..57d3bc31c 100644 --- a/gio/gfile.c +++ b/gio/gfile.c @@ -2510,7 +2510,7 @@ copy_symlink (GFile *destination, return TRUE; } -static GInputStream * +static GFileInputStream * open_source_for_copy (GFile *source, GFile *destination, GFileCopyFlags flags, @@ -2518,14 +2518,14 @@ open_source_for_copy (GFile *source, GError **error) { GError *my_error; - GInputStream *in; + GFileInputStream *ret; GFileInfo *info; GFileType file_type; my_error = NULL; - in = (GInputStream *)g_file_read (source, cancellable, &my_error); - if (in != NULL) - return in; + ret = g_file_read (source, cancellable, &my_error); + if (ret != NULL) + return ret; /* There was an error opening the source, try to set a good error for it: */ if (my_error->domain == G_IO_ERROR && my_error->code == G_IO_ERROR_IS_DIRECTORY) @@ -2588,26 +2588,48 @@ open_source_for_copy (GFile *source, static gboolean should_copy (GFileAttributeInfo *info, - gboolean as_move, + gboolean copy_all_attributes, gboolean skip_perms) { if (skip_perms && strcmp(info->name, "unix::mode") == 0) return FALSE; - if (as_move) + if (copy_all_attributes) return info->flags & G_FILE_ATTRIBUTE_INFO_COPY_WHEN_MOVED; return info->flags & G_FILE_ATTRIBUTE_INFO_COPY_WITH_FILE; } -static char * -build_attribute_list_for_copy (GFileAttributeInfoList *attributes, - GFileAttributeInfoList *namespaces, - gboolean as_move, - gboolean skip_perms) +static gboolean +build_attribute_list_for_copy (GFile *file, + GFileCopyFlags flags, + char **out_attributes, + GCancellable *cancellable, + GError **error) { + gboolean ret = FALSE; + GFileAttributeInfoList *attributes = NULL, *namespaces = NULL; GString *s; gboolean first; int i; + gboolean copy_all_attributes; + gboolean skip_perms; + + copy_all_attributes = flags & G_FILE_COPY_ALL_METADATA; + skip_perms = (flags & G_FILE_COPY_TARGET_DEFAULT_PERMS) != 0; + + /* Ignore errors here, if the target supports no attributes there is + * nothing to copy. We still honor the cancellable though. + */ + attributes = g_file_query_settable_attributes (file, cancellable, NULL); + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + goto out; + + namespaces = g_file_query_writable_namespaces (file, cancellable, NULL); + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + goto out; + + if (attributes == NULL && namespaces == NULL) + goto out; first = TRUE; s = g_string_new (""); @@ -2616,7 +2638,7 @@ build_attribute_list_for_copy (GFileAttributeInfoList *attributes, { for (i = 0; i < attributes->n_infos; i++) { - if (should_copy (&attributes->infos[i], as_move, skip_perms)) + if (should_copy (&attributes->infos[i], copy_all_attributes, skip_perms)) { if (first) first = FALSE; @@ -2632,7 +2654,7 @@ build_attribute_list_for_copy (GFileAttributeInfoList *attributes, { for (i = 0; i < namespaces->n_infos; i++) { - if (should_copy (&namespaces->infos[i], as_move, FALSE)) + if (should_copy (&namespaces->infos[i], copy_all_attributes, FALSE)) { if (first) first = FALSE; @@ -2645,7 +2667,18 @@ build_attribute_list_for_copy (GFileAttributeInfoList *attributes, } } - return g_string_free (s, FALSE); + ret = TRUE; + *out_attributes = g_string_free (s, FALSE); + s = NULL; + out: + if (s) + g_string_free (s, TRUE); + if (attributes) + g_file_attribute_info_list_unref (attributes); + if (namespaces) + g_file_attribute_info_list_unref (namespaces); + + return ret; } /** @@ -2676,28 +2709,16 @@ g_file_copy_attributes (GFile *source, GCancellable *cancellable, GError **error) { - GFileAttributeInfoList *attributes, *namespaces; char *attrs_to_read; gboolean res; GFileInfo *info; - gboolean as_move; gboolean source_nofollow_symlinks; - gboolean skip_perms; - as_move = flags & G_FILE_COPY_ALL_METADATA; + if (!build_attribute_list_for_copy (destination, flags, &attrs_to_read, + cancellable, error)) + return FALSE; + source_nofollow_symlinks = flags & G_FILE_COPY_NOFOLLOW_SYMLINKS; - skip_perms = (flags & G_FILE_COPY_TARGET_DEFAULT_PERMS) != 0; - - /* Ignore errors here, if the target supports no attributes there is - * nothing to copy - */ - attributes = g_file_query_settable_attributes (destination, cancellable, NULL); - namespaces = g_file_query_writable_namespaces (destination, cancellable, NULL); - - if (attributes == NULL && namespaces == NULL) - return TRUE; - - attrs_to_read = build_attribute_list_for_copy (attributes, namespaces, as_move, skip_perms); /* Ignore errors here, if we can't read some info (e.g. if it doesn't exist) * we just don't copy it. @@ -2720,9 +2741,6 @@ g_file_copy_attributes (GFile *source, g_object_unref (info); } - g_file_attribute_info_list_unref (attributes); - g_file_attribute_info_list_unref (namespaces); - return res; } @@ -3012,10 +3030,13 @@ file_copy_fallback (GFile *source, GError **error) { gboolean ret = FALSE; + GFileInputStream *file_in = NULL; GInputStream *in = NULL; GOutputStream *out = NULL; GFileInfo *info = NULL; const char *target; + char *attrs_to_read; + gboolean do_set_attributes = FALSE; /* need to know the file type */ info = g_file_query_info (source, @@ -3052,9 +3073,31 @@ file_copy_fallback (GFile *source, /* Everything else should just fall back on a regular copy. */ - in = open_source_for_copy (source, destination, flags, cancellable, error); - if (!in) + file_in = open_source_for_copy (source, destination, flags, cancellable, error); + if (!file_in) goto out; + in = G_INPUT_STREAM (file_in); + + if (!build_attribute_list_for_copy (destination, flags, &attrs_to_read, + cancellable, error)) + 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, error); + g_free (attrs_to_read); + if (!info) + goto out; + + do_set_attributes = TRUE; + } if (flags & G_FILE_COPY_OVERWRITE) { @@ -3151,10 +3194,15 @@ file_copy_fallback (GFile *source, } /* Ignore errors here. Failure to copy metadata is not a hard error */ - if (ret) - (void) g_file_copy_attributes (source, destination, - flags, cancellable, NULL); - + /* TODO: set these attributes /before/ we do the rename() on Unix */ + if (ret && do_set_attributes) + { + g_file_set_attributes_from_info (destination, + info, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + cancellable, + error); + } g_clear_object (&info);