1 /***************************************************************************
4 * hal-file-monitor.c: Kqueue-based file monitor
6 * Copyright (C) 2007 Joe Marcus Clarke <marcus@FreeBSD.org>
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.
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.
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
22 **************************************************************************/
32 #include <sys/types.h>
33 #include <sys/event.h>
38 #include <glib/gi18n.h>
39 #include <glib/gstdio.h>
40 #include <glib-object.h>
42 #include "../hal-file-monitor.h"
44 #define HAL_FILE_MONITOR_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), HAL_TYPE_FILE_MONITOR, HalFileMonitorPrivate))
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)
55 HalFileMonitorNotifyFunc func;
57 GHashTable *dir_contents;
65 HalFileMonitorNotifyFunc func;
69 struct HalFileMonitorPrivate
75 gboolean initialized_kqueue;
77 GHashTable *fd_to_kdata;
78 GHashTable *fd_to_adata;
81 G_DEFINE_TYPE (HalFileMonitor, hal_file_monitor, G_TYPE_OBJECT)
83 static gpointer monitor_object = NULL;
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);
103 hal_file_monitor_error_quark (void)
105 static GQuark ret = 0;
108 ret = g_quark_from_static_string ("hal_file_monitor_error");
115 hal_mask_to_kmask (int mask)
119 if (mask & HAL_FILE_MONITOR_EVENT_CREATE)
121 kmask |= NOTE_WRITE | NOTE_LINK;
124 if (mask & HAL_FILE_MONITOR_EVENT_DELETE)
126 kmask |= NOTE_WRITE | NOTE_LINK | VN_NOTE_DELETED;
129 if (mask & HAL_FILE_MONITOR_EVENT_CHANGE)
131 kmask |= VN_NOTE_CHANGED;
138 hal_file_monitor_add_notify (HalFileMonitor *monitor,
141 HalFileMonitorNotifyFunc notify_func,
149 if (! monitor->priv->initialized_kqueue)
154 fd = open (path, O_RDONLY);
160 if (fstat (fd, &sb) == -1)
166 if (mask & HAL_FILE_MONITOR_EVENT_ACCESS)
168 FileAccessData *adata;
170 adata = g_new0 (FileAccessData, 1);
171 adata->path = g_strdup (path);
172 adata->atime = sb.st_atime;
173 adata->func = notify_func;
175 if (mask == HAL_FILE_MONITOR_EVENT_ACCESS)
177 /* We will close the file descriptor when we release the adata. */
180 g_hash_table_insert (monitor->priv->fd_to_adata,
181 GINT_TO_POINTER (fd),
186 if ((mask & HAL_FILE_MONITOR_EVENT_CREATE) ||
187 (mask & HAL_FILE_MONITOR_EVENT_DELETE) ||
188 (mask & HAL_FILE_MONITOR_EVENT_CHANGE))
190 FileKqueueData *kdata;
194 kmask = hal_mask_to_kmask (mask);
196 isdir = (sb.st_mode & S_IFDIR) != 0;
197 if (! isdir && mask == HAL_FILE_MONITOR_EVENT_CREATE)
199 /* We can't monitor creation on a file. */
203 kdata = g_new0 (FileKqueueData, 1);
204 kdata->path = g_strdup (path);
206 kdata->isdir = isdir;
207 kdata->func = notify_func;
211 kdata->dir_contents = get_dir_contents (path);
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,
217 if (kevent (monitor->priv->kqueue_fd, &ev, 1, NULL, 0, NULL) < 0)
219 monitor_release_kdata (monitor, fd, kdata);
222 g_hash_table_insert (monitor->priv->fd_to_kdata,
223 GINT_TO_POINTER (fd),
233 hal_file_monitor_remove_kdata (HalFileMonitor *monitor,
236 FileKqueueData *kdata;
238 kdata = (FileKqueueData *) g_hash_table_lookup (monitor->priv->fd_to_kdata, GINT_TO_POINTER (id));
242 g_hash_table_remove (monitor->priv->fd_to_kdata, GINT_TO_POINTER (id));
243 monitor_release_kdata (monitor, id, kdata);
248 hal_file_monitor_remove_adata (HalFileMonitor *monitor,
251 FileAccessData *adata;
253 adata = (FileAccessData *) g_hash_table_lookup (monitor->priv->fd_to_adata, GINT_TO_POINTER (id));
257 g_hash_table_remove (monitor->priv->fd_to_adata, GINT_TO_POINTER (id));
258 monitor_release_adata (monitor, id, adata);
263 hal_file_monitor_remove_notify (HalFileMonitor *monitor,
266 if (! monitor->priv->initialized_kqueue)
269 hal_file_monitor_remove_kdata (monitor, id);
270 hal_file_monitor_remove_adata (monitor, id);
275 monitor_release_kdata (HalFileMonitor *monitor,
277 FileKqueueData *data)
282 if (data->dir_contents)
284 g_hash_table_remove_all (data->dir_contents);
285 g_hash_table_destroy (data->dir_contents);
287 data->dir_contents = NULL;
295 monitor_release_adata (HalFileMonitor *monitor,
297 FileAccessData *data)
311 remove_kdata_foreach (gpointer fd,
312 FileKqueueData *data,
313 HalFileMonitor *monitor)
315 monitor_release_kdata (monitor, GPOINTER_TO_INT (fd), data);
320 remove_adata_foreach (gpointer fd,
321 FileAccessData *data,
322 HalFileMonitor *monitor)
324 monitor_release_adata (monitor, GPOINTER_TO_INT (fd), data);
329 close_monitor (HalFileMonitor *monitor)
331 if (! monitor->priv->initialized_kqueue)
336 monitor->priv->initialized_kqueue = FALSE;
338 g_hash_table_foreach_remove (monitor->priv->fd_to_kdata,
339 (GHRFunc) remove_kdata_foreach,
342 g_hash_table_foreach_remove (monitor->priv->fd_to_adata,
343 (GHRFunc) remove_adata_foreach,
346 if (monitor->priv->io_watch)
348 g_source_remove (monitor->priv->io_watch);
350 monitor->priv->io_watch = 0;
352 if (monitor->priv->access_source)
354 g_source_remove (monitor->priv->access_source);
356 monitor->priv->access_source = 0;
358 if (monitor->priv->kqueue_fd > -1)
360 close (monitor->priv->kqueue_fd);
362 monitor->priv->kqueue_fd = -1;
366 hal_file_access_monitor (gpointer data)
368 HalFileMonitor *monitor;
371 g_return_val_if_fail (HAL_IS_FILE_MONITOR (data), FALSE);
373 monitor = HAL_FILE_MONITOR (data);
375 g_return_val_if_fail (monitor->priv != NULL, FALSE);
377 keys = g_hash_table_get_keys (monitor->priv->fd_to_adata);
379 for (l = keys; l != NULL; l = l->next)
381 FileAccessData *adata;
385 fd = GPOINTER_TO_INT (l->data);
386 adata = g_hash_table_lookup (monitor->priv->fd_to_adata,
394 if (fstat (fd, &sb) == -1)
396 g_warning ("Failed to stat %s: %s", adata->path, g_strerror (errno));
397 hal_file_monitor_remove_adata (monitor, fd);
401 if (sb.st_atime != adata->atime)
403 adata->atime = sb.st_atime;
404 emit_monitor_event (monitor, HAL_FILE_MONITOR_EVENT_ACCESS, adata->path, adata->func, adata->udata);
412 setup_monitor (HalFileMonitor *monitor)
414 GIOChannel *io_channel;
417 if (monitor->priv->initialized_kqueue)
422 if ((fd = kqueue ()) < 0)
424 g_warning ("Failed to initialize kqueue: %s",
429 monitor->priv->kqueue_fd = fd;
431 monitor->priv->fd_to_kdata = g_hash_table_new (g_direct_hash,
433 monitor->priv->fd_to_adata = g_hash_table_new (g_direct_hash,
436 io_channel = g_io_channel_unix_new (fd);
437 monitor->priv->io_watch = g_io_add_watch (io_channel,
439 (GIOFunc) handle_kqueue_event,
441 g_io_channel_unref (io_channel);
443 monitor->priv->access_source = g_timeout_add (1000, (GSourceFunc) hal_file_access_monitor, monitor);
445 monitor->priv->initialized_kqueue = TRUE;
449 hal_file_monitor_init (HalFileMonitor *monitor)
451 monitor->priv = HAL_FILE_MONITOR_GET_PRIVATE (monitor);
453 setup_monitor (monitor);
457 hal_file_monitor_class_init (HalFileMonitorClass *klass)
459 GObjectClass *object_class = G_OBJECT_CLASS (klass);
461 object_class->finalize = hal_file_monitor_finalize;
463 g_type_class_add_private (klass, sizeof (HalFileMonitorPrivate));
467 hal_file_monitor_finalize (GObject *object)
469 HalFileMonitor *monitor;
471 g_return_if_fail (object != NULL);
472 g_return_if_fail (HAL_IS_FILE_MONITOR (object));
474 monitor = HAL_FILE_MONITOR (object);
476 g_return_if_fail (monitor->priv != NULL);
478 close_monitor (monitor);
480 g_hash_table_destroy (monitor->priv->fd_to_kdata);
481 g_hash_table_destroy (monitor->priv->fd_to_adata);
483 G_OBJECT_CLASS (hal_file_monitor_parent_class)->finalize (object);
487 hal_file_monitor_new (void)
489 if (monitor_object != NULL)
491 g_object_ref (monitor_object);
495 monitor_object = g_object_new (HAL_TYPE_FILE_MONITOR, NULL);
497 g_object_add_weak_pointer (monitor_object,
498 (gpointer *) &monitor_object);
501 return HAL_FILE_MONITOR (monitor_object);
506 fflags_to_str (int fflags)
511 out = g_string_new (NULL);
513 if (fflags & NOTE_WRITE)
515 g_string_append (out, " WRITE ");
517 if (fflags & NOTE_EXTEND)
519 g_string_append (out, " EXTEND ");
521 if (fflags & NOTE_ATTRIB)
523 g_string_append (out, " ATTRIB ");
525 if (fflags & NOTE_LINK)
527 g_string_append (out, " LINK ");
529 if (fflags & NOTE_DELETE)
531 g_string_append (out, " DELETE ");
533 if (fflags & NOTE_REVOKE)
535 g_string_append (out, " REVOKE ");
537 if (fflags & NOTE_RENAME)
539 g_string_append (out, " RENAME ");
542 return g_string_free (out, FALSE);
547 emit_monitor_event (HalFileMonitor *monitor,
548 HalFileMonitorEvent event,
550 HalFileMonitorNotifyFunc func,
555 func (monitor, event, path, udata);
560 handle_kqueue_event (GIOChannel *source,
561 GIOCondition condition,
565 struct timespec timeout = { 0, 0 };
567 HalFileMonitor *monitor;
569 g_return_val_if_fail (HAL_IS_FILE_MONITOR (udata), FALSE);
571 monitor = HAL_FILE_MONITOR (udata);
573 g_return_val_if_fail (monitor->priv != NULL, FALSE);
575 g_return_val_if_fail (monitor->priv->kqueue_fd > -1, FALSE);
577 nevents = kevent (monitor->priv->kqueue_fd, NULL, 0, &ev, 1, &timeout);
581 FileKqueueData *data;
584 data = g_hash_table_lookup (monitor->priv->fd_to_kdata,
585 GINT_TO_POINTER (fd));
588 /* The monitor may have been deleted. */
592 if ((data->omask & HAL_FILE_MONITOR_EVENT_DELETE) &&
593 (ev.fflags & VN_NOTE_DELETED))
595 emit_monitor_event (monitor, HAL_FILE_MONITOR_EVENT_DELETE, data->path, data->func, data->udata);
596 hal_file_monitor_remove_kdata (monitor, fd);
600 if ((data->omask & HAL_FILE_MONITOR_EVENT_CREATE) ||
601 (data->omask & HAL_FILE_MONITOR_EVENT_DELETE))
605 GSList *added = NULL;
606 GSList *removed = NULL;
609 gboolean found_change = FALSE;
611 table = diff_dir_contents (data, &added, &removed);
612 if (data->omask & HAL_FILE_MONITOR_EVENT_CREATE)
614 for (l = added; l != NULL; l = l->next)
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);
624 if (data->omask & HAL_FILE_MONITOR_EVENT_DELETE)
626 for (l = removed; l != NULL; l = l->next)
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);
636 if (added || removed)
638 g_hash_table_remove_all (data->dir_contents);
639 g_hash_table_destroy (data->dir_contents);
640 data->dir_contents = table;
645 g_hash_table_remove_all (table);
646 g_hash_table_destroy (table);
650 hal_file_monitor_remove_kdata (monitor, fd);
655 g_slist_foreach (added, (GFunc) g_free, NULL);
656 g_slist_free (added);
660 g_slist_foreach (removed, (GFunc) g_free, NULL);
661 g_slist_free (removed);
671 if (data->omask & HAL_FILE_MONITOR_EVENT_CHANGE)
673 emit_monitor_event (monitor, HAL_FILE_MONITOR_EVENT_CHANGE, data->path, data->func, data->udata);
678 g_warning ("Failed to read from kqueue: %s", g_strerror (errno));
686 diff_dir_contents (FileKqueueData *data,
692 GHashTable *table = NULL;
694 dir = g_dir_open (data->path, 0, &err);
699 table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
701 while ((fname = g_dir_read_name (dir)))
705 if (! g_hash_table_lookup (data->dir_contents, fname))
707 *added = g_slist_prepend (*added, g_strdup (fname));
711 g_hash_table_insert (table, g_strdup (fname), GINT_TO_POINTER (TRUE));
721 keys = g_hash_table_get_keys (data->dir_contents);
723 for (l = keys; l != NULL; l = l->next)
725 if (! g_hash_table_lookup (table, l->data))
727 *removed = g_slist_prepend (*removed, g_strdup (l->data));
735 g_warning ("Failed to open directory: %s", err->message);
743 get_dir_contents (const char *path)
747 GHashTable *table = NULL;
749 dir = g_dir_open (path, 0, &err);
754 table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
756 while ((fname = g_dir_read_name (dir)))
758 g_hash_table_insert (table, g_strdup (fname), GINT_TO_POINTER (TRUE));
764 g_warning ("Failed to open directory %s: %s\n", path, err->message);
773 print_event (HalFileMonitor *monitor,
774 HalFileMonitorEvent event,
780 ename = g_string_new (NULL);
782 if (event & HAL_FILE_MONITOR_EVENT_ACCESS)
784 g_string_append (ename, "ACCESS ");
786 if (event & HAL_FILE_MONITOR_EVENT_CREATE)
788 g_string_append (ename, "CREATE ");
790 if (event & HAL_FILE_MONITOR_EVENT_DELETE)
792 g_string_append (ename, "DELETE ");
794 if (event & HAL_FILE_MONITOR_EVENT_CHANGE)
796 g_string_append (ename, "CHANGE ");
799 printf("Received event for %s: %s\n", path, g_string_free (ename, FALSE));
805 const char *path = "/tmp/kqueue.d";
808 HalFileMonitor *monitor;
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);
815 loop = g_main_loop_new (NULL, FALSE);
817 g_main_loop_run (loop);