diff --git a/config.h.meson b/config.h.meson index d540cb801..2bfe3f6b2 100644 --- a/config.h.meson +++ b/config.h.meson @@ -250,6 +250,9 @@ /* Define if libmount is available */ #mesondefine HAVE_LIBMOUNT +/* Define to 1 if you have the `link' function. */ +#mesondefine HAVE_LINK + /* Define to 1 if you have the header file. */ #mesondefine HAVE_LINUX_MAGIC_H diff --git a/config.h.win32.in b/config.h.win32.in index b50d8146a..14ac11d39 100644 --- a/config.h.win32.in +++ b/config.h.win32.in @@ -260,6 +260,9 @@ /* Define if libelf is available */ /* #undef HAVE_LIBELF */ +/* Define to 1 if you have the `link' function. */ +/* #undef HAVE_LINK */ + /* Define to 1 if you have the header file. */ /* #undef HAVE_LINUX_MAGIC_H */ diff --git a/configure.ac b/configure.ac index 06397b150..44c5ada59 100644 --- a/configure.ac +++ b/configure.ac @@ -874,7 +874,7 @@ int main () AC_MSG_RESULT($glib_ssize_type) # Check for some functions -AC_CHECK_FUNCS(lstat strsignal vsnprintf stpcpy strcasecmp strncasecmp poll vasprintf setenv unsetenv getc_unlocked readlink symlink fdwalk mkostemp) +AC_CHECK_FUNCS(lstat strsignal vsnprintf stpcpy strcasecmp strncasecmp poll vasprintf setenv unsetenv getc_unlocked readlink symlink fdwalk mkostemp link) AC_CHECK_FUNCS(lchmod lchown fchmod fchown utimes getresuid) AC_CHECK_FUNCS(getmntent_r setmntent endmntent hasmntopt getfsstat getvfsstat fallocate) case $host_os in aix*) ac_cv_func_splice=no ;; esac # AIX splice() is something else diff --git a/gio/tests/testfilemonitor.c b/gio/tests/testfilemonitor.c index 229fd2142..05a5481fc 100644 --- a/gio/tests/testfilemonitor.c +++ b/gio/tests/testfilemonitor.c @@ -1,3 +1,6 @@ +#include "config.h" + +#include #include #include @@ -29,6 +32,7 @@ typedef struct GMainLoop *loop; gint step; GList *events; + GFileOutputStream *output_stream; } TestData; #if 0 @@ -652,16 +656,171 @@ test_cross_dir_moves (void) g_object_unref (data[1].file); } +static gboolean +file_hard_links_step (gpointer user_data) +{ + gboolean retval = G_SOURCE_CONTINUE; + TestData *data = user_data; + GError *error = NULL; + + gchar *filename = g_file_get_path (data->file); + gchar *hard_link_name = g_strdup_printf ("%s2", filename); + GFile *hard_link_file = g_file_new_for_path (hard_link_name); + + switch (data->step) + { + case 0: + record_event (data, -1, NULL, NULL, 0); + g_output_stream_write_all (G_OUTPUT_STREAM (data->output_stream), + "hello, step 0", 13, NULL, NULL, &error); + g_assert_no_error (error); + g_output_stream_close (G_OUTPUT_STREAM (data->output_stream), NULL, &error); + g_assert_no_error (error); + break; + case 1: + record_event (data, -1, NULL, NULL, 1); + g_file_replace_contents (data->file, "step 1", 6, NULL, FALSE, + G_FILE_CREATE_NONE, NULL, NULL, &error); + g_assert_no_error (error); + break; + case 2: + record_event (data, -1, NULL, NULL, 2); +#ifdef HAVE_LINK + if (link (filename, hard_link_name) < 0) + { + g_error ("link(%s, %s) failed: %s", filename, hard_link_name, g_strerror (errno)); + } +#endif /* HAVE_LINK */ + break; + case 3: + record_event (data, -1, NULL, NULL, 3); +#ifdef HAVE_LINK + { + GOutputStream *hard_link_stream = NULL; + + /* Deliberately don’t do an atomic swap on the hard-linked file. */ + hard_link_stream = G_OUTPUT_STREAM (g_file_append_to (hard_link_file, + G_FILE_CREATE_NONE, + NULL, &error)); + g_assert_no_error (error); + g_output_stream_write_all (hard_link_stream, " step 3", 7, NULL, NULL, &error); + g_assert_no_error (error); + g_output_stream_close (hard_link_stream, NULL, &error); + g_assert_no_error (error); + g_object_unref (hard_link_stream); + } +#endif /* HAVE_LINK */ + break; + case 4: + record_event (data, -1, NULL, NULL, 4); + g_file_delete (data->file, NULL, &error); + g_assert_no_error (error); + break; + case 5: + record_event (data, -1, NULL, NULL, 5); +#ifdef HAVE_LINK + g_file_delete (hard_link_file, NULL, &error); + g_assert_no_error (error); +#endif /* HAVE_LINK */ + break; + case 6: + record_event (data, -1, NULL, NULL, 6); + g_main_loop_quit (data->loop); + retval = G_SOURCE_REMOVE; + break; + } + + if (retval != G_SOURCE_REMOVE) + data->step++; + + g_object_unref (hard_link_file); + g_free (hard_link_name); + g_free (filename); + + return retval; +} + +static RecordedEvent file_hard_links_output[] = { + { -1, NULL, NULL, 0 }, + { G_FILE_MONITOR_EVENT_CHANGED, "testfilemonitor.db", NULL, -1 }, + { G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, "testfilemonitor.db", NULL, -1 }, + { -1, NULL, NULL, 1 }, + { G_FILE_MONITOR_EVENT_RENAMED, NULL /* .goutputstream-XXXXXX */, "testfilemonitor.db", -1 }, + { -1, NULL, NULL, 2 }, + { -1, NULL, NULL, 3 }, + /* FIXME: There should be a EVENT_CHANGED and EVENT_CHANGES_DONE_HINT here + * from the modification of the hard link. */ + { -1, NULL, NULL, 4 }, + { G_FILE_MONITOR_EVENT_DELETED, "testfilemonitor.db", NULL, -1 }, + { -1, NULL, NULL, 5 }, + { -1, NULL, NULL, 6 }, +}; + +static void +test_file_hard_links (void) +{ + GError *error = NULL; + TestData data; + + g_test_bug ("755721"); + +#ifdef HAVE_LINK + g_test_message ("Running with hard link tests"); +#else /* if !HAVE_LINK */ + g_test_message ("Running without hard link tests"); +#endif /* !HAVE_LINK */ + + data.step = 0; + data.events = NULL; + + /* Create a file which exists and is not a directory. */ + data.file = g_file_new_for_path ("testfilemonitor.db"); + data.output_stream = g_file_replace (data.file, NULL, FALSE, + G_FILE_CREATE_NONE, NULL, &error); + g_assert_no_error (error); + + /* Monitor it. Creating the monitor should not crash (bug #755721). */ + data.monitor = g_file_monitor_file (data.file, + G_FILE_MONITOR_WATCH_MOUNTS | + G_FILE_MONITOR_WATCH_MOVES | + G_FILE_MONITOR_WATCH_HARD_LINKS, + NULL, + &error); + g_assert_no_error (error); + g_assert_nonnull (data.monitor); + + /* Change the file a bit. */ + g_file_monitor_set_rate_limit (data.monitor, 200); + g_signal_connect (data.monitor, "changed", (GCallback) monitor_changed, &data); + + data.loop = g_main_loop_new (NULL, TRUE); + g_timeout_add (500, file_hard_links_step, &data); + g_main_loop_run (data.loop); + + /* output_events (data.events); */ + check_expected_events (file_hard_links_output, + G_N_ELEMENTS (file_hard_links_output), data.events); + + g_list_free_full (data.events, (GDestroyNotify) free_recorded_event); + g_main_loop_unref (data.loop); + g_object_unref (data.monitor); + g_object_unref (data.file); + g_object_unref (data.output_stream); +} + int main (int argc, char *argv[]) { g_test_init (&argc, &argv, NULL); + g_test_bug_base ("https://bugzilla.gnome.org/show_bug.cgi?id="); + g_test_add_func ("/monitor/atomic-replace", test_atomic_replace); g_test_add_func ("/monitor/file-changes", test_file_changes); g_test_add_func ("/monitor/dir-monitor", test_dir_monitor); g_test_add_func ("/monitor/dir-not-existent", test_dir_non_existent); g_test_add_func ("/monitor/cross-dir-moves", test_cross_dir_moves); + g_test_add_func ("/monitor/file/hard-links", test_file_hard_links); return g_test_run (); } diff --git a/meson.build b/meson.build index 4974a51bd..d1f41e603 100644 --- a/meson.build +++ b/meson.build @@ -392,6 +392,7 @@ functions = [ 'endservent', 'sendmmsg', 'recvmmsg', + 'link', ] if glib_conf.has('HAVE_SYS_STATVFS_H')