+#include <locale.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
gint monitor_deleted;
gint monitor_changed;
gchar *monitor_path;
- gint pos;
+ gsize pos;
const gchar *data;
gchar *buffer;
guint timeout;
#endif
}
+static void
+test_replace_symlink_using_etag (void)
+{
+#ifdef G_OS_UNIX
+ gchar *tmpdir_path = NULL;
+ GFile *tmpdir = NULL, *source_file = NULL, *target_file = NULL;
+ GFileOutputStream *stream = NULL;
+ const gchar *old_contents = "this is a test message which should be written to target and then overwritten";
+ gchar *old_etag = NULL;
+ const gchar *new_contents = "this is an updated message";
+ gsize n_written;
+ gchar *contents = NULL;
+ gsize length = 0;
+ GFileInfo *info = NULL;
+ GError *local_error = NULL;
+
+ g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/2417");
+ g_test_summary ("Test that ETag checks work when replacing a file through a symlink");
+
+ /* Create a fresh, empty working directory. */
+ tmpdir_path = g_dir_make_tmp ("g_file_replace_symlink_using_etag_XXXXXX", &local_error);
+ g_assert_no_error (local_error);
+ tmpdir = g_file_new_for_path (tmpdir_path);
+
+ g_test_message ("Using temporary directory %s", tmpdir_path);
+ g_free (tmpdir_path);
+
+ /* Create symlink `source` which points to `target`. */
+ source_file = g_file_get_child (tmpdir, "source");
+ target_file = g_file_get_child (tmpdir, "target");
+ g_file_make_symbolic_link (source_file, "target", NULL, &local_error);
+ g_assert_no_error (local_error);
+
+ /* Sleep for at least 1s to ensure the mtimes of `source` and `target` differ,
+ * as that’s what _g_local_file_info_create_etag() uses to create the ETag,
+ * and one failure mode we’re testing for is that the ETags of `source` and
+ * `target` are conflated. */
+ sleep (1);
+
+ /* Create `target` with some arbitrary content. */
+ stream = g_file_create (target_file, G_FILE_CREATE_NONE, NULL, &local_error);
+ g_assert_no_error (local_error);
+ g_output_stream_write_all (G_OUTPUT_STREAM (stream), old_contents, strlen (old_contents),
+ &n_written, NULL, &local_error);
+ g_assert_no_error (local_error);
+ g_assert_cmpint (n_written, ==, strlen (old_contents));
+
+ g_output_stream_close (G_OUTPUT_STREAM (stream), NULL, &local_error);
+ g_assert_no_error (local_error);
+
+ old_etag = g_file_output_stream_get_etag (stream);
+ g_assert_nonnull (old_etag);
+ g_assert_cmpstr (old_etag, !=, "");
+
+ g_clear_object (&stream);
+
+ /* Sleep again to ensure the ETag changes again. */
+ sleep (1);
+
+ /* Write out a new copy of the `target`, checking its ETag first. This should
+ * replace `target` by following the symlink. */
+ stream = g_file_replace (source_file, old_etag, FALSE /* no backup */,
+ G_FILE_CREATE_NONE, NULL, &local_error);
+ g_assert_no_error (local_error);
+
+ g_output_stream_write_all (G_OUTPUT_STREAM (stream), new_contents, strlen (new_contents),
+ &n_written, NULL, &local_error);
+ g_assert_no_error (local_error);
+ g_assert_cmpint (n_written, ==, strlen (new_contents));
+
+ g_output_stream_close (G_OUTPUT_STREAM (stream), NULL, &local_error);
+ g_assert_no_error (local_error);
+
+ g_clear_object (&stream);
+
+ /* At this point, there should be a regular file, `target`, containing
+ * @new_contents; and a symlink `source` which points to `target`. */
+ g_assert_cmpint (g_file_query_file_type (source_file, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL), ==, G_FILE_TYPE_SYMBOLIC_LINK);
+ g_assert_cmpint (g_file_query_file_type (target_file, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL), ==, G_FILE_TYPE_REGULAR);
+
+ /* Check the content of `target`. */
+ g_file_load_contents (target_file,
+ NULL,
+ &contents,
+ &length,
+ NULL,
+ &local_error);
+ g_assert_no_error (local_error);
+ g_assert_cmpstr (contents, ==, new_contents);
+ g_assert_cmpuint (length, ==, strlen (new_contents));
+ g_free (contents);
+
+ /* And check its ETag value has changed. */
+ info = g_file_query_info (target_file, G_FILE_ATTRIBUTE_ETAG_VALUE,
+ G_FILE_QUERY_INFO_NONE, NULL, &local_error);
+ g_assert_no_error (local_error);
+ g_assert_cmpstr (g_file_info_get_etag (info), !=, old_etag);
+
+ g_clear_object (&info);
+ g_free (old_etag);
+
+ /* Tidy up. */
+ g_file_delete (target_file, NULL, &local_error);
+ g_assert_no_error (local_error);
+
+ g_file_delete (source_file, NULL, &local_error);
+ g_assert_no_error (local_error);
+
+ g_file_delete (tmpdir, NULL, &local_error);
+ g_assert_no_error (local_error);
+
+ g_clear_object (&target_file);
+ g_clear_object (&source_file);
+ g_clear_object (&tmpdir);
+#else /* if !G_OS_UNIX */
+ g_test_skip ("Symlink replacement tests can only be run on Unix")
+#endif
+}
+
/* FIXME: These tests have only been checked on Linux. Most of them are probably
* applicable on Windows, too, but that has not been tested yet.
* See https://gitlab.gnome.org/GNOME/glib/-/issues/2325 */
g_clear_object (&tmpfile);
}
+typedef struct
+{
+ GError *error;
+ gboolean done;
+ gboolean res;
+} MoveAsyncData;
+
+static void
+test_move_async_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GFile *file = G_FILE (object);
+ MoveAsyncData *data = user_data;
+ GError *error = NULL;
+
+ data->res = g_file_move_finish (file, result, &error);
+ data->error = error;
+ data->done = TRUE;
+}
+
+typedef struct
+{
+ goffset total_num_bytes;
+} MoveAsyncProgressData;
+
+static void
+test_move_async_progress_cb (goffset current_num_bytes,
+ goffset total_num_bytes,
+ gpointer user_data)
+{
+ MoveAsyncProgressData *data = user_data;
+ data->total_num_bytes = total_num_bytes;
+}
+
+/* Test that move_async() moves the file correctly */
+static void
+test_move_async (void)
+{
+ MoveAsyncData data = { 0 };
+ MoveAsyncProgressData progress_data = { 0 };
+ GFile *source;
+ GFileIOStream *iostream;
+ GOutputStream *ostream;
+ GFile *destination;
+ gchar *destination_path;
+ GError *error = NULL;
+ gboolean res;
+ const guint8 buffer[] = {1, 2, 3, 4, 5};
+
+ source = g_file_new_tmp ("g_file_move_XXXXXX", &iostream, NULL);
+
+ destination_path = g_build_path (G_DIR_SEPARATOR_S, g_get_tmp_dir (), "g_file_move_target", NULL);
+ destination = g_file_new_for_path (destination_path);
+
+ g_assert_nonnull (source);
+ g_assert_nonnull (iostream);
+
+ res = g_file_query_exists (source, NULL);
+ g_assert_true (res);
+ res = g_file_query_exists (destination, NULL);
+ g_assert_false (res);
+
+ // Write a known amount of bytes to the file, so we can test the progress callback against it
+ ostream = g_io_stream_get_output_stream (G_IO_STREAM (iostream));
+ g_output_stream_write (ostream, buffer, sizeof (buffer), NULL, &error);
+ g_assert_no_error (error);
+
+ g_file_move_async (source,
+ destination,
+ G_FILE_COPY_NONE,
+ 0,
+ NULL,
+ test_move_async_progress_cb,
+ &progress_data,
+ test_move_async_cb,
+ &data);
+
+ while (!data.done)
+ g_main_context_iteration (NULL, TRUE);
+
+ g_assert_no_error (data.error);
+ g_assert_true (data.res);
+ g_assert_cmpuint (progress_data.total_num_bytes, ==, sizeof (buffer));
+
+ res = g_file_query_exists (source, NULL);
+ g_assert_false (res);
+ res = g_file_query_exists (destination, NULL);
+ g_assert_true (res);
+
+ res = g_io_stream_close (G_IO_STREAM (iostream), NULL, &error);
+ g_assert_no_error (error);
+ g_assert_true (res);
+ g_object_unref (iostream);
+
+ res = g_file_delete (destination, NULL, &error);
+ g_assert_no_error (error);
+ g_assert_true (res);
+
+ g_object_unref (source);
+ g_object_unref (destination);
+
+ g_free (destination_path);
+}
+
int
main (int argc, char *argv[])
{
+ setlocale (LC_ALL, "");
+
g_test_init (&argc, &argv, NULL);
g_test_add_func ("/file/basic", test_basic);
g_test_add_func ("/file/replace-load", test_replace_load);
g_test_add_func ("/file/replace-cancel", test_replace_cancel);
g_test_add_func ("/file/replace-symlink", test_replace_symlink);
+ g_test_add_func ("/file/replace-symlink/using-etag", test_replace_symlink_using_etag);
g_test_add_data_func ("/file/replace/write-only", GUINT_TO_POINTER (FALSE), test_replace);
g_test_add_data_func ("/file/replace/read-write", GUINT_TO_POINTER (TRUE), test_replace);
g_test_add_func ("/file/async-delete", test_async_delete);
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);
+ g_test_add_func ("/file/move_async", test_move_async);
return g_test_run ();
}