Fixup fromcvs/togit conversion
[pkgsrcv2.git] / sysutils / hal / files / hald-netbsd / hal-file-monitor.c
1 /***************************************************************************
2  * CVSID: $Id$
3  *
4  * hal-file-monitor.c: Kqueue-based file monitor
5  *
6  * Copyright (C) 2007 Joe Marcus Clarke <marcus@FreeBSD.org>
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
21  *
22  **************************************************************************/
23
24 #include <stdlib.h>
25 #include <stdio.h>
26 #include <fcntl.h>
27 #include <time.h>
28 #include <unistd.h>
29 #include <string.h>
30 #include <errno.h>
31
32 #include <sys/types.h>
33 #include <sys/event.h>
34 #include <sys/time.h>
35 #include <sys/stat.h>
36
37 #include <glib.h>
38 #include <glib/gi18n.h>
39 #include <glib/gstdio.h>
40 #include <glib-object.h>
41
42 #include "../hal-file-monitor.h"
43
44 #define HAL_FILE_MONITOR_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), HAL_TYPE_FILE_MONITOR, HalFileMonitorPrivate))
45
46 #define VN_NOTE_CHANGED (NOTE_WRITE | NOTE_EXTEND | NOTE_ATTRIB | NOTE_LINK)
47 #define VN_NOTE_DELETED (NOTE_DELETE | NOTE_REVOKE)
48 #define VN_NOTE_ALL     (VN_NOTE_CHANGED | VN_NOTE_DELETED | NOTE_RENAME)
49
50 typedef struct
51 {
52   char *path;
53   int omask;
54   gboolean isdir;
55   HalFileMonitorNotifyFunc func;
56   gpointer udata;
57   GHashTable *dir_contents;
58 } FileKqueueData;
59
60 typedef struct
61 {
62   char *path;
63   time_t atime;
64   gboolean owner;
65   HalFileMonitorNotifyFunc func;
66   gpointer udata;
67 } FileAccessData;
68
69 struct HalFileMonitorPrivate
70 {
71   int kqueue_fd;
72   guint io_watch;
73   guint access_source;
74
75   gboolean initialized_kqueue;
76
77   GHashTable *fd_to_kdata;
78   GHashTable *fd_to_adata;
79 };
80
81 G_DEFINE_TYPE (HalFileMonitor, hal_file_monitor, G_TYPE_OBJECT)
82
83 static gpointer monitor_object = NULL;
84
85 static void hal_file_monitor_finalize (GObject *object);
86 static GHashTable *get_dir_contents (const char *path);
87 static GHashTable *diff_dir_contents (FileKqueueData *data, GSList **added, GSList **removed);
88 static int hal_mask_to_kmask (int mask);
89 static void monitor_release_kdata (HalFileMonitor *monitor, int fd, FileKqueueData *data);
90 static void monitor_release_adata (HalFileMonitor *monitor, int fd, FileAccessData *data);
91 static gboolean remove_kdata_foreach (gpointer fd, FileKqueueData *data, HalFileMonitor *monitor);
92 static gboolean remove_adata_foreach (gpointer fd, FileAccessData *data, HalFileMonitor *monitor);
93 static void hal_file_monitor_remove_kdata (HalFileMonitor *monitor, guint id);
94 static void hal_file_monitor_remove_adata (HalFileMonitor *monitor, guint id);
95 static void setup_monitor (HalFileMonitor *monitor);
96 static void close_monitor (HalFileMonitor *monitor);
97 static gboolean hal_file_access_monitor (gpointer data);
98 /*static char *fflags_to_str (int fflags);*/
99 static void emit_monitor_event (HalFileMonitor *monitor, HalFileMonitorEvent event, const char *path, HalFileMonitorNotifyFunc func, gpointer udata);
100 static gboolean handle_kqueue_event (GIOChannel *source, GIOCondition condition, gpointer udata);
101
102 GQuark
103 hal_file_monitor_error_quark (void)
104 {
105   static GQuark ret = 0;
106   if (ret == 0)
107     {
108       ret = g_quark_from_static_string ("hal_file_monitor_error");
109     }
110
111   return ret;
112 }
113
114 static int
115 hal_mask_to_kmask (int mask)
116 {
117   int kmask = 0;
118
119   if (mask & HAL_FILE_MONITOR_EVENT_CREATE)
120     {
121       kmask |= NOTE_WRITE | NOTE_LINK;
122     }
123
124   if (mask & HAL_FILE_MONITOR_EVENT_DELETE)
125     {
126       kmask |= NOTE_WRITE | NOTE_LINK | VN_NOTE_DELETED;
127     }
128
129   if (mask & HAL_FILE_MONITOR_EVENT_CHANGE)
130     {
131       kmask |= VN_NOTE_CHANGED;
132     }
133
134   return kmask;
135 }
136
137 guint
138 hal_file_monitor_add_notify (HalFileMonitor *monitor,
139                              const char *path,
140                              int mask,
141                              HalFileMonitorNotifyFunc notify_func,
142                              gpointer data)
143 {
144   struct kevent ev;
145   struct stat sb;
146   int fd;
147   int id = 0;
148
149   if (! monitor->priv->initialized_kqueue)
150     {
151       return id;
152     }
153
154   fd = open (path, O_RDONLY);
155   if (fd < 0)
156     {
157       return id;
158     }
159
160   if (fstat (fd, &sb) == -1)
161     {
162       close (fd);
163       return id;
164     }
165
166   if (mask & HAL_FILE_MONITOR_EVENT_ACCESS)
167     {
168       FileAccessData *adata;
169
170       adata = g_new0 (FileAccessData, 1);
171       adata->path = g_strdup (path);
172       adata->atime = sb.st_atime;
173       adata->func = notify_func;
174       adata->udata = data;
175       if (mask == HAL_FILE_MONITOR_EVENT_ACCESS)
176         {
177           /* We will close the file descriptor when we release the adata. */
178           adata->owner = TRUE;
179         }
180       g_hash_table_insert (monitor->priv->fd_to_adata,
181                            GINT_TO_POINTER (fd),
182                            adata);
183       id = fd;
184     }
185
186   if ((mask & HAL_FILE_MONITOR_EVENT_CREATE) ||
187       (mask & HAL_FILE_MONITOR_EVENT_DELETE) ||
188       (mask & HAL_FILE_MONITOR_EVENT_CHANGE))
189     {
190       FileKqueueData *kdata;
191       int kmask;
192       gboolean isdir;
193
194       kmask = hal_mask_to_kmask (mask);
195
196       isdir = (sb.st_mode & S_IFDIR) != 0;
197       if (! isdir && mask == HAL_FILE_MONITOR_EVENT_CREATE)
198         {
199           /* We can't monitor creation on a file. */
200           goto done;
201         }
202
203       kdata = g_new0 (FileKqueueData, 1);
204       kdata->path = g_strdup (path);
205       kdata->omask = mask;
206       kdata->isdir = isdir;
207       kdata->func = notify_func;
208       kdata->udata = data;
209       if (isdir)
210         {
211           kdata->dir_contents = get_dir_contents (path);
212         }
213
214       /*g_warning ("XXX: Adding event with mask %s", fflags_to_str (kmask));*/
215       EV_SET (&ev, fd, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR,
216               kmask, 0, monitor);
217       if (kevent (monitor->priv->kqueue_fd, &ev, 1, NULL, 0, NULL) < 0)
218         {
219           monitor_release_kdata (monitor, fd, kdata);
220           goto done;
221         }
222       g_hash_table_insert (monitor->priv->fd_to_kdata,
223                            GINT_TO_POINTER (fd),
224                            kdata);
225       id = fd;
226     }
227
228 done:
229   return id;
230 }
231
232 static void
233 hal_file_monitor_remove_kdata (HalFileMonitor *monitor,
234                                guint id)
235 {
236   FileKqueueData *kdata;
237
238   kdata = (FileKqueueData *) g_hash_table_lookup (monitor->priv->fd_to_kdata, GINT_TO_POINTER (id));
239
240   if (kdata)
241     {
242       g_hash_table_remove (monitor->priv->fd_to_kdata, GINT_TO_POINTER (id));
243       monitor_release_kdata (monitor, id, kdata);
244     }
245 }
246
247 static void
248 hal_file_monitor_remove_adata (HalFileMonitor *monitor,
249                                guint id)
250 {
251   FileAccessData *adata;
252
253   adata = (FileAccessData *) g_hash_table_lookup (monitor->priv->fd_to_adata, GINT_TO_POINTER (id));
254
255   if (adata)
256     {
257       g_hash_table_remove (monitor->priv->fd_to_adata, GINT_TO_POINTER (id));
258       monitor_release_adata (monitor, id, adata);
259     }
260 }
261
262 void
263 hal_file_monitor_remove_notify (HalFileMonitor *monitor,
264                                 guint id)
265 {
266   if (! monitor->priv->initialized_kqueue)
267     return;
268
269   hal_file_monitor_remove_kdata (monitor, id);
270   hal_file_monitor_remove_adata (monitor, id);
271
272 }
273
274 static void
275 monitor_release_kdata (HalFileMonitor *monitor,
276                        int fd,
277                        FileKqueueData *data)
278 {
279   g_free (data->path);
280   data->path = NULL;
281
282   if (data->dir_contents)
283     {
284       g_hash_table_remove_all (data->dir_contents);
285       g_hash_table_destroy (data->dir_contents);
286     }
287   data->dir_contents = NULL;
288
289   close (fd);
290
291   g_free (data);
292 }
293
294 static void
295 monitor_release_adata (HalFileMonitor *monitor,
296                        int fd,
297                        FileAccessData *data)
298 {
299   g_free (data->path);
300   data->path = NULL;
301
302   if (data->owner)
303     {
304       close (fd);
305     }
306
307   g_free (data);
308 }
309
310 static gboolean
311 remove_kdata_foreach (gpointer fd,
312                       FileKqueueData *data,
313                       HalFileMonitor *monitor)
314 {
315   monitor_release_kdata (monitor, GPOINTER_TO_INT (fd), data);
316   return TRUE;
317 }
318
319 static gboolean
320 remove_adata_foreach (gpointer fd,
321                       FileAccessData *data,
322                       HalFileMonitor *monitor)
323 {
324   monitor_release_adata (monitor, GPOINTER_TO_INT (fd), data);
325   return TRUE;
326 }
327
328 static void
329 close_monitor (HalFileMonitor *monitor)
330 {
331   if (! monitor->priv->initialized_kqueue)
332     {
333       return;
334     }
335
336   monitor->priv->initialized_kqueue = FALSE;
337
338   g_hash_table_foreach_remove (monitor->priv->fd_to_kdata,
339                                (GHRFunc) remove_kdata_foreach,
340                                monitor);
341
342   g_hash_table_foreach_remove (monitor->priv->fd_to_adata,
343                                (GHRFunc) remove_adata_foreach,
344                                monitor);
345
346   if (monitor->priv->io_watch)
347     {
348       g_source_remove (monitor->priv->io_watch);
349     }
350   monitor->priv->io_watch = 0;
351
352   if (monitor->priv->access_source)
353     {
354       g_source_remove (monitor->priv->access_source);
355     }
356   monitor->priv->access_source = 0;
357
358   if (monitor->priv->kqueue_fd > -1)
359     {
360       close (monitor->priv->kqueue_fd);
361     }
362   monitor->priv->kqueue_fd = -1;
363 }
364
365 static gboolean
366 hal_file_access_monitor (gpointer data)
367 {
368   HalFileMonitor *monitor;
369   GList *keys, *l;
370
371   g_return_val_if_fail (HAL_IS_FILE_MONITOR (data), FALSE);
372
373   monitor = HAL_FILE_MONITOR (data);
374
375   g_return_val_if_fail (monitor->priv != NULL, FALSE);
376
377   keys = g_hash_table_get_keys (monitor->priv->fd_to_adata);
378
379   for (l = keys; l != NULL; l = l->next)
380     {
381       FileAccessData *adata;
382       struct stat sb;
383       int fd;
384
385       fd = GPOINTER_TO_INT (l->data);
386       adata = g_hash_table_lookup (monitor->priv->fd_to_adata,
387                                    l->data);
388
389       if (! adata)
390         {
391           continue;
392         }
393
394       if (fstat (fd, &sb) == -1)
395         {
396           g_warning ("Failed to stat %s: %s", adata->path, g_strerror (errno));
397           hal_file_monitor_remove_adata (monitor, fd);
398           continue;
399         }
400
401       if (sb.st_atime != adata->atime)
402         {
403           adata->atime = sb.st_atime;
404           emit_monitor_event (monitor, HAL_FILE_MONITOR_EVENT_ACCESS, adata->path, adata->func, adata->udata);
405         }
406     }
407
408   return TRUE;
409 }
410
411 static void
412 setup_monitor (HalFileMonitor *monitor)
413 {
414   GIOChannel *io_channel;
415   int fd;
416
417   if (monitor->priv->initialized_kqueue)
418     {
419       return;
420     }
421
422   if ((fd = kqueue ()) < 0)
423     {
424       g_warning ("Failed to initialize kqueue: %s",
425                  g_strerror (errno));
426       return;
427     }
428
429   monitor->priv->kqueue_fd = fd;
430
431   monitor->priv->fd_to_kdata = g_hash_table_new (g_direct_hash,
432                                g_direct_equal);
433   monitor->priv->fd_to_adata = g_hash_table_new (g_direct_hash,
434                                g_direct_equal);
435
436   io_channel = g_io_channel_unix_new (fd);
437   monitor->priv->io_watch = g_io_add_watch (io_channel,
438                             G_IO_IN|G_IO_PRI,
439                             (GIOFunc) handle_kqueue_event,
440                             monitor);
441   g_io_channel_unref (io_channel);
442
443   monitor->priv->access_source = g_timeout_add (1000, (GSourceFunc) hal_file_access_monitor, monitor);
444
445   monitor->priv->initialized_kqueue = TRUE;
446 }
447
448 static void
449 hal_file_monitor_init (HalFileMonitor *monitor)
450 {
451   monitor->priv = HAL_FILE_MONITOR_GET_PRIVATE (monitor);
452
453   setup_monitor (monitor);
454 }
455
456 static void
457 hal_file_monitor_class_init (HalFileMonitorClass *klass)
458 {
459   GObjectClass   *object_class = G_OBJECT_CLASS (klass);
460
461   object_class->finalize = hal_file_monitor_finalize;
462
463   g_type_class_add_private (klass, sizeof (HalFileMonitorPrivate));
464 }
465
466 static void
467 hal_file_monitor_finalize (GObject *object)
468 {
469   HalFileMonitor *monitor;
470
471   g_return_if_fail (object != NULL);
472   g_return_if_fail (HAL_IS_FILE_MONITOR (object));
473
474   monitor = HAL_FILE_MONITOR (object);
475
476   g_return_if_fail (monitor->priv != NULL);
477
478   close_monitor (monitor);
479
480   g_hash_table_destroy (monitor->priv->fd_to_kdata);
481   g_hash_table_destroy (monitor->priv->fd_to_adata);
482
483   G_OBJECT_CLASS (hal_file_monitor_parent_class)->finalize (object);
484 }
485
486 HalFileMonitor *
487 hal_file_monitor_new (void)
488 {
489   if (monitor_object != NULL)
490     {
491       g_object_ref (monitor_object);
492     }
493   else
494     {
495       monitor_object = g_object_new (HAL_TYPE_FILE_MONITOR, NULL);
496
497       g_object_add_weak_pointer (monitor_object,
498                                  (gpointer *) &monitor_object);
499     }
500
501   return HAL_FILE_MONITOR (monitor_object);
502 }
503
504 /*
505 static char *
506 fflags_to_str (int fflags)
507 {
508
509   GString *out;
510
511   out = g_string_new (NULL);
512
513   if (fflags & NOTE_WRITE)
514     {
515       g_string_append (out, " WRITE ");
516     }
517   if (fflags & NOTE_EXTEND)
518     {
519       g_string_append (out, " EXTEND ");
520     }
521   if (fflags & NOTE_ATTRIB)
522     {
523       g_string_append (out, " ATTRIB ");
524     }
525   if (fflags & NOTE_LINK)
526     {
527       g_string_append (out, " LINK ");
528     }
529   if (fflags & NOTE_DELETE)
530     {
531       g_string_append (out, " DELETE ");
532     }
533   if (fflags & NOTE_REVOKE)
534     {
535       g_string_append (out, " REVOKE ");
536     }
537   if (fflags & NOTE_RENAME)
538     {
539       g_string_append (out, " RENAME ");
540     }
541
542   return g_string_free (out, FALSE);
543 }
544 */
545
546 static void
547 emit_monitor_event (HalFileMonitor *monitor,
548                     HalFileMonitorEvent event,
549                     const char *path,
550                     HalFileMonitorNotifyFunc func,
551                     gpointer udata)
552 {
553   if (func)
554     {
555       func (monitor, event, path, udata);
556     }
557 }
558
559 static gboolean
560 handle_kqueue_event (GIOChannel *source,
561                      GIOCondition   condition,
562                      gpointer udata)
563 {
564   struct kevent ev;
565   struct timespec timeout = { 0, 0 };
566   int nevents;
567   HalFileMonitor *monitor;
568
569   g_return_val_if_fail (HAL_IS_FILE_MONITOR (udata), FALSE);
570
571   monitor = HAL_FILE_MONITOR (udata);
572
573   g_return_val_if_fail (monitor->priv != NULL, FALSE);
574
575   g_return_val_if_fail (monitor->priv->kqueue_fd > -1, FALSE);
576
577   nevents = kevent (monitor->priv->kqueue_fd, NULL, 0, &ev, 1, &timeout);
578   if (nevents == 1)
579     {
580       int fd;
581       FileKqueueData *data;
582
583       fd = ev.ident;
584       data = g_hash_table_lookup (monitor->priv->fd_to_kdata,
585                                   GINT_TO_POINTER (fd));
586       if (! data)
587         {
588           /* The monitor may have been deleted. */
589           return TRUE;
590         }
591
592       if ((data->omask & HAL_FILE_MONITOR_EVENT_DELETE) &&
593           (ev.fflags & VN_NOTE_DELETED))
594         {
595           emit_monitor_event (monitor, HAL_FILE_MONITOR_EVENT_DELETE, data->path, data->func, data->udata);
596           hal_file_monitor_remove_kdata (monitor, fd);
597           return TRUE;
598         }
599
600       if ((data->omask & HAL_FILE_MONITOR_EVENT_CREATE) ||
601           (data->omask & HAL_FILE_MONITOR_EVENT_DELETE))
602         {
603           if (data->isdir)
604             {
605               GSList *added = NULL;
606               GSList *removed = NULL;
607               GSList *l;
608               GHashTable *table;
609               gboolean found_change = FALSE;
610
611               table = diff_dir_contents (data, &added, &removed);
612               if (data->omask & HAL_FILE_MONITOR_EVENT_CREATE)
613                 {
614                   for (l = added; l != NULL; l = l->next)
615                     {
616                       char *path;
617
618                       path = g_build_filename (data->path, l->data, NULL);
619                       emit_monitor_event (monitor, HAL_FILE_MONITOR_EVENT_CREATE, path, data->func, data->udata);
620                       g_free (path);
621                     }
622                 }
623
624               if (data->omask & HAL_FILE_MONITOR_EVENT_DELETE)
625                 {
626                   for (l = removed; l != NULL; l = l->next)
627                     {
628                       char *path;
629
630                       path = g_build_filename (data->path, l->data, NULL);
631                       emit_monitor_event (monitor, HAL_FILE_MONITOR_EVENT_DELETE, path, data->func, data->udata);
632                       g_free (path);
633                     }
634                 }
635
636               if (added || removed)
637                 {
638                   g_hash_table_remove_all (data->dir_contents);
639                   g_hash_table_destroy (data->dir_contents);
640                   data->dir_contents = table;
641                   found_change = TRUE;
642                 }
643               else if (table)
644                 {
645                   g_hash_table_remove_all (table);
646                   g_hash_table_destroy (table);
647                 }
648               else
649                 {
650                   hal_file_monitor_remove_kdata (monitor, fd);
651                 }
652
653               if (added)
654                 {
655                   g_slist_foreach (added, (GFunc) g_free, NULL);
656                   g_slist_free (added);
657                 }
658               if (removed)
659                 {
660                   g_slist_foreach (removed, (GFunc) g_free, NULL);
661                   g_slist_free (removed);
662                 }
663
664               if (found_change)
665                 {
666                   return TRUE;
667                 }
668             }
669         }
670
671       if (data->omask & HAL_FILE_MONITOR_EVENT_CHANGE)
672         {
673           emit_monitor_event (monitor, HAL_FILE_MONITOR_EVENT_CHANGE, data->path, data->func, data->udata);
674         }
675     }
676   else
677     {
678       g_warning ("Failed to read from kqueue: %s", g_strerror (errno));
679       return FALSE;
680     }
681
682   return TRUE;
683 }
684
685 static GHashTable *
686 diff_dir_contents (FileKqueueData *data,
687                    GSList **added,
688                    GSList **removed)
689 {
690   GDir *dir;
691   GError *err = NULL;
692   GHashTable *table = NULL;
693
694   dir = g_dir_open (data->path, 0, &err);
695   if (dir)
696     {
697       const char *fname;
698
699       table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
700
701       while ((fname = g_dir_read_name (dir)))
702         {
703           if (added)
704             {
705               if (! g_hash_table_lookup (data->dir_contents, fname))
706                 {
707                   *added = g_slist_prepend (*added, g_strdup (fname));
708                 }
709             }
710
711           g_hash_table_insert (table, g_strdup (fname), GINT_TO_POINTER (TRUE));
712         }
713
714       g_dir_close (dir);
715
716       if (removed)
717         {
718           GList *keys;
719           GList *l;
720
721           keys = g_hash_table_get_keys (data->dir_contents);
722
723           for (l = keys; l != NULL; l = l->next)
724             {
725               if (! g_hash_table_lookup (table, l->data))
726                 {
727                   *removed = g_slist_prepend (*removed, g_strdup (l->data));
728                 }
729             }
730         }
731
732     }
733   else
734     {
735       g_warning ("Failed to open directory: %s", err->message);
736       g_error_free (err);
737     }
738
739   return table;
740 }
741
742 static GHashTable *
743 get_dir_contents (const char *path)
744 {
745   GDir *dir;
746   GError *err = NULL;
747   GHashTable *table = NULL;
748
749   dir = g_dir_open (path, 0, &err);
750   if (dir)
751     {
752       const char *fname;
753
754       table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
755
756       while ((fname = g_dir_read_name (dir)))
757         {
758           g_hash_table_insert (table, g_strdup (fname), GINT_TO_POINTER (TRUE));
759         }
760       g_dir_close (dir);
761     }
762   else
763     {
764       g_warning ("Failed to open directory %s: %s\n", path, err->message);
765       g_error_free (err);
766     }
767
768   return table;
769 }
770
771 /*
772 static void
773 print_event (HalFileMonitor *monitor,
774              HalFileMonitorEvent event,
775              const char *path,
776              gpointer udata)
777 {
778   GString *ename;
779
780   ename = g_string_new (NULL);
781
782   if (event & HAL_FILE_MONITOR_EVENT_ACCESS)
783     {
784       g_string_append (ename, "ACCESS ");
785     }
786   if (event & HAL_FILE_MONITOR_EVENT_CREATE)
787     {
788       g_string_append (ename, "CREATE ");
789     }
790   if (event & HAL_FILE_MONITOR_EVENT_DELETE)
791     {
792       g_string_append (ename, "DELETE ");
793     }
794   if (event & HAL_FILE_MONITOR_EVENT_CHANGE)
795     {
796       g_string_append (ename, "CHANGE ");
797     }
798
799   printf("Received event for %s: %s\n", path, g_string_free (ename, FALSE));
800 }
801
802 int
803 main(void)
804 {
805   const char *path = "/tmp/kqueue.d";
806   guint watch;
807   GMainLoop *loop;
808   HalFileMonitor *monitor;
809
810   g_type_init ();
811
812   monitor = hal_file_monitor_new ();
813   hal_file_monitor_add_notify (monitor, path, HAL_FILE_MONITOR_EVENT_CREATE|HAL_FILE_MONITOR_EVENT_DELETE|HAL_FILE_MONITOR_EVENT_CHANGE,print_event, NULL);
814
815   loop = g_main_loop_new (NULL, FALSE);
816
817   g_main_loop_run (loop);
818
819   return 0;
820 }
821 */