• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #include "config.h"
2 
3 #include <errno.h>
4 #include <stdlib.h>
5 #include <gio/gio.h>
6 
7 #include "glib/glib-private.h"
8 
9 /* These tests were written for the inotify implementation.
10  * Other implementations may require slight adjustments in
11  * the tests, e.g. the length of timeouts
12  */
13 
14 typedef struct
15 {
16   GFile *tmp_dir;
17 } Fixture;
18 
19 static void
setup(Fixture * fixture,gconstpointer user_data)20 setup (Fixture       *fixture,
21        gconstpointer  user_data)
22 {
23   gchar *path = NULL;
24   GError *local_error = NULL;
25 
26   path = g_dir_make_tmp ("gio-test-testfilemonitor_XXXXXX", &local_error);
27   g_assert_no_error (local_error);
28 
29   fixture->tmp_dir = g_file_new_for_path (path);
30 
31   g_test_message ("Using temporary directory: %s", path);
32 
33   g_free (path);
34 }
35 
36 static void
teardown(Fixture * fixture,gconstpointer user_data)37 teardown (Fixture       *fixture,
38           gconstpointer  user_data)
39 {
40   GError *local_error = NULL;
41 
42   g_file_delete (fixture->tmp_dir, NULL, &local_error);
43   g_assert_no_error (local_error);
44   g_clear_object (&fixture->tmp_dir);
45 }
46 
47 typedef enum {
48   NONE      = 0,
49   INOTIFY   = (1 << 1),
50   KQUEUE    = (1 << 2)
51 } Environment;
52 
53 typedef struct
54 {
55   gint event_type;
56   gchar *file;
57   gchar *other_file;
58   gint step;
59 
60   /* Since different file monitor implementation has different capabilities,
61    * we cannot expect all implementations to report all kind of events without
62    * any loss. This 'optional' field is a bit mask used to mark events which
63    * may be lost under specific platforms.
64    */
65   Environment optional;
66 } RecordedEvent;
67 
68 static void
free_recorded_event(RecordedEvent * event)69 free_recorded_event (RecordedEvent *event)
70 {
71   g_free (event->file);
72   g_free (event->other_file);
73   g_free (event);
74 }
75 
76 typedef struct
77 {
78   GFile *file;
79   GFileMonitor *monitor;
80   GMainLoop *loop;
81   gint step;
82   GList *events;
83   GFileOutputStream *output_stream;
84 } TestData;
85 
86 static void
output_event(const RecordedEvent * event)87 output_event (const RecordedEvent *event)
88 {
89   if (event->step >= 0)
90     g_test_message (">>>> step %d", event->step);
91   else
92     {
93       GTypeClass *class;
94 
95       class = g_type_class_ref (g_type_from_name ("GFileMonitorEvent"));
96       g_test_message ("%s file=%s other_file=%s\n",
97                       g_enum_get_value (G_ENUM_CLASS (class), event->event_type)->value_nick,
98                       event->file,
99                       event->other_file);
100       g_type_class_unref (class);
101     }
102 }
103 
104 /* a placeholder for temp file names we don't want to compare */
105 static const gchar DONT_CARE[] = "";
106 
107 static Environment
get_environment(GFileMonitor * monitor)108 get_environment (GFileMonitor *monitor)
109 {
110   if (g_str_equal (G_OBJECT_TYPE_NAME (monitor), "GInotifyFileMonitor"))
111     return INOTIFY;
112   if (g_str_equal (G_OBJECT_TYPE_NAME (monitor), "GKqueueFileMonitor"))
113     return KQUEUE;
114   return NONE;
115 }
116 
117 static void
check_expected_events(RecordedEvent * expected,gsize n_expected,GList * recorded,Environment env)118 check_expected_events (RecordedEvent *expected,
119                        gsize          n_expected,
120                        GList         *recorded,
121                        Environment    env)
122 {
123   gint i, li;
124   GList *l;
125 
126   for (i = 0, li = 0, l = recorded; i < n_expected && l != NULL;)
127     {
128       RecordedEvent *e1 = &expected[i];
129       RecordedEvent *e2 = l->data;
130       gboolean mismatch = TRUE;
131       gboolean l_extra_step = FALSE;
132 
133       do
134         {
135           gboolean ignore_other_file = FALSE;
136 
137           if (e1->step != e2->step)
138             break;
139 
140           /* Kqueue isn't good at detecting file renaming, so
141            * G_FILE_MONITOR_WATCH_MOVES is mostly useless there.  */
142           if (e1->event_type != e2->event_type && env & KQUEUE)
143             {
144               /* It is possible for kqueue file monitor to emit 'RENAMED' event,
145                * but most of the time it is reported as a 'DELETED' event and
146                * a 'CREATED' event. */
147               if (e1->event_type == G_FILE_MONITOR_EVENT_RENAMED)
148                 {
149                   RecordedEvent *e2_next;
150 
151                   if (l->next == NULL)
152                     break;
153                   e2_next = l->next->data;
154 
155                   if (e2->event_type != G_FILE_MONITOR_EVENT_DELETED)
156                     break;
157                   if (e2_next->event_type != G_FILE_MONITOR_EVENT_CREATED)
158                     break;
159 
160                   if (e1->step != e2_next->step)
161                     break;
162 
163                   if (e1->file != DONT_CARE &&
164                       (g_strcmp0 (e1->file, e2->file) != 0 ||
165                        e2->other_file != NULL))
166                     break;
167 
168                   if (e1->other_file != DONT_CARE &&
169                       (g_strcmp0 (e1->other_file, e2_next->file) != 0 ||
170                        e2_next->other_file != NULL))
171                     break;
172 
173                   l_extra_step = TRUE;
174                   mismatch = FALSE;
175                   break;
176                 }
177               /* Kqueue won't report 'MOVED_IN' and 'MOVED_OUT' events. We set
178                * 'ignore_other_file' here to let the following code know that
179                * 'other_file' may not match. */
180               else if (e1->event_type == G_FILE_MONITOR_EVENT_MOVED_IN)
181                 {
182                   if (e2->event_type != G_FILE_MONITOR_EVENT_CREATED)
183                     break;
184                   ignore_other_file = TRUE;
185                 }
186               else if (e1->event_type == G_FILE_MONITOR_EVENT_MOVED_OUT)
187                 {
188                   if (e2->event_type != G_FILE_MONITOR_EVENT_DELETED)
189                     break;
190                   ignore_other_file = TRUE;
191                 }
192               else
193                 break;
194             }
195 
196           if (e1->file != DONT_CARE &&
197               g_strcmp0 (e1->file, e2->file) != 0)
198             break;
199 
200           if (e1->other_file != DONT_CARE && !ignore_other_file &&
201               g_strcmp0 (e1->other_file, e2->other_file) != 0)
202             break;
203 
204           mismatch = FALSE;
205         }
206       while (0);
207 
208       if (mismatch)
209         {
210           /* Sometimes the emission of 'CHANGES_DONE_HINT' may be late because
211            * it depends on the ability of file monitor implementation to report
212            * 'CHANGES_DONE_HINT' itself. If the file monitor implementation
213            * doesn't report 'CHANGES_DONE_HINT' itself, it may be emitted by
214            * GLocalFileMonitor after a few seconds, which causes the event to
215            * mix with results from different steps. Since 'CHANGES_DONE_HINT'
216            * is just a hint, we don't require it to be reliable and we simply
217            * ignore unexpected 'CHANGES_DONE_HINT' events here. */
218           if (e1->event_type != G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT &&
219               e2->event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT)
220             {
221               g_test_message ("Event CHANGES_DONE_HINT ignored at "
222                               "expected index %d, recorded index %d", i, li);
223               li++, l = l->next;
224               continue;
225             }
226           /* If an event is marked as optional in the current environment and
227            * the event doesn't match, it means the expected event has lost. */
228           else if (env & e1->optional)
229             {
230               g_test_message ("Event %d at expected index %d skipped because "
231                               "it is marked as optional", e1->event_type, i);
232               i++;
233               continue;
234             }
235           /* Run above checks under g_assert_* again to provide more useful
236            * error messages. Print the expected and actual events first. */
237           else
238             {
239               GList *l;
240               gsize j;
241 
242               g_test_message ("Recorded events:");
243               for (l = recorded; l != NULL; l = l->next)
244                 output_event ((RecordedEvent *) l->data);
245 
246               g_test_message ("Expected events:");
247               for (j = 0; j < n_expected; j++)
248                 output_event (&expected[j]);
249 
250               g_assert_cmpint (e1->step, ==, e2->step);
251               g_assert_cmpint (e1->event_type, ==, e2->event_type);
252 
253               if (e1->file != DONT_CARE)
254                 g_assert_cmpstr (e1->file, ==, e2->file);
255 
256               if (e1->other_file != DONT_CARE)
257                 g_assert_cmpstr (e1->other_file, ==, e2->other_file);
258 
259               g_assert_not_reached ();
260             }
261         }
262 
263       i++, li++, l = l->next;
264       if (l_extra_step)
265         li++, l = l->next;
266     }
267 
268   g_assert_cmpint (i, ==, n_expected);
269   g_assert_cmpint (li, ==, g_list_length (recorded));
270 }
271 
272 static void
record_event(TestData * data,gint event_type,const gchar * file,const gchar * other_file,gint step)273 record_event (TestData    *data,
274               gint         event_type,
275               const gchar *file,
276               const gchar *other_file,
277               gint         step)
278 {
279   RecordedEvent *event;
280 
281   event = g_new0 (RecordedEvent, 1);
282   event->event_type = event_type;
283   event->file = g_strdup (file);
284   event->other_file = g_strdup (other_file);
285   event->step = step;
286 
287   data->events = g_list_append (data->events, event);
288 }
289 
290 static void
monitor_changed(GFileMonitor * monitor,GFile * file,GFile * other_file,GFileMonitorEvent event_type,gpointer user_data)291 monitor_changed (GFileMonitor      *monitor,
292                  GFile             *file,
293                  GFile             *other_file,
294                  GFileMonitorEvent  event_type,
295                  gpointer           user_data)
296 {
297   TestData *data = user_data;
298   gchar *basename, *other_base;
299 
300   basename = g_file_get_basename (file);
301   if (other_file)
302     other_base = g_file_get_basename (other_file);
303   else
304     other_base = NULL;
305 
306   record_event (data, event_type, basename, other_base, -1);
307 
308   g_free (basename);
309   g_free (other_base);
310 }
311 
312 static gboolean
atomic_replace_step(gpointer user_data)313 atomic_replace_step (gpointer user_data)
314 {
315   TestData *data = user_data;
316   GError *error = NULL;
317 
318   switch (data->step)
319     {
320     case 0:
321       record_event (data, -1, NULL, NULL, 0);
322       g_file_replace_contents (data->file, "step 0", 6, NULL, FALSE, G_FILE_CREATE_NONE, NULL, NULL, &error);
323       g_assert_no_error (error);
324       break;
325     case 1:
326       record_event (data, -1, NULL, NULL, 1);
327       g_file_replace_contents (data->file, "step 1", 6, NULL, FALSE, G_FILE_CREATE_NONE, NULL, NULL, &error);
328       g_assert_no_error (error);
329       break;
330     case 2:
331       record_event (data, -1, NULL, NULL, 2);
332       g_file_delete (data->file, NULL, NULL);
333       break;
334     case 3:
335       record_event (data, -1, NULL, NULL, 3);
336       g_main_loop_quit (data->loop);
337       return G_SOURCE_REMOVE;
338     }
339 
340   data->step++;
341 
342   return G_SOURCE_CONTINUE;
343 }
344 
345 /* this is the output we expect from the above steps */
346 static RecordedEvent atomic_replace_output[] = {
347   { -1, NULL, NULL, 0, NONE },
348   { G_FILE_MONITOR_EVENT_CREATED, "atomic_replace_file", NULL, -1, NONE },
349   { G_FILE_MONITOR_EVENT_CHANGED, "atomic_replace_file", NULL, -1, KQUEUE },
350   { G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, "atomic_replace_file", NULL, -1, KQUEUE },
351   { -1, NULL, NULL, 1, NONE },
352   { G_FILE_MONITOR_EVENT_RENAMED, (gchar*)DONT_CARE, "atomic_replace_file", -1, NONE },
353   { -1, NULL, NULL, 2, NONE },
354   { G_FILE_MONITOR_EVENT_DELETED, "atomic_replace_file", NULL, -1, NONE },
355   { -1, NULL, NULL, 3, NONE }
356 };
357 
358 static void
test_atomic_replace(Fixture * fixture,gconstpointer user_data)359 test_atomic_replace (Fixture       *fixture,
360                      gconstpointer  user_data)
361 {
362   GError *error = NULL;
363   TestData data;
364 
365   data.step = 0;
366   data.events = NULL;
367 
368   data.file = g_file_get_child (fixture->tmp_dir, "atomic_replace_file");
369   g_file_delete (data.file, NULL, NULL);
370 
371   data.monitor = g_file_monitor_file (data.file, G_FILE_MONITOR_WATCH_MOVES, NULL, &error);
372   g_assert_no_error (error);
373 
374   g_test_message ("Using GFileMonitor %s", G_OBJECT_TYPE_NAME (data.monitor));
375 
376   g_file_monitor_set_rate_limit (data.monitor, 200);
377   g_signal_connect (data.monitor, "changed", G_CALLBACK (monitor_changed), &data);
378 
379   data.loop = g_main_loop_new (NULL, TRUE);
380 
381   g_timeout_add (500, atomic_replace_step, &data);
382 
383   g_main_loop_run (data.loop);
384 
385   check_expected_events (atomic_replace_output,
386                          G_N_ELEMENTS (atomic_replace_output),
387                          data.events,
388                          get_environment (data.monitor));
389 
390   g_list_free_full (data.events, (GDestroyNotify)free_recorded_event);
391   g_main_loop_unref (data.loop);
392   g_object_unref (data.monitor);
393   g_object_unref (data.file);
394 }
395 
396 static gboolean
change_step(gpointer user_data)397 change_step (gpointer user_data)
398 {
399   TestData *data = user_data;
400   GOutputStream *stream;
401   GError *error = NULL;
402   guint32 mode = 0660;
403 
404   switch (data->step)
405     {
406     case 0:
407       record_event (data, -1, NULL, NULL, 0);
408       g_file_replace_contents (data->file, "step 0", 6, NULL, FALSE, G_FILE_CREATE_NONE, NULL, NULL, &error);
409       g_assert_no_error (error);
410       break;
411     case 1:
412       record_event (data, -1, NULL, NULL, 1);
413       stream = (GOutputStream *)g_file_append_to (data->file, G_FILE_CREATE_NONE, NULL, &error);
414       g_assert_no_error (error);
415       g_output_stream_write_all (stream, " step 1", 7, NULL, NULL, &error);
416       g_assert_no_error (error);
417       g_output_stream_close (stream, NULL, &error);
418       g_assert_no_error (error);
419       g_object_unref (stream);
420       break;
421     case 2:
422       record_event (data, -1, NULL, NULL, 2);
423       g_file_set_attribute (data->file,
424                             G_FILE_ATTRIBUTE_UNIX_MODE,
425                             G_FILE_ATTRIBUTE_TYPE_UINT32,
426                             &mode,
427                             G_FILE_QUERY_INFO_NONE,
428                             NULL,
429                             &error);
430       g_assert_no_error (error);
431       break;
432     case 3:
433       record_event (data, -1, NULL, NULL, 3);
434       g_file_delete (data->file, NULL, NULL);
435       break;
436     case 4:
437       record_event (data, -1, NULL, NULL, 4);
438       g_main_loop_quit (data->loop);
439       return G_SOURCE_REMOVE;
440     }
441 
442   data->step++;
443 
444   return G_SOURCE_CONTINUE;
445 }
446 
447 /* this is the output we expect from the above steps */
448 static RecordedEvent change_output[] = {
449   { -1, NULL, NULL, 0, NONE },
450   { G_FILE_MONITOR_EVENT_CREATED, "change_file", NULL, -1, NONE },
451   { G_FILE_MONITOR_EVENT_CHANGED, "change_file", NULL, -1, KQUEUE },
452   { G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, "change_file", NULL, -1, KQUEUE },
453   { -1, NULL, NULL, 1, NONE },
454   { G_FILE_MONITOR_EVENT_CHANGED, "change_file", NULL, -1, NONE },
455   { G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, "change_file", NULL, -1, NONE },
456   { -1, NULL, NULL, 2, NONE },
457   { G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED, "change_file", NULL, -1, NONE },
458   { -1, NULL, NULL, 3, NONE },
459   { G_FILE_MONITOR_EVENT_DELETED, "change_file", NULL, -1, NONE },
460   { -1, NULL, NULL, 4, NONE }
461 };
462 
463 static void
test_file_changes(Fixture * fixture,gconstpointer user_data)464 test_file_changes (Fixture       *fixture,
465                    gconstpointer  user_data)
466 {
467   GError *error = NULL;
468   TestData data;
469 
470   data.step = 0;
471   data.events = NULL;
472 
473   data.file = g_file_get_child (fixture->tmp_dir, "change_file");
474   g_file_delete (data.file, NULL, NULL);
475 
476   data.monitor = g_file_monitor_file (data.file, G_FILE_MONITOR_WATCH_MOVES, NULL, &error);
477   g_assert_no_error (error);
478 
479   g_test_message ("Using GFileMonitor %s", G_OBJECT_TYPE_NAME (data.monitor));
480 
481   g_file_monitor_set_rate_limit (data.monitor, 200);
482   g_signal_connect (data.monitor, "changed", G_CALLBACK (monitor_changed), &data);
483 
484   data.loop = g_main_loop_new (NULL, TRUE);
485 
486   g_timeout_add (500, change_step, &data);
487 
488   g_main_loop_run (data.loop);
489 
490   check_expected_events (change_output,
491                          G_N_ELEMENTS (change_output),
492                          data.events,
493                          get_environment (data.monitor));
494 
495   g_list_free_full (data.events, (GDestroyNotify)free_recorded_event);
496   g_main_loop_unref (data.loop);
497   g_object_unref (data.monitor);
498   g_object_unref (data.file);
499 }
500 
501 static gboolean
dir_step(gpointer user_data)502 dir_step (gpointer user_data)
503 {
504   TestData *data = user_data;
505   GFile *parent, *file, *file2;
506   GError *error = NULL;
507 
508   switch (data->step)
509     {
510     case 1:
511       record_event (data, -1, NULL, NULL, 1);
512       parent = g_file_get_parent (data->file);
513       file = g_file_get_child (parent, "dir_test_file");
514       g_file_replace_contents (file, "step 1", 6, NULL, FALSE, G_FILE_CREATE_NONE, NULL, NULL, &error);
515       g_assert_no_error (error);
516       g_object_unref (file);
517       g_object_unref (parent);
518       break;
519     case 2:
520       record_event (data, -1, NULL, NULL, 2);
521       parent = g_file_get_parent (data->file);
522       file = g_file_get_child (parent, "dir_test_file");
523       file2 = g_file_get_child (data->file, "dir_test_file");
524       g_file_move (file, file2, G_FILE_COPY_NONE, NULL, NULL, NULL, &error);
525       g_assert_no_error (error);
526       g_object_unref (file);
527       g_object_unref (file2);
528       g_object_unref (parent);
529       break;
530     case 3:
531       record_event (data, -1, NULL, NULL, 3);
532       file = g_file_get_child (data->file, "dir_test_file");
533       file2 = g_file_get_child (data->file, "dir_test_file2");
534       g_file_move (file, file2, G_FILE_COPY_NONE, NULL, NULL, NULL, &error);
535       g_assert_no_error (error);
536       g_object_unref (file);
537       g_object_unref (file2);
538       break;
539     case 4:
540       record_event (data, -1, NULL, NULL, 4);
541       parent = g_file_get_parent (data->file);
542       file = g_file_get_child (data->file, "dir_test_file2");
543       file2 = g_file_get_child (parent, "dir_test_file2");
544       g_file_move (file, file2, G_FILE_COPY_NONE, NULL, NULL, NULL, &error);
545       g_assert_no_error (error);
546       g_file_delete (file2, NULL, NULL);
547       g_object_unref (file);
548       g_object_unref (file2);
549       g_object_unref (parent);
550       break;
551     case 5:
552       record_event (data, -1, NULL, NULL, 5);
553       g_file_delete (data->file, NULL, NULL);
554       break;
555     case 6:
556       record_event (data, -1, NULL, NULL, 6);
557       g_main_loop_quit (data->loop);
558       return G_SOURCE_REMOVE;
559     }
560 
561   data->step++;
562 
563   return G_SOURCE_CONTINUE;
564 }
565 
566 /* this is the output we expect from the above steps */
567 static RecordedEvent dir_output[] = {
568   { -1, NULL, NULL, 1, NONE },
569   { -1, NULL, NULL, 2, NONE },
570   { G_FILE_MONITOR_EVENT_MOVED_IN, "dir_test_file", NULL, -1, NONE },
571   { -1, NULL, NULL, 3, NONE },
572   { G_FILE_MONITOR_EVENT_RENAMED, "dir_test_file", "dir_test_file2", -1, NONE },
573   { -1, NULL, NULL, 4, NONE },
574   { G_FILE_MONITOR_EVENT_MOVED_OUT, "dir_test_file2", NULL, -1, NONE },
575   { -1, NULL, NULL, 5, NONE },
576   { G_FILE_MONITOR_EVENT_DELETED, "dir_monitor_test", NULL, -1, NONE },
577   { -1, NULL, NULL, 6, NONE }
578 };
579 
580 static void
test_dir_monitor(Fixture * fixture,gconstpointer user_data)581 test_dir_monitor (Fixture       *fixture,
582                   gconstpointer  user_data)
583 {
584   GError *error = NULL;
585   TestData data;
586 
587   data.step = 0;
588   data.events = NULL;
589 
590   data.file = g_file_get_child (fixture->tmp_dir, "dir_monitor_test");
591   g_file_delete (data.file, NULL, NULL);
592   g_file_make_directory (data.file, NULL, &error);
593 
594   data.monitor = g_file_monitor_directory (data.file, G_FILE_MONITOR_WATCH_MOVES, NULL, &error);
595   g_assert_no_error (error);
596 
597   g_test_message ("Using GFileMonitor %s", G_OBJECT_TYPE_NAME (data.monitor));
598 
599   g_file_monitor_set_rate_limit (data.monitor, 200);
600   g_signal_connect (data.monitor, "changed", G_CALLBACK (monitor_changed), &data);
601 
602   data.loop = g_main_loop_new (NULL, TRUE);
603 
604   g_timeout_add (500, dir_step, &data);
605 
606   g_main_loop_run (data.loop);
607 
608   check_expected_events (dir_output,
609                          G_N_ELEMENTS (dir_output),
610                          data.events,
611                          get_environment (data.monitor));
612 
613   g_list_free_full (data.events, (GDestroyNotify)free_recorded_event);
614   g_main_loop_unref (data.loop);
615   g_object_unref (data.monitor);
616   g_object_unref (data.file);
617 }
618 
619 static gboolean
nodir_step(gpointer user_data)620 nodir_step (gpointer user_data)
621 {
622   TestData *data = user_data;
623   GFile *parent;
624   GError *error = NULL;
625 
626   switch (data->step)
627     {
628     case 0:
629       record_event (data, -1, NULL, NULL, 0);
630       parent = g_file_get_parent (data->file);
631       g_file_make_directory (parent, NULL, &error);
632       g_assert_no_error (error);
633       g_object_unref (parent);
634       break;
635     case 1:
636       record_event (data, -1, NULL, NULL, 1);
637       g_file_replace_contents (data->file, "step 1", 6, NULL, FALSE, G_FILE_CREATE_NONE, NULL, NULL, &error);
638       g_assert_no_error (error);
639       break;
640     case 2:
641       record_event (data, -1, NULL, NULL, 2);
642       g_file_delete (data->file, NULL, &error);
643       g_assert_no_error (error);
644       break;
645     case 3:
646       record_event (data, -1, NULL, NULL, 3);
647       parent = g_file_get_parent (data->file);
648       g_file_delete (parent, NULL, &error);
649       g_assert_no_error (error);
650       g_object_unref (parent);
651       break;
652     case 4:
653       record_event (data, -1, NULL, NULL, 4);
654       g_main_loop_quit (data->loop);
655       return G_SOURCE_REMOVE;
656     }
657 
658   data->step++;
659 
660   return G_SOURCE_CONTINUE;
661 }
662 
663 static RecordedEvent nodir_output[] = {
664   { -1, NULL, NULL, 0, NONE },
665   { G_FILE_MONITOR_EVENT_CREATED, "nosuchfile", NULL, -1, KQUEUE },
666   { G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, "nosuchfile", NULL, -1, KQUEUE },
667   { -1, NULL, NULL, 1, NONE },
668   { G_FILE_MONITOR_EVENT_CREATED, "nosuchfile", NULL, -1, NONE },
669   { G_FILE_MONITOR_EVENT_CHANGED, "nosuchfile", NULL, -1, KQUEUE },
670   { G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, "nosuchfile", NULL, -1, KQUEUE },
671   { -1, NULL, NULL, 2, NONE },
672   { G_FILE_MONITOR_EVENT_DELETED, "nosuchfile", NULL, -1, NONE },
673   { -1, NULL, NULL, 3, NONE },
674   { -1, NULL, NULL, 4, NONE }
675 };
676 
677 static void
test_dir_non_existent(Fixture * fixture,gconstpointer user_data)678 test_dir_non_existent (Fixture       *fixture,
679                        gconstpointer  user_data)
680 {
681   TestData data;
682   GError *error = NULL;
683 
684   data.step = 0;
685   data.events = NULL;
686 
687   data.file = g_file_get_child (fixture->tmp_dir, "nosuchdir/nosuchfile");
688   data.monitor = g_file_monitor_file (data.file, G_FILE_MONITOR_WATCH_MOVES, NULL, &error);
689   g_assert_no_error (error);
690 
691   g_test_message ("Using GFileMonitor %s", G_OBJECT_TYPE_NAME (data.monitor));
692 
693   g_file_monitor_set_rate_limit (data.monitor, 200);
694   g_signal_connect (data.monitor, "changed", G_CALLBACK (monitor_changed), &data);
695 
696   data.loop = g_main_loop_new (NULL, TRUE);
697 
698   /* we need a long timeout here, since the inotify implementation only scans
699    * for missing files every 4 seconds.
700    */
701   g_timeout_add (5000, nodir_step, &data);
702 
703   g_main_loop_run (data.loop);
704 
705   check_expected_events (nodir_output,
706                          G_N_ELEMENTS (nodir_output),
707                          data.events,
708                          get_environment (data.monitor));
709 
710   g_list_free_full (data.events, (GDestroyNotify)free_recorded_event);
711   g_main_loop_unref (data.loop);
712   g_object_unref (data.monitor);
713   g_object_unref (data.file);
714 }
715 
716 static gboolean
cross_dir_step(gpointer user_data)717 cross_dir_step (gpointer user_data)
718 {
719   TestData *data = user_data;
720   GFile *file, *file2;
721   GError *error = NULL;
722 
723   switch (data[0].step)
724     {
725     case 0:
726       record_event (&data[0], -1, NULL, NULL, 0);
727       record_event (&data[1], -1, NULL, NULL, 0);
728       file = g_file_get_child (data[1].file, "a");
729       g_file_replace_contents (file, "step 0", 6, NULL, FALSE, G_FILE_CREATE_NONE, NULL, NULL, &error);
730       g_assert_no_error (error);
731       g_object_unref (file);
732       break;
733     case 1:
734       record_event (&data[0], -1, NULL, NULL, 1);
735       record_event (&data[1], -1, NULL, NULL, 1);
736       file = g_file_get_child (data[1].file, "a");
737       file2 = g_file_get_child (data[0].file, "a");
738       g_file_move (file, file2, 0, NULL, NULL, NULL, &error);
739       g_assert_no_error (error);
740       g_object_unref (file);
741       g_object_unref (file2);
742       break;
743     case 2:
744       record_event (&data[0], -1, NULL, NULL, 2);
745       record_event (&data[1], -1, NULL, NULL, 2);
746       file2 = g_file_get_child (data[0].file, "a");
747       g_file_delete (file2, NULL, NULL);
748       g_file_delete (data[0].file, NULL, NULL);
749       g_file_delete (data[1].file, NULL, NULL);
750       g_object_unref (file2);
751       break;
752     case 3:
753       record_event (&data[0], -1, NULL, NULL, 3);
754       record_event (&data[1], -1, NULL, NULL, 3);
755       g_main_loop_quit (data->loop);
756       return G_SOURCE_REMOVE;
757     }
758 
759   data->step++;
760 
761   return G_SOURCE_CONTINUE;
762 }
763 
764 static RecordedEvent cross_dir_a_output[] = {
765   { -1, NULL, NULL, 0, NONE },
766   { -1, NULL, NULL, 1, NONE },
767   { G_FILE_MONITOR_EVENT_CREATED, "a", NULL, -1, NONE },
768   { G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, "a", NULL, -1, KQUEUE },
769   { -1, NULL, NULL, 2, NONE },
770   { G_FILE_MONITOR_EVENT_DELETED, "a", NULL, -1, NONE },
771   { G_FILE_MONITOR_EVENT_DELETED, "cross_dir_a", NULL, -1, NONE },
772   { -1, NULL, NULL, 3, NONE },
773 };
774 
775 static RecordedEvent cross_dir_b_output[] = {
776   { -1, NULL, NULL, 0, NONE },
777   { G_FILE_MONITOR_EVENT_CREATED, "a", NULL, -1, NONE },
778   { G_FILE_MONITOR_EVENT_CHANGED, "a", NULL, -1, KQUEUE },
779   { G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, "a", NULL, -1, KQUEUE },
780   { -1, NULL, NULL, 1, NONE },
781   { G_FILE_MONITOR_EVENT_MOVED_OUT, "a", "a", -1, NONE },
782   { -1, NULL, NULL, 2, NONE },
783   { G_FILE_MONITOR_EVENT_DELETED, "cross_dir_b", NULL, -1, NONE },
784   { -1, NULL, NULL, 3, NONE },
785 };
786 static void
test_cross_dir_moves(Fixture * fixture,gconstpointer user_data)787 test_cross_dir_moves (Fixture       *fixture,
788                       gconstpointer  user_data)
789 {
790   GError *error = NULL;
791   TestData data[2];
792 
793   data[0].step = 0;
794   data[0].events = NULL;
795 
796   data[0].file = g_file_get_child (fixture->tmp_dir, "cross_dir_a");
797   g_file_delete (data[0].file, NULL, NULL);
798   g_file_make_directory (data[0].file, NULL, &error);
799 
800   data[0].monitor = g_file_monitor_directory (data[0].file, 0, NULL, &error);
801   g_assert_no_error (error);
802 
803   g_test_message ("Using GFileMonitor 0 %s", G_OBJECT_TYPE_NAME (data[0].monitor));
804 
805   g_file_monitor_set_rate_limit (data[0].monitor, 200);
806   g_signal_connect (data[0].monitor, "changed", G_CALLBACK (monitor_changed), &data[0]);
807 
808   data[1].step = 0;
809   data[1].events = NULL;
810 
811   data[1].file = g_file_get_child (fixture->tmp_dir, "cross_dir_b");
812   g_file_delete (data[1].file, NULL, NULL);
813   g_file_make_directory (data[1].file, NULL, &error);
814 
815   data[1].monitor = g_file_monitor_directory (data[1].file, G_FILE_MONITOR_WATCH_MOVES, NULL, &error);
816   g_assert_no_error (error);
817 
818   g_test_message ("Using GFileMonitor 1 %s", G_OBJECT_TYPE_NAME (data[1].monitor));
819 
820   g_file_monitor_set_rate_limit (data[1].monitor, 200);
821   g_signal_connect (data[1].monitor, "changed", G_CALLBACK (monitor_changed), &data[1]);
822 
823   data[0].loop = g_main_loop_new (NULL, TRUE);
824 
825   g_timeout_add (500, cross_dir_step, data);
826 
827   g_main_loop_run (data[0].loop);
828 
829   check_expected_events (cross_dir_a_output,
830                          G_N_ELEMENTS (cross_dir_a_output),
831                          data[0].events,
832                          get_environment (data[0].monitor));
833   check_expected_events (cross_dir_b_output,
834                          G_N_ELEMENTS (cross_dir_b_output),
835                          data[1].events,
836                          get_environment (data[1].monitor));
837 
838   g_list_free_full (data[0].events, (GDestroyNotify)free_recorded_event);
839   g_main_loop_unref (data[0].loop);
840   g_object_unref (data[0].monitor);
841   g_object_unref (data[0].file);
842 
843   g_list_free_full (data[1].events, (GDestroyNotify)free_recorded_event);
844   g_object_unref (data[1].monitor);
845   g_object_unref (data[1].file);
846 }
847 
848 static gboolean
file_hard_links_step(gpointer user_data)849 file_hard_links_step (gpointer user_data)
850 {
851   gboolean retval = G_SOURCE_CONTINUE;
852   TestData *data = user_data;
853   GError *error = NULL;
854 
855   gchar *filename = g_file_get_path (data->file);
856   gchar *hard_link_name = g_strdup_printf ("%s2", filename);
857   GFile *hard_link_file = g_file_new_for_path (hard_link_name);
858 
859   switch (data->step)
860     {
861     case 0:
862       record_event (data, -1, NULL, NULL, 0);
863       g_output_stream_write_all (G_OUTPUT_STREAM (data->output_stream),
864                                  "hello, step 0", 13, NULL, NULL, &error);
865       g_assert_no_error (error);
866       g_output_stream_close (G_OUTPUT_STREAM (data->output_stream), NULL, &error);
867       g_assert_no_error (error);
868       break;
869     case 1:
870       record_event (data, -1, NULL, NULL, 1);
871       g_file_replace_contents (data->file, "step 1", 6, NULL, FALSE,
872                                G_FILE_CREATE_NONE, NULL, NULL, &error);
873       g_assert_no_error (error);
874       break;
875     case 2:
876       record_event (data, -1, NULL, NULL, 2);
877 #ifdef HAVE_LINK
878       if (link (filename, hard_link_name) < 0)
879         {
880           g_error ("link(%s, %s) failed: %s", filename, hard_link_name, g_strerror (errno));
881         }
882 #endif  /* HAVE_LINK */
883       break;
884     case 3:
885       record_event (data, -1, NULL, NULL, 3);
886 #ifdef HAVE_LINK
887       {
888         GOutputStream *hard_link_stream = NULL;
889 
890         /* Deliberately don’t do an atomic swap on the hard-linked file. */
891         hard_link_stream = G_OUTPUT_STREAM (g_file_append_to (hard_link_file,
892                                                               G_FILE_CREATE_NONE,
893                                                               NULL, &error));
894         g_assert_no_error (error);
895         g_output_stream_write_all (hard_link_stream, " step 3", 7, NULL, NULL, &error);
896         g_assert_no_error (error);
897         g_output_stream_close (hard_link_stream, NULL, &error);
898         g_assert_no_error (error);
899         g_object_unref (hard_link_stream);
900       }
901 #endif  /* HAVE_LINK */
902       break;
903     case 4:
904       record_event (data, -1, NULL, NULL, 4);
905       g_file_delete (data->file, NULL, &error);
906       g_assert_no_error (error);
907       break;
908     case 5:
909       record_event (data, -1, NULL, NULL, 5);
910 #ifdef HAVE_LINK
911       g_file_delete (hard_link_file, NULL, &error);
912       g_assert_no_error (error);
913 #endif  /* HAVE_LINK */
914       break;
915     case 6:
916       record_event (data, -1, NULL, NULL, 6);
917       g_main_loop_quit (data->loop);
918       retval = G_SOURCE_REMOVE;
919       break;
920     }
921 
922   if (retval != G_SOURCE_REMOVE)
923     data->step++;
924 
925   g_object_unref (hard_link_file);
926   g_free (hard_link_name);
927   g_free (filename);
928 
929   return retval;
930 }
931 
932 static RecordedEvent file_hard_links_output[] = {
933   { -1, NULL, NULL, 0, NONE },
934   { G_FILE_MONITOR_EVENT_CHANGED, "testfilemonitor.db", NULL, -1, NONE },
935   { G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, "testfilemonitor.db", NULL, -1, NONE },
936   { -1, NULL, NULL, 1, NONE },
937   { G_FILE_MONITOR_EVENT_RENAMED, (gchar*)DONT_CARE /* .goutputstream-XXXXXX */, "testfilemonitor.db", -1, NONE },
938   { -1, NULL, NULL, 2, NONE },
939   { -1, NULL, NULL, 3, NONE },
940   /* Kqueue is based on file descriptors. You can get events from all hard
941    * links by just monitoring one open file descriptor, and it is not possible
942    * to know whether it is done on the file name we use to open the file. Since
943    * the hard link count of 'testfilemonitor.db' is 2, it is expected to see
944    * two 'DELETED' events reported here. You have to call 'unlink' twice on
945    * different file names to remove 'testfilemonitor.db' from the file system,
946    * and each 'unlink' call generates a 'DELETED' event. */
947   { G_FILE_MONITOR_EVENT_CHANGED, "testfilemonitor.db", NULL, -1, INOTIFY },
948   { -1, NULL, NULL, 4, NONE },
949   { G_FILE_MONITOR_EVENT_DELETED, "testfilemonitor.db", NULL, -1, NONE },
950   { -1, NULL, NULL, 5, NONE },
951   { G_FILE_MONITOR_EVENT_DELETED, "testfilemonitor.db", NULL, -1, INOTIFY },
952   { -1, NULL, NULL, 6, NONE },
953 };
954 
955 static void
test_file_hard_links(Fixture * fixture,gconstpointer user_data)956 test_file_hard_links (Fixture       *fixture,
957                       gconstpointer  user_data)
958 {
959 #ifdef _GLIB_ADDRESS_SANITIZER
960   g_test_incomplete ("FIXME: Leaks an inotify data structure, see glib#2311");
961   (void) file_hard_links_output;
962   (void) file_hard_links_step;
963 #else
964   GError *error = NULL;
965   TestData data;
966 
967   g_test_bug ("755721");
968 
969 #ifdef HAVE_LINK
970   g_test_message ("Running with hard link tests");
971 #else  /* if !HAVE_LINK */
972   g_test_message ("Running without hard link tests");
973 #endif  /* !HAVE_LINK */
974 
975   data.step = 0;
976   data.events = NULL;
977 
978   /* Create a file which exists and is not a directory. */
979   data.file = g_file_get_child (fixture->tmp_dir, "testfilemonitor.db");
980   data.output_stream = g_file_replace (data.file, NULL, FALSE,
981                                        G_FILE_CREATE_NONE, NULL, &error);
982   g_assert_no_error (error);
983 
984   /* Monitor it. Creating the monitor should not crash (bug #755721). */
985   data.monitor = g_file_monitor_file (data.file,
986                                       G_FILE_MONITOR_WATCH_MOUNTS |
987                                       G_FILE_MONITOR_WATCH_MOVES |
988                                       G_FILE_MONITOR_WATCH_HARD_LINKS,
989                                       NULL,
990                                       &error);
991   g_assert_no_error (error);
992   g_assert_nonnull (data.monitor);
993 
994   g_test_message ("Using GFileMonitor %s", G_OBJECT_TYPE_NAME (data.monitor));
995 
996   /* Change the file a bit. */
997   g_file_monitor_set_rate_limit (data.monitor, 200);
998   g_signal_connect (data.monitor, "changed", (GCallback) monitor_changed, &data);
999 
1000   data.loop = g_main_loop_new (NULL, TRUE);
1001   g_timeout_add (500, file_hard_links_step, &data);
1002   g_main_loop_run (data.loop);
1003 
1004   check_expected_events (file_hard_links_output,
1005                          G_N_ELEMENTS (file_hard_links_output),
1006                          data.events,
1007                          get_environment (data.monitor));
1008 
1009   g_list_free_full (data.events, (GDestroyNotify) free_recorded_event);
1010   g_main_loop_unref (data.loop);
1011   g_object_unref (data.monitor);
1012   g_object_unref (data.file);
1013   g_object_unref (data.output_stream);
1014 #endif
1015 }
1016 
1017 int
main(int argc,char * argv[])1018 main (int argc, char *argv[])
1019 {
1020   g_test_init (&argc, &argv, NULL);
1021 
1022   g_test_bug_base ("https://bugzilla.gnome.org/show_bug.cgi?id=");
1023 
1024   g_test_add ("/monitor/atomic-replace", Fixture, NULL, setup, test_atomic_replace, teardown);
1025   g_test_add ("/monitor/file-changes", Fixture, NULL, setup, test_file_changes, teardown);
1026   g_test_add ("/monitor/dir-monitor", Fixture, NULL, setup, test_dir_monitor, teardown);
1027   g_test_add ("/monitor/dir-not-existent", Fixture, NULL, setup, test_dir_non_existent, teardown);
1028   g_test_add ("/monitor/cross-dir-moves", Fixture, NULL, setup, test_cross_dir_moves, teardown);
1029   g_test_add ("/monitor/file/hard-links", Fixture, NULL, setup, test_file_hard_links, teardown);
1030 
1031   return g_test_run ();
1032 }
1033