Fix a failing testcase
[platform/upstream/glib.git] / gio / kqueue / kqueue-helper.c
1 /*******************************************************************************
2   Copyright (c) 2011, 2012 Dmitry Matveev <me@dmitrymatveev.co.uk>
3
4   Permission is hereby granted, free of charge, to any person obtaining a copy
5   of this software and associated documentation files (the "Software"), to deal
6   in the Software without restriction, including without limitation the rights
7   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8   copies of the Software, and to permit persons to whom the Software is
9   furnished to do so, subject to the following conditions:
10
11   The above copyright notice and this permission notice shall be included in
12   all copies or substantial portions of the Software.
13
14   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20   THE SOFTWARE.
21 *******************************************************************************/
22
23 #include "config.h"
24 #include <sys/types.h>
25 #include <sys/event.h>
26 #include <sys/time.h>
27 #include <sys/socket.h>
28 #include <gio/glocalfile.h>
29 #include <gio/gfile.h>
30 #include <fcntl.h>
31 #include <unistd.h>
32 #include <string.h>
33 #include <errno.h>
34 #include <pthread.h>
35 #include "kqueue-helper.h"
36 #include "kqueue-utils.h"
37 #include "kqueue-thread.h"
38 #include "kqueue-missing.h"
39 #include "kqueue-exclusions.h"
40
41 #include "gkqueuedirectorymonitor.h"
42
43 static gboolean kh_debug_enabled = FALSE;
44 #define KH_W if (kh_debug_enabled) g_warning
45
46 static GHashTable *subs_hash_table = NULL;
47 G_LOCK_DEFINE_STATIC (hash_lock);
48
49 static int kqueue_descriptor = -1;
50 static int kqueue_socket_pair[] = {-1, -1};
51 static pthread_t kqueue_thread;
52
53
54 void _kh_file_appeared_cb (kqueue_sub *sub);
55
56 /**
57  * accessor function for kqueue_descriptor
58  **/
59 int
60 get_kqueue_descriptor()
61 {
62   return kqueue_descriptor;
63 }
64
65 /**
66  * convert_kqueue_events_to_gio:
67  * @flags: a set of kqueue filter flags
68  * @done: a pointer to #gboolean indicating that the
69  *      conversion has been done (out)
70  *
71  * Translates kqueue filter flags into GIO event flags.
72  *
73  * Returns: a #GFileMonitorEvent
74  **/
75 static GFileMonitorEvent
76 convert_kqueue_events_to_gio (uint32_t flags, gboolean *done)
77 {
78   g_assert (done != NULL);
79   *done = FALSE;
80
81   /* TODO: The following notifications should be emulated, if possible:
82    * - G_FILE_MONITOR_EVENT_PRE_UNMOUNT
83    */
84   if (flags & NOTE_DELETE)
85     {    
86       *done = TRUE;
87       return G_FILE_MONITOR_EVENT_DELETED;
88     }
89   if (flags & NOTE_ATTRIB)
90     {
91       *done = TRUE;
92       return G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED;
93     }
94   if (flags & (NOTE_WRITE | NOTE_EXTEND))
95     {
96       *done = TRUE;
97       return G_FILE_MONITOR_EVENT_CHANGED;
98     }
99   if (flags & NOTE_RENAME)
100     {
101       *done = TRUE;
102       return G_FILE_MONITOR_EVENT_MOVED;
103     }
104   if (flags & NOTE_REVOKE)
105     {
106       *done = TRUE;
107       return G_FILE_MONITOR_EVENT_UNMOUNTED;
108     }
109
110   /* done is FALSE */
111   return 0;
112 }
113
114 typedef struct {
115   kqueue_sub *sub;
116   GFileMonitor *monitor;  
117 } handle_ctx;
118
119 /**
120  * handle_created: 
121  * @udata: a pointer to user data (#handle_context).
122  * @path: file name of a new file.
123  * @inode: inode number of a new file.
124  *
125  * A callback function for the directory diff calculation routine,
126  * produces G_FILE_MONITOR_EVENT_CREATED event for a created file.
127  **/
128 static void
129 handle_created (void *udata, const char *path, ino_t inode)
130 {
131   handle_ctx *ctx = NULL;
132   GFile *file = NULL;
133   gchar *fpath = NULL;
134
135   (void) inode;
136   ctx = (handle_ctx *) udata;
137   g_assert (udata != NULL);
138   g_assert (ctx->sub != NULL);
139   g_assert (ctx->monitor != NULL);
140
141   fpath = _ku_path_concat (ctx->sub->filename, path);
142   if (fpath == NULL)
143     {
144       KH_W ("Failed to allocate a string for a new event");
145       return;
146     }
147
148   file = g_file_new_for_path (fpath);
149   g_file_monitor_emit_event (ctx->monitor,
150                              file,
151                              NULL,
152                              G_FILE_MONITOR_EVENT_CREATED);
153   g_free (fpath);
154   g_object_unref (file);
155 }
156
157 /**
158  * handle_deleted:
159  * @udata: a pointer to user data (#handle_context).
160  * @path: file name of the removed file.
161  * @inode: inode number of the removed file.
162  *
163  * A callback function for the directory diff calculation routine,
164  * produces G_FILE_MONITOR_EVENT_DELETED event for a deleted file.
165  **/
166 static void
167 handle_deleted (void *udata, const char *path, ino_t inode)
168 {
169   handle_ctx *ctx = NULL;
170   GFile *file = NULL;
171   gchar *fpath = NULL;
172
173   (void) inode;
174   ctx = (handle_ctx *) udata;
175   g_assert (udata != NULL);
176   g_assert (ctx->sub != NULL);
177   g_assert (ctx->monitor != NULL);
178
179   fpath = _ku_path_concat (ctx->sub->filename, path);
180   if (fpath == NULL)
181     {
182       KH_W ("Failed to allocate a string for a new event");
183       return;
184     }
185
186   file = g_file_new_for_path (fpath);
187   g_file_monitor_emit_event (ctx->monitor,
188                              file,
189                              NULL,
190                              G_FILE_MONITOR_EVENT_DELETED);
191   g_free (fpath);
192   g_object_unref (file);
193 }
194
195 /**
196  * handle_moved:
197  * @udata: a pointer to user data (#handle_context).
198  * @from_path: file name of the source file.
199  * @from_inode: inode number of the source file.
200  * @to_path: file name of the replaced file.
201  * @to_inode: inode number of the replaced file.
202  *
203  * A callback function for the directory diff calculation routine,
204  * produces G_FILE_MONITOR_EVENT_MOVED event on a move.
205  **/
206 static void
207 handle_moved (void       *udata,
208               const char *from_path,
209               ino_t       from_inode,
210               const char *to_path,
211               ino_t       to_inode)
212 {
213   handle_ctx *ctx = NULL;
214   GFile *file = NULL;
215   GFile *other = NULL;
216   gchar *path = NULL;
217   gchar *npath = NULL;
218
219   (void) from_inode;
220   (void) to_inode;
221
222   ctx = (handle_ctx *) udata;
223   g_assert (udata != NULL);
224   g_assert (ctx->sub != NULL);
225   g_assert (ctx->monitor != NULL);
226
227
228   path = _ku_path_concat (ctx->sub->filename, from_path);
229   npath = _ku_path_concat (ctx->sub->filename, to_path);
230   if (path == NULL || npath == NULL)
231     {
232       KH_W ("Failed to allocate strings for event");
233       return;
234     }
235
236   file = g_file_new_for_path (path);
237   other = g_file_new_for_path (npath);
238
239   if (ctx->sub->pair_moves)
240     {
241       g_file_monitor_emit_event (ctx->monitor,
242                                  file,
243                                  other,
244                                  G_FILE_MONITOR_EVENT_MOVED);
245     }
246   else
247     {
248       g_file_monitor_emit_event (ctx->monitor,
249                                  file,
250                                  NULL,
251                                  G_FILE_MONITOR_EVENT_DELETED);
252       g_file_monitor_emit_event (ctx->monitor,
253                                  other,
254                                  NULL,
255                                  G_FILE_MONITOR_EVENT_CREATED);
256     }
257
258   g_free (path);
259   g_free (npath);
260
261   g_object_unref (file);
262   g_object_unref (other);
263 }
264
265
266 /**
267  * handle_overwritten:
268  * @data: a pointer to user data (#handle_context).
269  * @path: file name of the overwritten file.
270  * @node: inode number of the overwritten file.
271  *
272  * A callback function for the directory diff calculation routine,
273  * produces G_FILE_MONITOR_EVENT_DELETED/CREATED event pair when
274  * an overwrite occurs in the directory (see dep-list for details).
275  **/
276 static void
277 handle_overwritten (void *udata, const char *path, ino_t inode)
278 {
279   handle_ctx *ctx = NULL;
280   GFile *file = NULL;
281   gchar *fpath = NULL;
282
283   (void) inode;
284   ctx = (handle_ctx *) udata;
285   g_assert (udata != NULL);
286   g_assert (ctx->sub != NULL);
287   g_assert (ctx->monitor != NULL);
288
289   fpath = _ku_path_concat (ctx->sub->filename, path);
290   if (fpath == NULL)
291     {
292       KH_W ("Failed to allocate a string for a new event");
293       return;
294     }
295
296   file = g_file_new_for_path (fpath);
297   g_file_monitor_emit_event (ctx->monitor,
298                              file,
299                              NULL,
300                              G_FILE_MONITOR_EVENT_DELETED);
301   g_file_monitor_emit_event (ctx->monitor,
302                              file,
303                              NULL,
304                              G_FILE_MONITOR_EVENT_CREATED);
305
306   g_free (fpath);
307   g_object_unref (file);
308 }
309
310 static const traverse_cbs cbs = {
311   handle_created,
312   handle_deleted,
313   handle_moved,
314   handle_overwritten,
315   handle_moved,
316   NULL, /* many added */
317   NULL, /* many removed */
318   NULL, /* names updated */
319 };
320
321
322 void
323 _kh_dir_diff (kqueue_sub *sub, GFileMonitor *monitor)
324 {
325   dep_list *was;
326   handle_ctx ctx;
327
328   g_assert (sub != NULL);
329   g_assert (monitor != NULL);
330
331   memset (&ctx, 0, sizeof (handle_ctx));
332   ctx.sub = sub;
333   ctx.monitor = monitor;
334
335   was = sub->deps;
336   sub->deps = dl_listing (sub->filename);
337  
338   dl_calculate (was, sub->deps, &cbs, &ctx);
339
340   dl_free (was);
341 }
342
343
344 /**
345  * process_kqueue_notifications:
346  * @gioc: unused.
347  * @cond: unused.
348  * @data: unused.
349  *
350  * Processes notifications, coming from the kqueue thread.
351  *
352  * Reads notifications from the command file descriptor, emits the
353  * "changed" event on the appropriate monitor.
354  *
355  * A typical GIO Channel callback function.
356  *
357  * Returns: %TRUE
358  **/
359 static gboolean
360 process_kqueue_notifications (GIOChannel   *gioc,
361                               GIOCondition  cond,
362                               gpointer      data)
363 {
364   struct kqueue_notification n;
365   kqueue_sub *sub = NULL;
366   GFileMonitor *monitor = NULL;
367   GFileMonitorEvent mask = 0;
368   
369   g_assert (kqueue_socket_pair[0] != -1);
370   if (!_ku_read (kqueue_socket_pair[0], &n, sizeof (struct kqueue_notification)))
371     {
372       KH_W ("Failed to read a kqueue notification, error %d", errno);
373       return TRUE;
374     }
375
376   G_LOCK (hash_lock);
377   sub = (kqueue_sub *) g_hash_table_lookup (subs_hash_table, GINT_TO_POINTER (n.fd));
378   G_UNLOCK (hash_lock);
379
380   if (sub == NULL)
381     {
382       KH_W ("Got a notification for a deleted or non-existing subscription %d",
383              n.fd);
384       return TRUE;
385     }
386
387   monitor = G_FILE_MONITOR (sub->user_data);
388   g_assert (monitor != NULL);
389
390   if (n.flags & (NOTE_DELETE | NOTE_REVOKE))
391     {
392       if (sub->deps)
393         {
394           dl_free (sub->deps);
395           sub->deps = NULL;  
396         }  
397       _km_add_missing (sub);
398
399       if (!(n.flags & NOTE_REVOKE))
400         {
401           /* Note that NOTE_REVOKE is issued by the kqueue thread
402            * on EV_ERROR kevent. In this case, a file descriptor is
403            * already closed from the kqueue thread, no need to close
404            * it manually */ 
405           _kh_cancel_sub (sub);
406         }
407     }
408
409   if (sub->is_dir && n.flags & (NOTE_WRITE | NOTE_EXTEND))
410     {
411       _kh_dir_diff (sub, monitor);  
412       n.flags &= ~(NOTE_WRITE | NOTE_EXTEND);
413     }
414
415   if (n.flags)
416     {
417       gboolean done = FALSE;
418       mask = convert_kqueue_events_to_gio (n.flags, &done);
419       if (done == TRUE)
420         {
421           GFile *file = g_file_new_for_path (sub->filename);
422           g_file_monitor_emit_event (monitor, file, NULL, mask);
423           g_object_unref (file);
424         }
425     }
426
427   return TRUE;
428 }
429
430
431 /**
432  * _kh_startup_impl:
433  * @unused: unused
434  *
435  * Kqueue backend startup code. Should be called only once.
436  *
437  * Returns: %TRUE on success, %FALSE otherwise.
438  **/
439 static gpointer
440 _kh_startup_impl (gpointer unused)
441 {
442   GIOChannel *channel = NULL;
443   gboolean result = FALSE;
444
445   kqueue_descriptor = kqueue ();
446   result = (kqueue_descriptor != -1);
447   if (!result)
448     {
449       KH_W ("Failed to initialize kqueue\n!");
450       return GINT_TO_POINTER (FALSE);
451     }
452
453   result = socketpair (AF_UNIX, SOCK_STREAM, 0, kqueue_socket_pair);
454   if (result != 0)
455     {
456       KH_W ("Failed to create socket pair\n!");
457       return GINT_TO_POINTER (FALSE) ;
458     }
459
460   result = pthread_create (&kqueue_thread,
461                            NULL,
462                            _kqueue_thread_func,
463                            &kqueue_socket_pair[1]);
464   if (result != 0)
465     {
466       KH_W ("Failed to run kqueue thread\n!");
467       return GINT_TO_POINTER (FALSE);
468     }
469
470   _km_init (_kh_file_appeared_cb);
471
472   channel = g_io_channel_unix_new (kqueue_socket_pair[0]);
473   g_io_add_watch (channel, G_IO_IN, process_kqueue_notifications, NULL);
474
475   subs_hash_table = g_hash_table_new (g_direct_hash, g_direct_equal);
476
477   KH_W ("started gio kqueue backend\n");
478   return GINT_TO_POINTER (TRUE);
479 }
480
481
482 /**
483  * _kh_startup:
484  * Kqueue backend initialization.
485  *
486  * Returns: %TRUE on success, %FALSE otherwise.
487  **/
488 gboolean
489 _kh_startup (void)
490 {
491   static GOnce init_once = G_ONCE_INIT;
492   g_once (&init_once, _kh_startup_impl, NULL);
493   return GPOINTER_TO_INT (init_once.retval);
494 }
495
496
497 /**
498  * _kh_start_watching:
499  * @sub: a #kqueue_sub
500  *
501  * Starts watching on a subscription.
502  *
503  * Returns: %TRUE on success, %FALSE otherwise.
504  **/
505 gboolean
506 _kh_start_watching (kqueue_sub *sub)
507 {
508   g_assert (kqueue_socket_pair[0] != -1);
509   g_assert (sub != NULL);
510   g_assert (sub->filename != NULL);
511
512   /* kqueue requires a file descriptor to monitor. Sad but true */
513 #if defined (O_EVTONLY)
514   sub->fd = open (sub->filename, O_EVTONLY);
515 #else
516   sub->fd = open (sub->filename, O_RDONLY);
517 #endif
518
519   if (sub->fd == -1)
520     {
521       KH_W ("failed to open file %s (error %d)", sub->filename, errno);
522       return FALSE;
523     }
524
525   _ku_file_information (sub->fd, &sub->is_dir, NULL);
526   if (sub->is_dir)
527     {
528       /* I know, it is very bad to make such decisions in this way and here.
529        * We already do have an user_data at the #kqueue_sub, and it may point to
530        * GKqueueFileMonitor or GKqueueDirectoryMonitor. For a directory case,
531        * we need to scan in contents for the further diffs. Ideally this process
532        * should be delegated to the GKqueueDirectoryMonitor, but for now I will
533        * do it in a dirty way right here. */
534       if (sub->deps)
535         dl_free (sub->deps);
536
537       sub->deps = dl_listing (sub->filename);  
538     }
539
540   G_LOCK (hash_lock);
541   g_hash_table_insert (subs_hash_table, GINT_TO_POINTER (sub->fd), sub);
542   G_UNLOCK (hash_lock);
543
544   _kqueue_thread_push_fd (sub->fd);
545   
546   /* Bump the kqueue thread. It will pick up a new sub entry to monitor */
547   if (!_ku_write (kqueue_socket_pair[0], "A", 1))
548     KH_W ("Failed to bump the kqueue thread (add fd, error %d)", errno);
549   return TRUE;
550 }
551
552
553 /**
554  * _kh_add_sub:
555  * @sub: a #kqueue_sub
556  *
557  * Adds a subscription for monitoring.
558  *
559  * This funciton tries to start watching a subscription with
560  * _kh_start_watching(). On failure, i.e. when a file does not exist yet,
561  * the subscription will be added to a list of missing files to continue
562  * watching when the file will appear.
563  *
564  * Returns: %TRUE
565  **/
566 gboolean
567 _kh_add_sub (kqueue_sub *sub)
568 {
569   g_assert (sub != NULL);
570
571   if (!_kh_start_watching (sub))
572     _km_add_missing (sub);
573
574   return TRUE;
575 }
576
577
578 /**
579  * _kh_cancel_sub:
580  * @sub a #kqueue_sub
581  *
582  * Stops monitoring on a subscription.
583  *
584  * Returns: %TRUE
585  **/
586 gboolean
587 _kh_cancel_sub (kqueue_sub *sub)
588 {
589   gboolean missing = FALSE;
590   g_assert (kqueue_socket_pair[0] != -1);
591   g_assert (sub != NULL);
592
593   G_LOCK (hash_lock);
594   missing = !g_hash_table_remove (subs_hash_table, GINT_TO_POINTER (sub->fd));
595   G_UNLOCK (hash_lock);
596
597   if (missing)
598     {
599       /* If there were no fd for this subscription, file is still
600        * missing. */
601       KH_W ("Removing subscription from missing");
602       _km_remove (sub);
603     }
604   else
605     {
606       /* fd will be closed in the kqueue thread */
607       _kqueue_thread_remove_fd (sub->fd);
608
609       /* Bump the kqueue thread. It will pick up a new sub entry to remove*/
610       if (!_ku_write (kqueue_socket_pair[0], "R", 1))
611         KH_W ("Failed to bump the kqueue thread (remove fd, error %d)", errno);
612     }
613
614   return TRUE;
615 }
616
617
618 /**
619  * _kh_file_appeared_cb:
620  * @sub: a #kqueue_sub
621  *
622  * A callback function for kqueue-missing subsystem.
623  *
624  * Signals that a missing file has finally appeared in the filesystem.
625  * Emits %G_FILE_MONITOR_EVENT_CREATED.
626  **/
627 void
628 _kh_file_appeared_cb (kqueue_sub *sub)
629 {
630   GFile* child;
631
632   g_assert (sub != NULL);
633   g_assert (sub->filename);
634
635   if (!g_file_test (sub->filename, G_FILE_TEST_EXISTS))
636     return;
637
638   child = g_file_new_for_path (sub->filename);
639
640   g_file_monitor_emit_event (G_FILE_MONITOR (sub->user_data),
641                              child,
642                              NULL,
643                              G_FILE_MONITOR_EVENT_CREATED);
644
645   g_object_unref (child);
646 }